diff --git a/.openzeppelin/mainnet.json b/.openzeppelin/mainnet.json
index 3f1e6fa6..89740106 100644
--- a/.openzeppelin/mainnet.json
+++ b/.openzeppelin/mainnet.json
@@ -1767,8 +1767,8 @@
}
},
"8dcb1ee047f8720601ec56f8a96045de3d207b3cea6564105d3ee438a30d5802": {
- "address": "0xefED40D1eb1577d1073e9C4F277463486D39b084",
- "txHash": "0x4e820b8e15298d5b1fbe30e151e0a0b16c0c23948e28dc84ae9a52280104b5be",
+ "address": "0xD4998Cc1ba435298C521f250b81856B1F25C8455",
+ "txHash": "0xf51d10838d35cb2d97fd55f1c32c3399384133dd95ca2043125666c9245bb5de",
"layout": {
"solcVersion": "0.8.9",
"storage": [
@@ -1878,20 +1878,36 @@
"src": "contracts/access/WithMidasAccessControl.sol:24"
},
{
- "label": "metadata",
+ "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": "mTBILL",
- "src": "contracts/mTBILL.sol:29"
+ "src": "contracts/mTBILL/mTBILL.sol:18"
},
{
"label": "__gap",
"offset": 0,
- "slot": "203",
+ "slot": "303",
"type": "t_array(t_uint256)50_storage",
"contract": "mTBILL",
- "src": "contracts/mTBILL.sol:34"
+ "src": "contracts/mTBILL/mTBILL.sol:23"
}
],
"types": {
diff --git a/.openzeppelin/sepolia.json b/.openzeppelin/sepolia.json
index 90d50f25..4a92431c 100644
--- a/.openzeppelin/sepolia.json
+++ b/.openzeppelin/sepolia.json
@@ -784,6 +784,46 @@
"address": "0x75726da161ef6aE712e941E00705879715b260f6",
"txHash": "0xcb8a84e91a264b44fc00c807ea3736e68d2fa84562889cbd9062d8747963febb",
"kind": "transparent"
+ },
+ {
+ "address": "0xbf21e448410BAA6039e73033F530027143c0c280",
+ "txHash": "0xe7d50e32586a9b999bd075b487036652d3bbfb3395c6c565c89335a5a828e162",
+ "kind": "transparent"
+ },
+ {
+ "address": "0xc6Ad27a2446Aa6223512D9FF8A6f3440a20ccd12",
+ "txHash": "0x174016bd9ade21fb7b15606e276313cacf21ebd836231b8371e320c386152573",
+ "kind": "transparent"
+ },
+ {
+ "address": "0x982550a433239C23BFe6C57005A7396D2Ed706d2",
+ "txHash": "0x630698c2dd09bfe7b4c2570ecc17cd547d4045909f351407d7348d55bb8b8b02",
+ "kind": "transparent"
+ },
+ {
+ "address": "0x9393811adDd7F7Ff60D1be11DbD29025A15bf630",
+ "txHash": "0x0b6bf43b19dc9ca9fda56bc73d1bd8d8913eec82f01a7f5b021ff9eb3e02d7fb",
+ "kind": "transparent"
+ },
+ {
+ "address": "0x2Abf7B5766Fc75bdb99e7aD76d6B539D08F3f8E1",
+ "txHash": "0x4964eb928bffa347c2db9a3d25da2ba1084bde4b7d5c6c7d5c85d0877f3b33ae",
+ "kind": "transparent"
+ },
+ {
+ "address": "0x95059CaD850a0531dd9b086F019C7f1ACB15955c",
+ "txHash": "0x6bc9c4a7a2e50a05fc70dd3b41dd21e15b6ffbcc249541b79abf729debefb2f4",
+ "kind": "transparent"
+ },
+ {
+ "address": "0x95AACf1bAD48336B0a62E90C2799fA8623CD9128",
+ "txHash": "0x2e0e2e3d93a9194c18d04c1898b65651964f14613a2303c0d02c3d358cda1265",
+ "kind": "transparent"
+ },
+ {
+ "address": "0x4e830D858c253C81ACF225E7101DF820D0e98415",
+ "txHash": "0x30daf803fd617c6734d733c5d48dc5bda3ca82784b153062caa086e8d7a50219",
+ "kind": "transparent"
}
],
"impls": {
@@ -3237,20 +3277,36 @@
"src": "contracts/access/WithMidasAccessControl.sol:24"
},
{
- "label": "metadata",
+ "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": "mTBILL",
- "src": "contracts/mTBILL.sol:29"
+ "src": "contracts/mTBILL/mTBILL.sol:18"
},
{
"label": "__gap",
"offset": 0,
- "slot": "203",
+ "slot": "303",
"type": "t_array(t_uint256)50_storage",
"contract": "mTBILL",
- "src": "contracts/mTBILL.sol:34"
+ "src": "contracts/mTBILL/mTBILL.sol:23"
}
],
"types": {
@@ -46498,11 +46554,11 @@
}
}
},
- "c518b0b692d61f7c9db1bc9da6cc655d25c957482487018d343f2cb778fe71f1": {
- "address": "0xdEaA809C72b8Aabbe9037F497B2779Ad3f7d4f87",
- "txHash": "0xd4470fc2dba4177ae6937ab4fa32b612f94b3b5e6936d2f26d7ce2ac5621b326",
+ "3ef1a8831ff39394090b1cd20d6b0082bde8f25035625f08aece01b4a32fb6ee": {
+ "address": "0x3256a123Ea7FAfc783b15c158D60db5eFB9895Ed",
+ "txHash": "0x1dc32471e107d70d4f672d132513d0cfc653a8f134913bd5f5899f82a22c5e8f",
"layout": {
- "solcVersion": "0.8.9",
+ "solcVersion": "0.8.34",
"storage": [
{
"label": "_initialized",
@@ -46521,269 +46577,6517 @@
"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"
+ "contract": "ContextUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36"
},
{
"label": "__gap",
"offset": 0,
"slot": "51",
"type": "t_array(t_uint256)50_storage",
- "contract": "ContextUpgradeable",
- "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36"
+ "contract": "ERC165Upgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol:41"
},
{
- "label": "_paused",
+ "label": "_roles",
"offset": 0,
"slot": "101",
- "type": "t_bool",
- "contract": "PausableUpgradeable",
- "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:29"
+ "type": "t_mapping(t_bytes32,t_struct(RoleData)9957_storage)",
+ "contract": "AccessControlUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:62"
},
{
"label": "__gap",
"offset": 0,
"slot": "102",
"type": "t_array(t_uint256)49_storage",
- "contract": "PausableUpgradeable",
- "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:116"
+ "contract": "AccessControlUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:260"
},
{
- "label": "fnPaused",
+ "label": "isUserFacingRole",
"offset": 0,
"slot": "151",
- "type": "t_mapping(t_bytes4,t_bool)",
- "contract": "Pausable",
- "src": "contracts/access/Pausable.sol:14"
+ "type": "t_mapping(t_bytes32,t_bool)",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:29"
},
{
- "label": "__gap",
+ "label": "_grantOperatorRoles",
"offset": 0,
"slot": "152",
- "type": "t_array(t_uint256)50_storage",
- "contract": "Pausable",
- "src": "contracts/access/Pausable.sol:19"
+ "type": "t_mapping(t_bytes32,t_mapping(t_address,t_bool))",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:34"
},
{
- "label": "__gap",
+ "label": "_permissionRoles",
"offset": 0,
- "slot": "202",
- "type": "t_array(t_uint256)50_storage",
- "contract": "Blacklistable",
- "src": "contracts/access/Blacklistable.sol:16"
+ "slot": "153",
+ "type": "t_mapping(t_bytes32,t_mapping(t_address,t_bool))",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:39"
},
{
- "label": "greenlistEnabled",
+ "label": "_roleTimelockDelays",
"offset": 0,
- "slot": "252",
- "type": "t_bool",
- "contract": "Greenlistable",
- "src": "contracts/access/Greenlistable.sol:16"
+ "slot": "154",
+ "type": "t_mapping(t_bytes32,t_uint32)",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:44"
},
{
- "label": "__gap",
+ "label": "timelockManager",
"offset": 0,
- "slot": "253",
- "type": "t_array(t_uint256)50_storage",
- "contract": "Greenlistable",
- "src": "contracts/access/Greenlistable.sol:21"
+ "slot": "155",
+ "type": "t_address",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:49"
},
{
- "label": "sanctionsList",
+ "label": "pauseManager",
"offset": 0,
- "slot": "303",
+ "slot": "156",
"type": "t_address",
- "contract": "WithSanctionsList",
- "src": "contracts/abstract/WithSanctionsList.sol:18"
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:54"
+ },
+ {
+ "label": "defaultDelay",
+ "offset": 20,
+ "slot": "156",
+ "type": "t_uint32",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:59"
},
{
"label": "__gap",
"offset": 0,
- "slot": "304",
+ "slot": "157",
"type": "t_array(t_uint256)50_storage",
- "contract": "WithSanctionsList",
- "src": "contracts/abstract/WithSanctionsList.sol:23"
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:64"
+ }
+ ],
+ "types": {
+ "t_address": {
+ "label": "address",
+ "numberOfBytes": "20"
+ },
+ "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_mapping(t_address,t_bool)": {
+ "label": "mapping(address => bool)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_bool)": {
+ "label": "mapping(bytes32 => bool)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_mapping(t_address,t_bool))": {
+ "label": "mapping(bytes32 => mapping(address => bool))",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_struct(RoleData)9957_storage)": {
+ "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_uint32)": {
+ "label": "mapping(bytes32 => uint32)",
+ "numberOfBytes": "32"
+ },
+ "t_struct(RoleData)9957_storage": {
+ "label": "struct AccessControlUpgradeable.RoleData",
+ "members": [
+ {
+ "label": "members",
+ "type": "t_mapping(t_address,t_bool)",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "adminRole",
+ "type": "t_bytes32",
+ "offset": 0,
+ "slot": "1"
+ }
+ ],
+ "numberOfBytes": "64"
},
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint32": {
+ "label": "uint32",
+ "numberOfBytes": "4"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "a126b9c885bba301b6802e2191f6360a6b7bdc5e260785f1037251a8d7ccb0d1": {
+ "address": "0x10Fad91a1E468CC887C4caBd084AFC4678a3f576",
+ "txHash": "0x444d4882871bead83b845b0909fa189fd0471214154958d2f275940832c90582",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "storage": [
{
- "label": "currentRequestId",
+ "label": "_initialized",
"offset": 0,
- "slot": "354",
- "type": "t_struct(Counter)8079_storage",
- "contract": "ManageableVault",
- "src": "contracts/abstract/ManageableVault.sol:53"
+ "slot": "0",
+ "type": "t_uint8",
+ "contract": "Initializable",
+ "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63",
+ "retypedFrom": "bool"
},
{
- "label": "mToken",
- "offset": 0,
- "slot": "355",
- "type": "t_contract(IMToken)20077",
- "contract": "ManageableVault",
- "src": "contracts/abstract/ManageableVault.sol:66"
+ "label": "_initializing",
+ "offset": 1,
+ "slot": "0",
+ "type": "t_bool",
+ "contract": "Initializable",
+ "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68"
},
{
- "label": "mTokenDataFeed",
- "offset": 0,
- "slot": "356",
- "type": "t_contract(IDataFeed)19792",
- "contract": "ManageableVault",
- "src": "contracts/abstract/ManageableVault.sol:71"
+ "label": "accessControl",
+ "offset": 2,
+ "slot": "0",
+ "type": "t_contract(IMidasAccessControl)37005",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:31",
+ "retypedFrom": "MidasAccessControl"
},
{
- "label": "tokensReceiver",
+ "label": "__gap",
"offset": 0,
- "slot": "357",
- "type": "t_address",
- "contract": "ManageableVault",
- "src": "contracts/abstract/ManageableVault.sol:76"
+ "slot": "1",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:36"
},
{
- "label": "instantFee",
+ "label": "contractPaused",
"offset": 0,
- "slot": "358",
- "type": "t_uint256",
- "contract": "ManageableVault",
- "src": "contracts/abstract/ManageableVault.sol:81"
+ "slot": "51",
+ "type": "t_mapping(t_address,t_bool)",
+ "contract": "MidasPauseManager",
+ "src": "contracts/access/MidasPauseManager.sol:31"
},
{
- "label": "instantDailyLimit",
+ "label": "contractFnPaused",
"offset": 0,
- "slot": "359",
- "type": "t_uint256",
- "contract": "ManageableVault",
- "src": "contracts/abstract/ManageableVault.sol:88"
+ "slot": "52",
+ "type": "t_mapping(t_address,t_mapping(t_bytes4,t_bool))",
+ "contract": "MidasPauseManager",
+ "src": "contracts/access/MidasPauseManager.sol:36"
},
{
- "label": "dailyLimits",
+ "label": "pauseDelay",
"offset": 0,
- "slot": "360",
- "type": "t_mapping(t_uint256,t_uint256)",
- "contract": "ManageableVault",
- "src": "contracts/abstract/ManageableVault.sol:93"
+ "slot": "53",
+ "type": "t_uint32",
+ "contract": "MidasPauseManager",
+ "src": "contracts/access/MidasPauseManager.sol:41"
},
{
- "label": "feeReceiver",
+ "label": "unpauseDelay",
+ "offset": 4,
+ "slot": "53",
+ "type": "t_uint32",
+ "contract": "MidasPauseManager",
+ "src": "contracts/access/MidasPauseManager.sol:46"
+ },
+ {
+ "label": "globalPaused",
+ "offset": 8,
+ "slot": "53",
+ "type": "t_bool",
+ "contract": "MidasPauseManager",
+ "src": "contracts/access/MidasPauseManager.sol:51"
+ }
+ ],
+ "types": {
+ "t_address": {
+ "label": "address",
+ "numberOfBytes": "20"
+ },
+ "t_array(t_uint256)50_storage": {
+ "label": "uint256[50]",
+ "numberOfBytes": "1600"
+ },
+ "t_bool": {
+ "label": "bool",
+ "numberOfBytes": "1"
+ },
+ "t_bytes4": {
+ "label": "bytes4",
+ "numberOfBytes": "4"
+ },
+ "t_contract(IMidasAccessControl)37005": {
+ "label": "contract IMidasAccessControl",
+ "numberOfBytes": "20"
+ },
+ "t_mapping(t_address,t_bool)": {
+ "label": "mapping(address => bool)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_address,t_mapping(t_bytes4,t_bool))": {
+ "label": "mapping(address => mapping(bytes4 => bool))",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes4,t_bool)": {
+ "label": "mapping(bytes4 => bool)",
+ "numberOfBytes": "32"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint32": {
+ "label": "uint32",
+ "numberOfBytes": "4"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "e2315ea6241d2ea6edfd531a9cc8c7fcd75b525e334d1763756efa59fc902271": {
+ "address": "0xE4107C64A9ab2B2730de7c7035324bfE3993edC6",
+ "txHash": "0x8a17a1b65a6a12321badaf85686a7a05f5d47253ea15919432f9f6de9b8a2563",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "storage": [
+ {
+ "label": "_initialized",
"offset": 0,
- "slot": "361",
- "type": "t_address",
- "contract": "ManageableVault",
- "src": "contracts/abstract/ManageableVault.sol:98"
+ "slot": "0",
+ "type": "t_uint8",
+ "contract": "Initializable",
+ "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63",
+ "retypedFrom": "bool"
},
{
- "label": "variationTolerance",
+ "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(IMidasAccessControl)37005",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:31",
+ "retypedFrom": "MidasAccessControl"
+ },
+ {
+ "label": "__gap",
"offset": 0,
- "slot": "362",
- "type": "t_uint256",
- "contract": "ManageableVault",
- "src": "contracts/abstract/ManageableVault.sol:103"
+ "slot": "1",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:36"
},
{
- "label": "waivedFeeRestriction",
+ "label": "_status",
"offset": 0,
- "slot": "363",
- "type": "t_mapping(t_address,t_bool)",
- "contract": "ManageableVault",
- "src": "contracts/abstract/ManageableVault.sol:108"
+ "slot": "51",
+ "type": "t_uint256",
+ "contract": "ReentrancyGuardUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:38"
},
{
- "label": "_paymentTokens",
+ "label": "__gap",
"offset": 0,
- "slot": "364",
- "type": "t_struct(AddressSet)3891_storage",
- "contract": "ManageableVault",
- "src": "contracts/abstract/ManageableVault.sol:113"
+ "slot": "52",
+ "type": "t_array(t_uint256)49_storage",
+ "contract": "ReentrancyGuardUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:88"
},
{
- "label": "tokensConfig",
+ "label": "dataHashIndexes",
"offset": 0,
- "slot": "366",
- "type": "t_mapping(t_address,t_struct(TokenConfig)20090_storage)",
- "contract": "ManageableVault",
- "src": "contracts/abstract/ManageableVault.sol:118"
+ "slot": "101",
+ "type": "t_mapping(t_bytes32,t_uint256)",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:84"
},
{
- "label": "minAmount",
+ "label": "proposerPendingOperationsCount",
"offset": 0,
- "slot": "367",
- "type": "t_uint256",
- "contract": "ManageableVault",
- "src": "contracts/abstract/ManageableVault.sol:123"
+ "slot": "102",
+ "type": "t_mapping(t_address,t_uint256)",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:89"
},
{
- "label": "isFreeFromMinAmount",
+ "label": "_securityCouncils",
"offset": 0,
- "slot": "368",
- "type": "t_mapping(t_address,t_bool)",
- "contract": "ManageableVault",
- "src": "contracts/abstract/ManageableVault.sol:128"
+ "slot": "103",
+ "type": "t_mapping(t_uint256,t_struct(AddressSet)14890_storage)",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:94"
},
{
- "label": "__gap",
+ "label": "_operationDetails",
"offset": 0,
- "slot": "369",
- "type": "t_array(t_uint256)50_storage",
- "contract": "ManageableVault",
- "src": "contracts/abstract/ManageableVault.sol:133"
+ "slot": "104",
+ "type": "t_mapping(t_bytes32,t_struct(TimelockOperationDetails)31632_storage)",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:99"
},
{
- "label": "minMTokenAmountForFirstDeposit",
+ "label": "_pendingOperations",
"offset": 0,
- "slot": "419",
- "type": "t_uint256",
- "contract": "DepositVault",
- "src": "contracts/DepositVault.sol:78"
+ "slot": "105",
+ "type": "t_struct(Bytes32Set)14769_storage",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:104"
},
{
- "label": "mintRequests",
+ "label": "timelock",
"offset": 0,
- "slot": "420",
- "type": "t_mapping(t_uint256,t_struct(Request)19809_storage)",
- "contract": "DepositVault",
- "src": "contracts/DepositVault.sol:83"
+ "slot": "107",
+ "type": "t_address",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:109"
},
{
- "label": "totalMinted",
+ "label": "maxPendingOperationsPerProposer",
"offset": 0,
- "slot": "421",
- "type": "t_mapping(t_address,t_uint256)",
- "contract": "DepositVault",
- "src": "contracts/DepositVault.sol:88"
+ "slot": "108",
+ "type": "t_uint256",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:114"
},
{
- "label": "maxSupplyCap",
+ "label": "securityCouncilVersion",
"offset": 0,
- "slot": "422",
+ "slot": "109",
"type": "t_uint256",
- "contract": "DepositVault",
- "src": "contracts/DepositVault.sol:95"
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:119"
},
{
- "label": "__gap",
+ "label": "pendingSetCouncilOperationId",
"offset": 0,
- "slot": "423",
- "type": "t_array(t_uint256)49_storage",
- "contract": "DepositVault",
- "src": "contracts/DepositVault.sol:103"
+ "slot": "110",
+ "type": "t_bytes32",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:124"
},
{
"label": "__gap",
"offset": 0,
- "slot": "472",
+ "slot": "111",
"type": "t_array(t_uint256)50_storage",
- "contract": "MFOneDepositVault",
- "src": "contracts/products/mFONE/MFOneDepositVault.sol:16"
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:129"
+ }
+ ],
+ "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_contract(IMidasAccessControl)37005": {
+ "label": "contract IMidasAccessControl",
+ "numberOfBytes": "20"
+ },
+ "t_enum(TimelockOperationStatus)37167": {
+ "label": "enum TimelockOperationStatus",
+ "members": [
+ "NotExist",
+ "Pending",
+ "Paused",
+ "ApprovedExecution",
+ "ReadyToExecute",
+ "ReadyToAbort",
+ "Expired",
+ "Aborted",
+ "Executed"
+ ],
+ "numberOfBytes": "1"
+ },
+ "t_mapping(t_address,t_uint256)": {
+ "label": "mapping(address => uint256)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_struct(TimelockOperationDetails)31632_storage)": {
+ "label": "mapping(bytes32 => struct MidasTimelockManager.TimelockOperationDetails)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_uint256)": {
+ "label": "mapping(bytes32 => uint256)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint256,t_struct(AddressSet)14890_storage)": {
+ "label": "mapping(uint256 => struct EnumerableSetUpgradeable.AddressSet)",
+ "numberOfBytes": "32"
+ },
+ "t_struct(AddressSet)14890_storage": {
+ "label": "struct EnumerableSetUpgradeable.AddressSet",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)14575_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(Bytes32Set)14769_storage": {
+ "label": "struct EnumerableSetUpgradeable.Bytes32Set",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)14575_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(Set)14575_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(TimelockOperationDetails)31632_storage": {
+ "label": "struct MidasTimelockManager.TimelockOperationDetails",
+ "members": [
+ {
+ "label": "votersForExecution",
+ "type": "t_struct(AddressSet)14890_storage",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "votersForVeto",
+ "type": "t_struct(AddressSet)14890_storage",
+ "offset": 0,
+ "slot": "2"
+ },
+ {
+ "label": "councilVersion",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "4"
+ },
+ {
+ "label": "dataHash",
+ "type": "t_bytes32",
+ "offset": 0,
+ "slot": "5"
+ },
+ {
+ "label": "status",
+ "type": "t_enum(TimelockOperationStatus)37167",
+ "offset": 0,
+ "slot": "6"
+ },
+ {
+ "label": "pauseReasonCode",
+ "type": "t_uint8",
+ "offset": 1,
+ "slot": "6"
+ },
+ {
+ "label": "isSetCouncilOperation",
+ "type": "t_bool",
+ "offset": 2,
+ "slot": "6"
+ },
+ {
+ "label": "createdAt",
+ "type": "t_uint32",
+ "offset": 3,
+ "slot": "6"
+ },
+ {
+ "label": "executionApprovedAt",
+ "type": "t_uint32",
+ "offset": 7,
+ "slot": "6"
+ },
+ {
+ "label": "operationProposer",
+ "type": "t_address",
+ "offset": 11,
+ "slot": "6"
+ },
+ {
+ "label": "pauser",
+ "type": "t_address",
+ "offset": 0,
+ "slot": "7"
+ }
+ ],
+ "numberOfBytes": "256"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint32": {
+ "label": "uint32",
+ "numberOfBytes": "4"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "7262a056736a11dbb5a53975a018c7e829bf0a9db69b5d4a7f2a9b382e23c660": {
+ "address": "0x7326b037332Cb248Eb007d643649F4d742A26eec",
+ "txHash": "0xae9704e9065c3a47b15f6ce7eff4b81a40bcbcbc487495a9e43b966e113388c4",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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": "__gap",
+ "offset": 0,
+ "slot": "51",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "ERC165Upgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol:41"
+ },
+ {
+ "label": "_roles",
+ "offset": 0,
+ "slot": "101",
+ "type": "t_mapping(t_bytes32,t_struct(RoleData)9957_storage)",
+ "contract": "AccessControlUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:62"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "102",
+ "type": "t_array(t_uint256)49_storage",
+ "contract": "AccessControlUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:260"
+ },
+ {
+ "label": "_timestamps",
+ "offset": 0,
+ "slot": "151",
+ "type": "t_mapping(t_bytes32,t_uint256)",
+ "contract": "TimelockControllerUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/governance/TimelockControllerUpgradeable.sol:33"
+ },
+ {
+ "label": "_minDelay",
+ "offset": 0,
+ "slot": "152",
+ "type": "t_uint256",
+ "contract": "TimelockControllerUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/governance/TimelockControllerUpgradeable.sol:34"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "153",
+ "type": "t_array(t_uint256)48_storage",
+ "contract": "TimelockControllerUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/governance/TimelockControllerUpgradeable.sol:433"
+ },
+ {
+ "label": "timelockManager",
+ "offset": 0,
+ "slot": "201",
+ "type": "t_address",
+ "contract": "MidasAccessControlTimelockController",
+ "src": "contracts/access/MidasAccessControlTimelockController.sol:19"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "202",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "MidasAccessControlTimelockController",
+ "src": "contracts/access/MidasAccessControlTimelockController.sol:24"
+ }
+ ],
+ "types": {
+ "t_address": {
+ "label": "address",
+ "numberOfBytes": "20"
+ },
+ "t_array(t_uint256)48_storage": {
+ "label": "uint256[48]",
+ "numberOfBytes": "1536"
+ },
+ "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_mapping(t_address,t_bool)": {
+ "label": "mapping(address => bool)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_struct(RoleData)9957_storage)": {
+ "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_uint256)": {
+ "label": "mapping(bytes32 => uint256)",
+ "numberOfBytes": "32"
+ },
+ "t_struct(RoleData)9957_storage": {
+ "label": "struct AccessControlUpgradeable.RoleData",
+ "members": [
+ {
+ "label": "members",
+ "type": "t_mapping(t_address,t_bool)",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "adminRole",
+ "type": "t_bytes32",
+ "offset": 0,
+ "slot": "1"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "41882497f70e7e9c3e719a15270b2950e9a1da6a5e5dc447d681bf3dbee9d443": {
+ "address": "0x83BB73f653F93e3E0B52Ef323f93D767d5dB924F",
+ "txHash": "0x16daf1436d7134c942e6822e5a4cc0505569cca59835eccab90c8221c88b4a14",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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(IMidasAccessControl)37005",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:31",
+ "retypedFrom": "MidasAccessControl"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "1",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:36"
+ },
+ {
+ "label": "description",
+ "offset": 0,
+ "slot": "51",
+ "type": "t_string_storage",
+ "contract": "CustomAggregatorV3CompatibleFeed",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:40"
+ },
+ {
+ "label": "latestRound",
+ "offset": 0,
+ "slot": "52",
+ "type": "t_uint80",
+ "contract": "CustomAggregatorV3CompatibleFeed",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:45"
+ },
+ {
+ "label": "maxAnswerDeviation",
+ "offset": 0,
+ "slot": "53",
+ "type": "t_uint256",
+ "contract": "CustomAggregatorV3CompatibleFeed",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:51"
+ },
+ {
+ "label": "minAnswer",
+ "offset": 0,
+ "slot": "54",
+ "type": "t_int192",
+ "contract": "CustomAggregatorV3CompatibleFeed",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:56"
+ },
+ {
+ "label": "maxAnswer",
+ "offset": 0,
+ "slot": "55",
+ "type": "t_int192",
+ "contract": "CustomAggregatorV3CompatibleFeed",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:61"
+ },
+ {
+ "label": "_roundData",
+ "offset": 0,
+ "slot": "56",
+ "type": "t_mapping(t_uint80,t_struct(RoundData)33952_storage)",
+ "contract": "CustomAggregatorV3CompatibleFeed",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:66"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "57",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "CustomAggregatorV3CompatibleFeed",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:71"
+ }
+ ],
+ "types": {
+ "t_array(t_uint256)50_storage": {
+ "label": "uint256[50]",
+ "numberOfBytes": "1600"
+ },
+ "t_bool": {
+ "label": "bool",
+ "numberOfBytes": "1"
+ },
+ "t_contract(IMidasAccessControl)37005": {
+ "label": "contract IMidasAccessControl",
+ "numberOfBytes": "20"
+ },
+ "t_int192": {
+ "label": "int192",
+ "numberOfBytes": "24"
+ },
+ "t_int256": {
+ "label": "int256",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint80,t_struct(RoundData)33952_storage)": {
+ "label": "mapping(uint80 => struct CustomAggregatorV3CompatibleFeed.RoundData)",
+ "numberOfBytes": "32"
+ },
+ "t_string_storage": {
+ "label": "string",
+ "numberOfBytes": "32"
+ },
+ "t_struct(RoundData)33952_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"
+ }
+ }
+ }
+ },
+ "dac7ae292c3bf8cfe0ea3840ff0efb17d72f65abad2854c40b40513843d9a54c": {
+ "address": "0xD30d95068E98683123e4e0F6CfA7562cc8aA8AA0",
+ "txHash": "0x3d0f23dfc31b27cc803d62fef382916fe3812c75cceb0c9616d2c1750e294b6f",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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(IMidasAccessControl)37005",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:31",
+ "retypedFrom": "MidasAccessControl"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "1",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:36"
+ },
+ {
+ "label": "description",
+ "offset": 0,
+ "slot": "51",
+ "type": "t_string_storage",
+ "contract": "CustomAggregatorV3CompatibleFeedGrowth",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol:50"
+ },
+ {
+ "label": "maxAnswerDeviation",
+ "offset": 0,
+ "slot": "52",
+ "type": "t_uint256",
+ "contract": "CustomAggregatorV3CompatibleFeedGrowth",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol:56"
+ },
+ {
+ "label": "minAnswer",
+ "offset": 0,
+ "slot": "53",
+ "type": "t_int192",
+ "contract": "CustomAggregatorV3CompatibleFeedGrowth",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol:61"
+ },
+ {
+ "label": "maxAnswer",
+ "offset": 0,
+ "slot": "54",
+ "type": "t_int192",
+ "contract": "CustomAggregatorV3CompatibleFeedGrowth",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol:66"
+ },
+ {
+ "label": "minGrowthApr",
+ "offset": 0,
+ "slot": "55",
+ "type": "t_int80",
+ "contract": "CustomAggregatorV3CompatibleFeedGrowth",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol:71"
+ },
+ {
+ "label": "maxGrowthApr",
+ "offset": 10,
+ "slot": "55",
+ "type": "t_int80",
+ "contract": "CustomAggregatorV3CompatibleFeedGrowth",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol:76"
+ },
+ {
+ "label": "latestRound",
+ "offset": 20,
+ "slot": "55",
+ "type": "t_uint80",
+ "contract": "CustomAggregatorV3CompatibleFeedGrowth",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol:81"
+ },
+ {
+ "label": "onlyUp",
+ "offset": 30,
+ "slot": "55",
+ "type": "t_bool",
+ "contract": "CustomAggregatorV3CompatibleFeedGrowth",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol:87"
+ },
+ {
+ "label": "_roundData",
+ "offset": 0,
+ "slot": "56",
+ "type": "t_mapping(t_uint80,t_struct(RoundDataWithGrowth)34621_storage)",
+ "contract": "CustomAggregatorV3CompatibleFeedGrowth",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol:92"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "57",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "CustomAggregatorV3CompatibleFeedGrowth",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol:97"
+ },
+ {
+ "label": "___gap",
+ "offset": 0,
+ "slot": "107",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "CustomAggregatorV3CompatibleFeedGrowth",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol:102"
+ }
+ ],
+ "types": {
+ "t_array(t_uint256)50_storage": {
+ "label": "uint256[50]",
+ "numberOfBytes": "1600"
+ },
+ "t_bool": {
+ "label": "bool",
+ "numberOfBytes": "1"
+ },
+ "t_contract(IMidasAccessControl)37005": {
+ "label": "contract IMidasAccessControl",
+ "numberOfBytes": "20"
+ },
+ "t_int192": {
+ "label": "int192",
+ "numberOfBytes": "24"
+ },
+ "t_int256": {
+ "label": "int256",
+ "numberOfBytes": "32"
+ },
+ "t_int80": {
+ "label": "int80",
+ "numberOfBytes": "10"
+ },
+ "t_mapping(t_uint80,t_struct(RoundDataWithGrowth)34621_storage)": {
+ "label": "mapping(uint80 => struct CustomAggregatorV3CompatibleFeedGrowth.RoundDataWithGrowth)",
+ "numberOfBytes": "32"
+ },
+ "t_string_storage": {
+ "label": "string",
+ "numberOfBytes": "32"
+ },
+ "t_struct(RoundDataWithGrowth)34621_storage": {
+ "label": "struct CustomAggregatorV3CompatibleFeedGrowth.RoundDataWithGrowth",
+ "members": [
+ {
+ "label": "roundId",
+ "type": "t_uint80",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "answeredInRound",
+ "type": "t_uint80",
+ "offset": 10,
+ "slot": "0"
+ },
+ {
+ "label": "growthApr",
+ "type": "t_int80",
+ "offset": 20,
+ "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"
+ }
+ ],
+ "numberOfBytes": "128"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ },
+ "t_uint80": {
+ "label": "uint80",
+ "numberOfBytes": "10"
+ }
+ }
+ }
+ },
+ "8c48189fe801cb8895506566da34c8343e43270f28c937c1cddcdf7cbae78543": {
+ "address": "0x88557BCbb69b676810ca951E8b5C84837A7C9a2f",
+ "txHash": "0x81b770a2726e89a2f6401a9eaf3d05b21869d905729b865b2eaece5b957f4368",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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(IMidasAccessControl)37005",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:31",
+ "retypedFrom": "MidasAccessControl"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "1",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:36"
+ },
+ {
+ "label": "aggregator",
+ "offset": 0,
+ "slot": "51",
+ "type": "t_contract(AggregatorV3Interface)1801",
+ "contract": "DataFeed",
+ "src": "contracts/feeds/DataFeed.sol:31"
+ },
+ {
+ "label": "healthyDiff",
+ "offset": 0,
+ "slot": "52",
+ "type": "t_uint256",
+ "contract": "DataFeed",
+ "src": "contracts/feeds/DataFeed.sol:36"
+ },
+ {
+ "label": "minExpectedAnswer",
+ "offset": 0,
+ "slot": "53",
+ "type": "t_int256",
+ "contract": "DataFeed",
+ "src": "contracts/feeds/DataFeed.sol:41"
+ },
+ {
+ "label": "maxExpectedAnswer",
+ "offset": 0,
+ "slot": "54",
+ "type": "t_int256",
+ "contract": "DataFeed",
+ "src": "contracts/feeds/DataFeed.sol:46"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "55",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "DataFeed",
+ "src": "contracts/feeds/DataFeed.sol:51"
+ },
+ {
+ "label": "___gap",
+ "offset": 0,
+ "slot": "105",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "DataFeed",
+ "src": "contracts/feeds/DataFeed.sol:56"
+ }
+ ],
+ "types": {
+ "t_array(t_uint256)50_storage": {
+ "label": "uint256[50]",
+ "numberOfBytes": "1600"
+ },
+ "t_bool": {
+ "label": "bool",
+ "numberOfBytes": "1"
+ },
+ "t_contract(AggregatorV3Interface)1801": {
+ "label": "contract AggregatorV3Interface",
+ "numberOfBytes": "20"
+ },
+ "t_contract(IMidasAccessControl)37005": {
+ "label": "contract IMidasAccessControl",
+ "numberOfBytes": "20"
+ },
+ "t_int256": {
+ "label": "int256",
+ "numberOfBytes": "32"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "d329f90349be99f0c1fc5cb2710494c305693139536df3f57185fb041907ef10": {
+ "address": "0x25d6bA13aF3742eb57D691107d4277a79812Ea71",
+ "txHash": "0x645125e0c43d89d7c9c1a0eb6bf8459dd880169adccfe5d162d53592ea796860",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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(IMidasAccessControl)37005",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:31",
+ "retypedFrom": "MidasAccessControl"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "202",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:36"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "252",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "Blacklistable",
+ "src": "contracts/access/Blacklistable.sol:17"
+ },
+ {
+ "label": "metadata",
+ "offset": 0,
+ "slot": "302",
+ "type": "t_mapping(t_bytes32,t_bytes_storage)",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:48"
+ },
+ {
+ "label": "_mintRateLimits",
+ "offset": 0,
+ "slot": "303",
+ "type": "t_struct(WindowRateLimits)39059_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:53"
+ },
+ {
+ "label": "clawbackReceiver",
+ "offset": 0,
+ "slot": "306",
+ "type": "t_address",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:58"
+ },
+ {
+ "label": "_inClawback",
+ "offset": 20,
+ "slot": "306",
+ "type": "t_bool",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:63"
+ },
+ {
+ "label": "_name",
+ "offset": 0,
+ "slot": "307",
+ "type": "t_string_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:68"
+ },
+ {
+ "label": "_symbol",
+ "offset": 0,
+ "slot": "308",
+ "type": "t_string_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:73"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "309",
+ "type": "t_array(t_uint256)44_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:78"
+ },
+ {
+ "label": "___gap",
+ "offset": 0,
+ "slot": "353",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:83"
+ }
+ ],
+ "types": {
+ "t_address": {
+ "label": "address",
+ "numberOfBytes": "20"
+ },
+ "t_array(t_bytes32)dyn_storage": {
+ "label": "bytes32[]",
+ "numberOfBytes": "32"
+ },
+ "t_array(t_uint256)44_storage": {
+ "label": "uint256[44]",
+ "numberOfBytes": "1408"
+ },
+ "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(IMidasAccessControl)37005": {
+ "label": "contract IMidasAccessControl",
+ "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_mapping(t_bytes32,t_uint256)": {
+ "label": "mapping(bytes32 => uint256)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)39049_storage)": {
+ "label": "mapping(uint256 => struct RateLimitLibrary.WindowRateLimitConfig)",
+ "numberOfBytes": "32"
+ },
+ "t_string_storage": {
+ "label": "string",
+ "numberOfBytes": "32"
+ },
+ "t_struct(Set)14575_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(UintSet)15047_storage": {
+ "label": "struct EnumerableSetUpgradeable.UintSet",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)14575_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(WindowRateLimitConfig)39049_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimitConfig",
+ "members": [
+ {
+ "label": "limit",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "amountInFlight",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "1"
+ },
+ {
+ "label": "lastUpdated",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "2"
+ },
+ {
+ "label": "window",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "3"
+ }
+ ],
+ "numberOfBytes": "128"
+ },
+ "t_struct(WindowRateLimits)39059_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimits",
+ "members": [
+ {
+ "label": "windows",
+ "type": "t_struct(UintSet)15047_storage",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "configs",
+ "type": "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)39049_storage)",
+ "offset": 0,
+ "slot": "2"
+ }
+ ],
+ "numberOfBytes": "96"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "ff1293c348820419429b1c45b4f5cec2c1d888d62a96eae2dda72edff6a201af": {
+ "address": "0xE9999c3D9869D0128F35a689E6ea19Cb12B328FA",
+ "txHash": "0x99945d87801306bf946fdee0a52dbe0d3c69c35a073fb9511c4300cc086ca095",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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(IMidasAccessControl)37005",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:31",
+ "retypedFrom": "MidasAccessControl"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "202",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:36"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "252",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "Blacklistable",
+ "src": "contracts/access/Blacklistable.sol:17"
+ },
+ {
+ "label": "metadata",
+ "offset": 0,
+ "slot": "302",
+ "type": "t_mapping(t_bytes32,t_bytes_storage)",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:48"
+ },
+ {
+ "label": "_mintRateLimits",
+ "offset": 0,
+ "slot": "303",
+ "type": "t_struct(WindowRateLimits)39059_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:53"
+ },
+ {
+ "label": "clawbackReceiver",
+ "offset": 0,
+ "slot": "306",
+ "type": "t_address",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:58"
+ },
+ {
+ "label": "_inClawback",
+ "offset": 20,
+ "slot": "306",
+ "type": "t_bool",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:63"
+ },
+ {
+ "label": "_name",
+ "offset": 0,
+ "slot": "307",
+ "type": "t_string_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:68"
+ },
+ {
+ "label": "_symbol",
+ "offset": 0,
+ "slot": "308",
+ "type": "t_string_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:73"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "309",
+ "type": "t_array(t_uint256)44_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:78"
+ },
+ {
+ "label": "___gap",
+ "offset": 0,
+ "slot": "353",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:83"
+ }
+ ],
+ "types": {
+ "t_address": {
+ "label": "address",
+ "numberOfBytes": "20"
+ },
+ "t_array(t_bytes32)dyn_storage": {
+ "label": "bytes32[]",
+ "numberOfBytes": "32"
+ },
+ "t_array(t_uint256)44_storage": {
+ "label": "uint256[44]",
+ "numberOfBytes": "1408"
+ },
+ "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(IMidasAccessControl)37005": {
+ "label": "contract IMidasAccessControl",
+ "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_mapping(t_bytes32,t_uint256)": {
+ "label": "mapping(bytes32 => uint256)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)39049_storage)": {
+ "label": "mapping(uint256 => struct RateLimitLibrary.WindowRateLimitConfig)",
+ "numberOfBytes": "32"
+ },
+ "t_string_storage": {
+ "label": "string",
+ "numberOfBytes": "32"
+ },
+ "t_struct(Set)14575_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(UintSet)15047_storage": {
+ "label": "struct EnumerableSetUpgradeable.UintSet",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)14575_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(WindowRateLimitConfig)39049_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimitConfig",
+ "members": [
+ {
+ "label": "limit",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "amountInFlight",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "1"
+ },
+ {
+ "label": "lastUpdated",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "2"
+ },
+ {
+ "label": "window",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "3"
+ }
+ ],
+ "numberOfBytes": "128"
+ },
+ "t_struct(WindowRateLimits)39059_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimits",
+ "members": [
+ {
+ "label": "windows",
+ "type": "t_struct(UintSet)15047_storage",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "configs",
+ "type": "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)39049_storage)",
+ "offset": 0,
+ "slot": "2"
+ }
+ ],
+ "numberOfBytes": "96"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "9212f7c0d6393cbf9f66f44dacee270a8914f2d880b22005c76b4244e8db1243": {
+ "address": "0xa8988ea6B4a94A15e8185215f49a32c9e5E12C5F",
+ "txHash": "0xc0eeff356cd73b771f6bf4dc9e35cf9a546f5bf8d2cc05d1b8dff24ad9338e9a",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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(IMidasAccessControl)37005",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:31",
+ "retypedFrom": "MidasAccessControl"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "1",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:36"
+ },
+ {
+ "label": "aggregator",
+ "offset": 0,
+ "slot": "51",
+ "type": "t_contract(AggregatorV3Interface)1801",
+ "contract": "DataFeed",
+ "src": "contracts/feeds/DataFeed.sol:31"
+ },
+ {
+ "label": "healthyDiff",
+ "offset": 0,
+ "slot": "52",
+ "type": "t_uint256",
+ "contract": "DataFeed",
+ "src": "contracts/feeds/DataFeed.sol:36"
+ },
+ {
+ "label": "minExpectedAnswer",
+ "offset": 0,
+ "slot": "53",
+ "type": "t_int256",
+ "contract": "DataFeed",
+ "src": "contracts/feeds/DataFeed.sol:41"
+ },
+ {
+ "label": "maxExpectedAnswer",
+ "offset": 0,
+ "slot": "54",
+ "type": "t_int256",
+ "contract": "DataFeed",
+ "src": "contracts/feeds/DataFeed.sol:46"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "55",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "DataFeed",
+ "src": "contracts/feeds/DataFeed.sol:51"
+ },
+ {
+ "label": "___gap",
+ "offset": 0,
+ "slot": "105",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "DataFeed",
+ "src": "contracts/feeds/DataFeed.sol:56"
+ }
+ ],
+ "types": {
+ "t_array(t_uint256)50_storage": {
+ "label": "uint256[50]",
+ "numberOfBytes": "1600"
+ },
+ "t_bool": {
+ "label": "bool",
+ "numberOfBytes": "1"
+ },
+ "t_contract(AggregatorV3Interface)1801": {
+ "label": "contract AggregatorV3Interface",
+ "numberOfBytes": "20"
+ },
+ "t_contract(IMidasAccessControl)37005": {
+ "label": "contract IMidasAccessControl",
+ "numberOfBytes": "20"
+ },
+ "t_int256": {
+ "label": "int256",
+ "numberOfBytes": "32"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "e1ac3fcb29bba83c7285d7f6dd1712aade9f91a31013b930c960a972d4bb01d4": {
+ "address": "0x5FfDE2871aC76fc033b3F775e280840a5600Cd16",
+ "txHash": "0x093401621115c5472968f287aa1b7056f7d10cd2fff8f031818cc3d58a4d8fa2",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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(IMidasAccessControl)13426",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:31",
+ "retypedFrom": "MidasAccessControl"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "1",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:36"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "51",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "Blacklistable",
+ "src": "contracts/access/Blacklistable.sol:17"
+ },
+ {
+ "label": "greenlistEnabled",
+ "offset": 0,
+ "slot": "101",
+ "type": "t_bool",
+ "contract": "Greenlistable",
+ "src": "contracts/access/Greenlistable.sol:17"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "102",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "Greenlistable",
+ "src": "contracts/access/Greenlistable.sol:22"
+ },
+ {
+ "label": "sanctionsList",
+ "offset": 0,
+ "slot": "152",
+ "type": "t_address",
+ "contract": "WithSanctionsList",
+ "src": "contracts/abstract/WithSanctionsList.sol:17"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "153",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithSanctionsList",
+ "src": "contracts/abstract/WithSanctionsList.sol:22"
+ },
+ {
+ "label": "tokensConfig",
+ "offset": 0,
+ "slot": "203",
+ "type": "t_mapping(t_address,t_struct(TokenConfig)12675_storage)",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:73"
+ },
+ {
+ "label": "isFreeFromMinAmount",
+ "offset": 0,
+ "slot": "204",
+ "type": "t_mapping(t_address,t_bool)",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:78"
+ },
+ {
+ "label": "waivedFeeRestriction",
+ "offset": 0,
+ "slot": "205",
+ "type": "t_mapping(t_address,t_bool)",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:83"
+ },
+ {
+ "label": "_paymentTokens",
+ "offset": 0,
+ "slot": "206",
+ "type": "t_struct(AddressSet)2278_storage",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:88"
+ },
+ {
+ "label": "_instantRateLimits",
+ "offset": 0,
+ "slot": "208",
+ "type": "t_struct(WindowRateLimits)15297_storage",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:93"
+ },
+ {
+ "label": "currentRequestId",
+ "offset": 0,
+ "slot": "211",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:98"
+ },
+ {
+ "label": "nextExpectedRequestIdToProcess",
+ "offset": 0,
+ "slot": "212",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:103"
+ },
+ {
+ "label": "maxApproveRequestId",
+ "offset": 0,
+ "slot": "213",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:108"
+ },
+ {
+ "label": "mToken",
+ "offset": 0,
+ "slot": "214",
+ "type": "t_contract(IMToken)12657",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:113"
+ },
+ {
+ "label": "mTokenDataFeed",
+ "offset": 0,
+ "slot": "215",
+ "type": "t_contract(IDataFeed)12284",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:118"
+ },
+ {
+ "label": "tokensReceiver",
+ "offset": 0,
+ "slot": "216",
+ "type": "t_address",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:123"
+ },
+ {
+ "label": "variationTolerance",
+ "offset": 0,
+ "slot": "217",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:128"
+ },
+ {
+ "label": "minAmount",
+ "offset": 0,
+ "slot": "218",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:133"
+ },
+ {
+ "label": "instantFee",
+ "offset": 0,
+ "slot": "219",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:138"
+ },
+ {
+ "label": "minInstantFee",
+ "offset": 0,
+ "slot": "220",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:143"
+ },
+ {
+ "label": "maxInstantFee",
+ "offset": 0,
+ "slot": "221",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:148"
+ },
+ {
+ "label": "maxInstantShare",
+ "offset": 0,
+ "slot": "222",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:153"
+ },
+ {
+ "label": "sequentialRequestProcessing",
+ "offset": 0,
+ "slot": "223",
+ "type": "t_bool",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:158"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "224",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:163"
+ },
+ {
+ "label": "mintRequests",
+ "offset": 0,
+ "slot": "274",
+ "type": "t_mapping(t_uint256,t_struct(Request)12317_storage)",
+ "contract": "DepositVault",
+ "src": "contracts/DepositVault.sol:44"
+ },
+ {
+ "label": "totalMinted",
+ "offset": 0,
+ "slot": "275",
+ "type": "t_mapping(t_address,t_uint256)",
+ "contract": "DepositVault",
+ "src": "contracts/DepositVault.sol:50"
+ },
+ {
+ "label": "minMTokenAmountForFirstDeposit",
+ "offset": 0,
+ "slot": "276",
+ "type": "t_uint256",
+ "contract": "DepositVault",
+ "src": "contracts/DepositVault.sol:55"
+ },
+ {
+ "label": "maxSupplyCap",
+ "offset": 0,
+ "slot": "277",
+ "type": "t_uint256",
+ "contract": "DepositVault",
+ "src": "contracts/DepositVault.sol:62"
+ },
+ {
+ "label": "maxAmountPerRequest",
+ "offset": 0,
+ "slot": "278",
+ "type": "t_uint256",
+ "contract": "DepositVault",
+ "src": "contracts/DepositVault.sol:67"
+ },
+ {
+ "label": "upcomingSupply",
+ "offset": 0,
+ "slot": "279",
+ "type": "t_uint256",
+ "contract": "DepositVault",
+ "src": "contracts/DepositVault.sol:73"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "280",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "DepositVault",
+ "src": "contracts/DepositVault.sol:78"
+ }
+ ],
+ "types": {
+ "t_address": {
+ "label": "address",
+ "numberOfBytes": "20"
+ },
+ "t_array(t_bytes32)dyn_storage": {
+ "label": "bytes32[]",
+ "numberOfBytes": "32"
+ },
+ "t_array(t_uint256)50_storage": {
+ "label": "uint256[50]",
+ "numberOfBytes": "1600"
+ },
+ "t_bool": {
+ "label": "bool",
+ "numberOfBytes": "1"
+ },
+ "t_bytes32": {
+ "label": "bytes32",
+ "numberOfBytes": "32"
+ },
+ "t_contract(IDataFeed)12284": {
+ "label": "contract IDataFeed",
+ "numberOfBytes": "20"
+ },
+ "t_contract(IMToken)12657": {
+ "label": "contract IMToken",
+ "numberOfBytes": "20"
+ },
+ "t_contract(IMidasAccessControl)13426": {
+ "label": "contract IMidasAccessControl",
+ "numberOfBytes": "20"
+ },
+ "t_enum(RequestStatus)12679": {
+ "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)12675_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_uint256,t_struct(Request)12317_storage)": {
+ "label": "mapping(uint256 => struct Request)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)15287_storage)": {
+ "label": "mapping(uint256 => struct RateLimitLibrary.WindowRateLimitConfig)",
+ "numberOfBytes": "32"
+ },
+ "t_struct(AddressSet)2278_storage": {
+ "label": "struct EnumerableSetUpgradeable.AddressSet",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)1963_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(Request)12317_storage": {
+ "label": "struct Request",
+ "members": [
+ {
+ "label": "recipient",
+ "type": "t_address",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "tokenIn",
+ "type": "t_address",
+ "offset": 0,
+ "slot": "1"
+ },
+ {
+ "label": "status",
+ "type": "t_enum(RequestStatus)12679",
+ "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"
+ },
+ {
+ "label": "depositedInstantUsdAmount",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "5"
+ },
+ {
+ "label": "approvedTokenOutRate",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "6"
+ },
+ {
+ "label": "amountMToken",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "7"
+ }
+ ],
+ "numberOfBytes": "256"
+ },
+ "t_struct(Set)1963_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)12675_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_struct(UintSet)2435_storage": {
+ "label": "struct EnumerableSetUpgradeable.UintSet",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)1963_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(WindowRateLimitConfig)15287_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimitConfig",
+ "members": [
+ {
+ "label": "limit",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "amountInFlight",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "1"
+ },
+ {
+ "label": "lastUpdated",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "2"
+ },
+ {
+ "label": "window",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "3"
+ }
+ ],
+ "numberOfBytes": "128"
+ },
+ "t_struct(WindowRateLimits)15297_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimits",
+ "members": [
+ {
+ "label": "windows",
+ "type": "t_struct(UintSet)2435_storage",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "configs",
+ "type": "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)15287_storage)",
+ "offset": 0,
+ "slot": "2"
+ }
+ ],
+ "numberOfBytes": "96"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "46731beb42684086b70c655a4821f81a82d510642b2bc658a03f4aaf684bfdbb": {
+ "address": "0xC3512A9bcdE459607C8599a34E3B834f9fCF235b",
+ "txHash": "0xc033934264b71662cfbaa821661b258cf27519ecd6e1e586371b958f1c09be75",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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(IMidasAccessControl)13426",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:31",
+ "retypedFrom": "MidasAccessControl"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "1",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:36"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "51",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "Blacklistable",
+ "src": "contracts/access/Blacklistable.sol:17"
+ },
+ {
+ "label": "greenlistEnabled",
+ "offset": 0,
+ "slot": "101",
+ "type": "t_bool",
+ "contract": "Greenlistable",
+ "src": "contracts/access/Greenlistable.sol:17"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "102",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "Greenlistable",
+ "src": "contracts/access/Greenlistable.sol:22"
+ },
+ {
+ "label": "sanctionsList",
+ "offset": 0,
+ "slot": "152",
+ "type": "t_address",
+ "contract": "WithSanctionsList",
+ "src": "contracts/abstract/WithSanctionsList.sol:17"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "153",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithSanctionsList",
+ "src": "contracts/abstract/WithSanctionsList.sol:22"
+ },
+ {
+ "label": "tokensConfig",
+ "offset": 0,
+ "slot": "203",
+ "type": "t_mapping(t_address,t_struct(TokenConfig)12675_storage)",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:73"
+ },
+ {
+ "label": "isFreeFromMinAmount",
+ "offset": 0,
+ "slot": "204",
+ "type": "t_mapping(t_address,t_bool)",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:78"
+ },
+ {
+ "label": "waivedFeeRestriction",
+ "offset": 0,
+ "slot": "205",
+ "type": "t_mapping(t_address,t_bool)",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:83"
+ },
+ {
+ "label": "_paymentTokens",
+ "offset": 0,
+ "slot": "206",
+ "type": "t_struct(AddressSet)2278_storage",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:88"
+ },
+ {
+ "label": "_instantRateLimits",
+ "offset": 0,
+ "slot": "208",
+ "type": "t_struct(WindowRateLimits)15297_storage",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:93"
+ },
+ {
+ "label": "currentRequestId",
+ "offset": 0,
+ "slot": "211",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:98"
+ },
+ {
+ "label": "nextExpectedRequestIdToProcess",
+ "offset": 0,
+ "slot": "212",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:103"
+ },
+ {
+ "label": "maxApproveRequestId",
+ "offset": 0,
+ "slot": "213",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:108"
+ },
+ {
+ "label": "mToken",
+ "offset": 0,
+ "slot": "214",
+ "type": "t_contract(IMToken)12657",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:113"
+ },
+ {
+ "label": "mTokenDataFeed",
+ "offset": 0,
+ "slot": "215",
+ "type": "t_contract(IDataFeed)12284",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:118"
+ },
+ {
+ "label": "tokensReceiver",
+ "offset": 0,
+ "slot": "216",
+ "type": "t_address",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:123"
+ },
+ {
+ "label": "variationTolerance",
+ "offset": 0,
+ "slot": "217",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:128"
+ },
+ {
+ "label": "minAmount",
+ "offset": 0,
+ "slot": "218",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:133"
+ },
+ {
+ "label": "instantFee",
+ "offset": 0,
+ "slot": "219",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:138"
+ },
+ {
+ "label": "minInstantFee",
+ "offset": 0,
+ "slot": "220",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:143"
+ },
+ {
+ "label": "maxInstantFee",
+ "offset": 0,
+ "slot": "221",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:148"
+ },
+ {
+ "label": "maxInstantShare",
+ "offset": 0,
+ "slot": "222",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:153"
+ },
+ {
+ "label": "sequentialRequestProcessing",
+ "offset": 0,
+ "slot": "223",
+ "type": "t_bool",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:158"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "224",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:163"
+ },
+ {
+ "label": "mintRequests",
+ "offset": 0,
+ "slot": "274",
+ "type": "t_mapping(t_uint256,t_struct(Request)12317_storage)",
+ "contract": "DepositVault",
+ "src": "contracts/DepositVault.sol:44"
+ },
+ {
+ "label": "totalMinted",
+ "offset": 0,
+ "slot": "275",
+ "type": "t_mapping(t_address,t_uint256)",
+ "contract": "DepositVault",
+ "src": "contracts/DepositVault.sol:50"
+ },
+ {
+ "label": "minMTokenAmountForFirstDeposit",
+ "offset": 0,
+ "slot": "276",
+ "type": "t_uint256",
+ "contract": "DepositVault",
+ "src": "contracts/DepositVault.sol:55"
+ },
+ {
+ "label": "maxSupplyCap",
+ "offset": 0,
+ "slot": "277",
+ "type": "t_uint256",
+ "contract": "DepositVault",
+ "src": "contracts/DepositVault.sol:62"
+ },
+ {
+ "label": "maxAmountPerRequest",
+ "offset": 0,
+ "slot": "278",
+ "type": "t_uint256",
+ "contract": "DepositVault",
+ "src": "contracts/DepositVault.sol:67"
+ },
+ {
+ "label": "upcomingSupply",
+ "offset": 0,
+ "slot": "279",
+ "type": "t_uint256",
+ "contract": "DepositVault",
+ "src": "contracts/DepositVault.sol:73"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "280",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "DepositVault",
+ "src": "contracts/DepositVault.sol:78"
+ }
+ ],
+ "types": {
+ "t_address": {
+ "label": "address",
+ "numberOfBytes": "20"
+ },
+ "t_array(t_bytes32)dyn_storage": {
+ "label": "bytes32[]",
+ "numberOfBytes": "32"
+ },
+ "t_array(t_uint256)50_storage": {
+ "label": "uint256[50]",
+ "numberOfBytes": "1600"
+ },
+ "t_bool": {
+ "label": "bool",
+ "numberOfBytes": "1"
+ },
+ "t_bytes32": {
+ "label": "bytes32",
+ "numberOfBytes": "32"
+ },
+ "t_contract(IDataFeed)12284": {
+ "label": "contract IDataFeed",
+ "numberOfBytes": "20"
+ },
+ "t_contract(IMToken)12657": {
+ "label": "contract IMToken",
+ "numberOfBytes": "20"
+ },
+ "t_contract(IMidasAccessControl)13426": {
+ "label": "contract IMidasAccessControl",
+ "numberOfBytes": "20"
+ },
+ "t_enum(RequestStatus)12679": {
+ "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)12675_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_uint256,t_struct(Request)12317_storage)": {
+ "label": "mapping(uint256 => struct Request)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)15287_storage)": {
+ "label": "mapping(uint256 => struct RateLimitLibrary.WindowRateLimitConfig)",
+ "numberOfBytes": "32"
+ },
+ "t_struct(AddressSet)2278_storage": {
+ "label": "struct EnumerableSetUpgradeable.AddressSet",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)1963_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(Request)12317_storage": {
+ "label": "struct Request",
+ "members": [
+ {
+ "label": "recipient",
+ "type": "t_address",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "tokenIn",
+ "type": "t_address",
+ "offset": 0,
+ "slot": "1"
+ },
+ {
+ "label": "status",
+ "type": "t_enum(RequestStatus)12679",
+ "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"
+ },
+ {
+ "label": "depositedInstantUsdAmount",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "5"
+ },
+ {
+ "label": "approvedTokenOutRate",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "6"
+ },
+ {
+ "label": "amountMToken",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "7"
+ }
+ ],
+ "numberOfBytes": "256"
+ },
+ "t_struct(Set)1963_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)12675_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_struct(UintSet)2435_storage": {
+ "label": "struct EnumerableSetUpgradeable.UintSet",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)1963_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(WindowRateLimitConfig)15287_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimitConfig",
+ "members": [
+ {
+ "label": "limit",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "amountInFlight",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "1"
+ },
+ {
+ "label": "lastUpdated",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "2"
+ },
+ {
+ "label": "window",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "3"
+ }
+ ],
+ "numberOfBytes": "128"
+ },
+ "t_struct(WindowRateLimits)15297_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimits",
+ "members": [
+ {
+ "label": "windows",
+ "type": "t_struct(UintSet)2435_storage",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "configs",
+ "type": "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)15287_storage)",
+ "offset": 0,
+ "slot": "2"
+ }
+ ],
+ "numberOfBytes": "96"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "7c63d5b9dc0375dc28bad37ede49a85021fb80a7a13b0cdd8b3ad72be912cc5a": {
+ "address": "0xF17851FdF529Ab55864bD513EE00607D894868FF",
+ "txHash": "0xcff5131ced5d93ba06cd59b90487cf1f92cbfbc21a50b1348fb470bf2600ba48",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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(IMidasAccessControl)13426",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:31",
+ "retypedFrom": "MidasAccessControl"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "1",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:36"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "51",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "Blacklistable",
+ "src": "contracts/access/Blacklistable.sol:17"
+ },
+ {
+ "label": "greenlistEnabled",
+ "offset": 0,
+ "slot": "101",
+ "type": "t_bool",
+ "contract": "Greenlistable",
+ "src": "contracts/access/Greenlistable.sol:17"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "102",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "Greenlistable",
+ "src": "contracts/access/Greenlistable.sol:22"
+ },
+ {
+ "label": "sanctionsList",
+ "offset": 0,
+ "slot": "152",
+ "type": "t_address",
+ "contract": "WithSanctionsList",
+ "src": "contracts/abstract/WithSanctionsList.sol:17"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "153",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithSanctionsList",
+ "src": "contracts/abstract/WithSanctionsList.sol:22"
+ },
+ {
+ "label": "tokensConfig",
+ "offset": 0,
+ "slot": "203",
+ "type": "t_mapping(t_address,t_struct(TokenConfig)12675_storage)",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:73"
+ },
+ {
+ "label": "isFreeFromMinAmount",
+ "offset": 0,
+ "slot": "204",
+ "type": "t_mapping(t_address,t_bool)",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:78"
+ },
+ {
+ "label": "waivedFeeRestriction",
+ "offset": 0,
+ "slot": "205",
+ "type": "t_mapping(t_address,t_bool)",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:83"
+ },
+ {
+ "label": "_paymentTokens",
+ "offset": 0,
+ "slot": "206",
+ "type": "t_struct(AddressSet)2278_storage",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:88"
+ },
+ {
+ "label": "_instantRateLimits",
+ "offset": 0,
+ "slot": "208",
+ "type": "t_struct(WindowRateLimits)15297_storage",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:93"
+ },
+ {
+ "label": "currentRequestId",
+ "offset": 0,
+ "slot": "211",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:98"
+ },
+ {
+ "label": "nextExpectedRequestIdToProcess",
+ "offset": 0,
+ "slot": "212",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:103"
+ },
+ {
+ "label": "maxApproveRequestId",
+ "offset": 0,
+ "slot": "213",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:108"
+ },
+ {
+ "label": "mToken",
+ "offset": 0,
+ "slot": "214",
+ "type": "t_contract(IMToken)12657",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:113"
+ },
+ {
+ "label": "mTokenDataFeed",
+ "offset": 0,
+ "slot": "215",
+ "type": "t_contract(IDataFeed)12284",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:118"
+ },
+ {
+ "label": "tokensReceiver",
+ "offset": 0,
+ "slot": "216",
+ "type": "t_address",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:123"
+ },
+ {
+ "label": "variationTolerance",
+ "offset": 0,
+ "slot": "217",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:128"
+ },
+ {
+ "label": "minAmount",
+ "offset": 0,
+ "slot": "218",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:133"
+ },
+ {
+ "label": "instantFee",
+ "offset": 0,
+ "slot": "219",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:138"
+ },
+ {
+ "label": "minInstantFee",
+ "offset": 0,
+ "slot": "220",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:143"
+ },
+ {
+ "label": "maxInstantFee",
+ "offset": 0,
+ "slot": "221",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:148"
+ },
+ {
+ "label": "maxInstantShare",
+ "offset": 0,
+ "slot": "222",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:153"
+ },
+ {
+ "label": "sequentialRequestProcessing",
+ "offset": 0,
+ "slot": "223",
+ "type": "t_bool",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:158"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "224",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:163"
+ },
+ {
+ "label": "redeemRequests",
+ "offset": 0,
+ "slot": "274",
+ "type": "t_mapping(t_uint256,t_struct(Request)14010_storage)",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:44"
+ },
+ {
+ "label": "loanRequests",
+ "offset": 0,
+ "slot": "275",
+ "type": "t_mapping(t_uint256,t_struct(LiquidityProviderLoanRequest)14045_storage)",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:49"
+ },
+ {
+ "label": "requestRedeemer",
+ "offset": 0,
+ "slot": "276",
+ "type": "t_address",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:54"
+ },
+ {
+ "label": "loanLp",
+ "offset": 0,
+ "slot": "277",
+ "type": "t_address",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:59"
+ },
+ {
+ "label": "loanRepaymentAddress",
+ "offset": 0,
+ "slot": "278",
+ "type": "t_address",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:64"
+ },
+ {
+ "label": "loanApr",
+ "offset": 0,
+ "slot": "279",
+ "type": "t_uint256",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:69"
+ },
+ {
+ "label": "preferLoanLiquidity",
+ "offset": 0,
+ "slot": "280",
+ "type": "t_bool",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:74"
+ },
+ {
+ "label": "currentLoanRequestId",
+ "offset": 0,
+ "slot": "281",
+ "type": "t_uint256",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:79"
+ },
+ {
+ "label": "loanSwapperVault",
+ "offset": 0,
+ "slot": "282",
+ "type": "t_contract(IRedemptionVault)14332",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:84"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "283",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:89"
+ }
+ ],
+ "types": {
+ "t_address": {
+ "label": "address",
+ "numberOfBytes": "20"
+ },
+ "t_array(t_bytes32)dyn_storage": {
+ "label": "bytes32[]",
+ "numberOfBytes": "32"
+ },
+ "t_array(t_uint256)50_storage": {
+ "label": "uint256[50]",
+ "numberOfBytes": "1600"
+ },
+ "t_bool": {
+ "label": "bool",
+ "numberOfBytes": "1"
+ },
+ "t_bytes32": {
+ "label": "bytes32",
+ "numberOfBytes": "32"
+ },
+ "t_contract(IDataFeed)12284": {
+ "label": "contract IDataFeed",
+ "numberOfBytes": "20"
+ },
+ "t_contract(IMToken)12657": {
+ "label": "contract IMToken",
+ "numberOfBytes": "20"
+ },
+ "t_contract(IMidasAccessControl)13426": {
+ "label": "contract IMidasAccessControl",
+ "numberOfBytes": "20"
+ },
+ "t_contract(IRedemptionVault)14332": {
+ "label": "contract IRedemptionVault",
+ "numberOfBytes": "20"
+ },
+ "t_enum(RequestStatus)12679": {
+ "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)12675_storage)": {
+ "label": "mapping(address => struct TokenConfig)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_uint256)": {
+ "label": "mapping(bytes32 => uint256)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint256,t_struct(LiquidityProviderLoanRequest)14045_storage)": {
+ "label": "mapping(uint256 => struct LiquidityProviderLoanRequest)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint256,t_struct(Request)14010_storage)": {
+ "label": "mapping(uint256 => struct Request)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)15287_storage)": {
+ "label": "mapping(uint256 => struct RateLimitLibrary.WindowRateLimitConfig)",
+ "numberOfBytes": "32"
+ },
+ "t_struct(AddressSet)2278_storage": {
+ "label": "struct EnumerableSetUpgradeable.AddressSet",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)1963_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(LiquidityProviderLoanRequest)14045_storage": {
+ "label": "struct LiquidityProviderLoanRequest",
+ "members": [
+ {
+ "label": "tokenOut",
+ "type": "t_address",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "amountTokenOut",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "1"
+ },
+ {
+ "label": "amountFee",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "2"
+ },
+ {
+ "label": "createdAt",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "3"
+ },
+ {
+ "label": "status",
+ "type": "t_enum(RequestStatus)12679",
+ "offset": 0,
+ "slot": "4"
+ }
+ ],
+ "numberOfBytes": "160"
+ },
+ "t_struct(Request)14010_storage": {
+ "label": "struct Request",
+ "members": [
+ {
+ "label": "recipient",
+ "type": "t_address",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "tokenOut",
+ "type": "t_address",
+ "offset": 0,
+ "slot": "1"
+ },
+ {
+ "label": "status",
+ "type": "t_enum(RequestStatus)12679",
+ "offset": 20,
+ "slot": "1"
+ },
+ {
+ "label": "feePercent",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "2"
+ },
+ {
+ "label": "amountMToken",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "3"
+ },
+ {
+ "label": "mTokenRate",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "4"
+ },
+ {
+ "label": "tokenOutRate",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "5"
+ },
+ {
+ "label": "amountMTokenInstant",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "6"
+ },
+ {
+ "label": "approvedMTokenRate",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "7"
+ },
+ {
+ "label": "amountTokenOut",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "8"
+ }
+ ],
+ "numberOfBytes": "288"
+ },
+ "t_struct(Set)1963_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)12675_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_struct(UintSet)2435_storage": {
+ "label": "struct EnumerableSetUpgradeable.UintSet",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)1963_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(WindowRateLimitConfig)15287_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimitConfig",
+ "members": [
+ {
+ "label": "limit",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "amountInFlight",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "1"
+ },
+ {
+ "label": "lastUpdated",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "2"
+ },
+ {
+ "label": "window",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "3"
+ }
+ ],
+ "numberOfBytes": "128"
+ },
+ "t_struct(WindowRateLimits)15297_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimits",
+ "members": [
+ {
+ "label": "windows",
+ "type": "t_struct(UintSet)2435_storage",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "configs",
+ "type": "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)15287_storage)",
+ "offset": 0,
+ "slot": "2"
+ }
+ ],
+ "numberOfBytes": "96"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "55f2a882774d1b505010be526407522659ffdfc4881e7816fd925fb298250dcf": {
+ "address": "0x26d6D401982B8d60004B313eE9381A98d4ce9E76",
+ "txHash": "0x4c0c0380eda2cfd4d71e541f1ab561e03e52b01a41faf22ffcbade37b5d74f82",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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(IMidasAccessControl)13426",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:31",
+ "retypedFrom": "MidasAccessControl"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "1",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:36"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "51",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "Blacklistable",
+ "src": "contracts/access/Blacklistable.sol:17"
+ },
+ {
+ "label": "greenlistEnabled",
+ "offset": 0,
+ "slot": "101",
+ "type": "t_bool",
+ "contract": "Greenlistable",
+ "src": "contracts/access/Greenlistable.sol:17"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "102",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "Greenlistable",
+ "src": "contracts/access/Greenlistable.sol:22"
+ },
+ {
+ "label": "sanctionsList",
+ "offset": 0,
+ "slot": "152",
+ "type": "t_address",
+ "contract": "WithSanctionsList",
+ "src": "contracts/abstract/WithSanctionsList.sol:17"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "153",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithSanctionsList",
+ "src": "contracts/abstract/WithSanctionsList.sol:22"
+ },
+ {
+ "label": "tokensConfig",
+ "offset": 0,
+ "slot": "203",
+ "type": "t_mapping(t_address,t_struct(TokenConfig)12675_storage)",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:73"
+ },
+ {
+ "label": "isFreeFromMinAmount",
+ "offset": 0,
+ "slot": "204",
+ "type": "t_mapping(t_address,t_bool)",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:78"
+ },
+ {
+ "label": "waivedFeeRestriction",
+ "offset": 0,
+ "slot": "205",
+ "type": "t_mapping(t_address,t_bool)",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:83"
+ },
+ {
+ "label": "_paymentTokens",
+ "offset": 0,
+ "slot": "206",
+ "type": "t_struct(AddressSet)2278_storage",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:88"
+ },
+ {
+ "label": "_instantRateLimits",
+ "offset": 0,
+ "slot": "208",
+ "type": "t_struct(WindowRateLimits)15297_storage",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:93"
+ },
+ {
+ "label": "currentRequestId",
+ "offset": 0,
+ "slot": "211",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:98"
+ },
+ {
+ "label": "nextExpectedRequestIdToProcess",
+ "offset": 0,
+ "slot": "212",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:103"
+ },
+ {
+ "label": "maxApproveRequestId",
+ "offset": 0,
+ "slot": "213",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:108"
+ },
+ {
+ "label": "mToken",
+ "offset": 0,
+ "slot": "214",
+ "type": "t_contract(IMToken)12657",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:113"
+ },
+ {
+ "label": "mTokenDataFeed",
+ "offset": 0,
+ "slot": "215",
+ "type": "t_contract(IDataFeed)12284",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:118"
+ },
+ {
+ "label": "tokensReceiver",
+ "offset": 0,
+ "slot": "216",
+ "type": "t_address",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:123"
+ },
+ {
+ "label": "variationTolerance",
+ "offset": 0,
+ "slot": "217",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:128"
+ },
+ {
+ "label": "minAmount",
+ "offset": 0,
+ "slot": "218",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:133"
+ },
+ {
+ "label": "instantFee",
+ "offset": 0,
+ "slot": "219",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:138"
+ },
+ {
+ "label": "minInstantFee",
+ "offset": 0,
+ "slot": "220",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:143"
+ },
+ {
+ "label": "maxInstantFee",
+ "offset": 0,
+ "slot": "221",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:148"
+ },
+ {
+ "label": "maxInstantShare",
+ "offset": 0,
+ "slot": "222",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:153"
+ },
+ {
+ "label": "sequentialRequestProcessing",
+ "offset": 0,
+ "slot": "223",
+ "type": "t_bool",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:158"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "224",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:163"
+ },
+ {
+ "label": "redeemRequests",
+ "offset": 0,
+ "slot": "274",
+ "type": "t_mapping(t_uint256,t_struct(Request)14010_storage)",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:44"
+ },
+ {
+ "label": "loanRequests",
+ "offset": 0,
+ "slot": "275",
+ "type": "t_mapping(t_uint256,t_struct(LiquidityProviderLoanRequest)14045_storage)",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:49"
+ },
+ {
+ "label": "requestRedeemer",
+ "offset": 0,
+ "slot": "276",
+ "type": "t_address",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:54"
+ },
+ {
+ "label": "loanLp",
+ "offset": 0,
+ "slot": "277",
+ "type": "t_address",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:59"
+ },
+ {
+ "label": "loanRepaymentAddress",
+ "offset": 0,
+ "slot": "278",
+ "type": "t_address",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:64"
+ },
+ {
+ "label": "loanApr",
+ "offset": 0,
+ "slot": "279",
+ "type": "t_uint256",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:69"
+ },
+ {
+ "label": "preferLoanLiquidity",
+ "offset": 0,
+ "slot": "280",
+ "type": "t_bool",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:74"
+ },
+ {
+ "label": "currentLoanRequestId",
+ "offset": 0,
+ "slot": "281",
+ "type": "t_uint256",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:79"
+ },
+ {
+ "label": "loanSwapperVault",
+ "offset": 0,
+ "slot": "282",
+ "type": "t_contract(IRedemptionVault)14332",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:84"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "283",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:89"
+ }
+ ],
+ "types": {
+ "t_address": {
+ "label": "address",
+ "numberOfBytes": "20"
+ },
+ "t_array(t_bytes32)dyn_storage": {
+ "label": "bytes32[]",
+ "numberOfBytes": "32"
+ },
+ "t_array(t_uint256)50_storage": {
+ "label": "uint256[50]",
+ "numberOfBytes": "1600"
+ },
+ "t_bool": {
+ "label": "bool",
+ "numberOfBytes": "1"
+ },
+ "t_bytes32": {
+ "label": "bytes32",
+ "numberOfBytes": "32"
+ },
+ "t_contract(IDataFeed)12284": {
+ "label": "contract IDataFeed",
+ "numberOfBytes": "20"
+ },
+ "t_contract(IMToken)12657": {
+ "label": "contract IMToken",
+ "numberOfBytes": "20"
+ },
+ "t_contract(IMidasAccessControl)13426": {
+ "label": "contract IMidasAccessControl",
+ "numberOfBytes": "20"
+ },
+ "t_contract(IRedemptionVault)14332": {
+ "label": "contract IRedemptionVault",
+ "numberOfBytes": "20"
+ },
+ "t_enum(RequestStatus)12679": {
+ "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)12675_storage)": {
+ "label": "mapping(address => struct TokenConfig)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_uint256)": {
+ "label": "mapping(bytes32 => uint256)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint256,t_struct(LiquidityProviderLoanRequest)14045_storage)": {
+ "label": "mapping(uint256 => struct LiquidityProviderLoanRequest)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint256,t_struct(Request)14010_storage)": {
+ "label": "mapping(uint256 => struct Request)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)15287_storage)": {
+ "label": "mapping(uint256 => struct RateLimitLibrary.WindowRateLimitConfig)",
+ "numberOfBytes": "32"
+ },
+ "t_struct(AddressSet)2278_storage": {
+ "label": "struct EnumerableSetUpgradeable.AddressSet",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)1963_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(LiquidityProviderLoanRequest)14045_storage": {
+ "label": "struct LiquidityProviderLoanRequest",
+ "members": [
+ {
+ "label": "tokenOut",
+ "type": "t_address",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "amountTokenOut",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "1"
+ },
+ {
+ "label": "amountFee",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "2"
+ },
+ {
+ "label": "createdAt",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "3"
+ },
+ {
+ "label": "status",
+ "type": "t_enum(RequestStatus)12679",
+ "offset": 0,
+ "slot": "4"
+ }
+ ],
+ "numberOfBytes": "160"
+ },
+ "t_struct(Request)14010_storage": {
+ "label": "struct Request",
+ "members": [
+ {
+ "label": "recipient",
+ "type": "t_address",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "tokenOut",
+ "type": "t_address",
+ "offset": 0,
+ "slot": "1"
+ },
+ {
+ "label": "status",
+ "type": "t_enum(RequestStatus)12679",
+ "offset": 20,
+ "slot": "1"
+ },
+ {
+ "label": "feePercent",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "2"
+ },
+ {
+ "label": "amountMToken",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "3"
+ },
+ {
+ "label": "mTokenRate",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "4"
+ },
+ {
+ "label": "tokenOutRate",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "5"
+ },
+ {
+ "label": "amountMTokenInstant",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "6"
+ },
+ {
+ "label": "approvedMTokenRate",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "7"
+ },
+ {
+ "label": "amountTokenOut",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "8"
+ }
+ ],
+ "numberOfBytes": "288"
+ },
+ "t_struct(Set)1963_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)12675_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_struct(UintSet)2435_storage": {
+ "label": "struct EnumerableSetUpgradeable.UintSet",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)1963_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(WindowRateLimitConfig)15287_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimitConfig",
+ "members": [
+ {
+ "label": "limit",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "amountInFlight",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "1"
+ },
+ {
+ "label": "lastUpdated",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "2"
+ },
+ {
+ "label": "window",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "3"
+ }
+ ],
+ "numberOfBytes": "128"
+ },
+ "t_struct(WindowRateLimits)15297_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimits",
+ "members": [
+ {
+ "label": "windows",
+ "type": "t_struct(UintSet)2435_storage",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "configs",
+ "type": "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)15287_storage)",
+ "offset": 0,
+ "slot": "2"
+ }
+ ],
+ "numberOfBytes": "96"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "f879537ca17d83db8cdf95ab8f8e98f5476d47aa4308e955af8d9a7f58612397": {
+ "address": "0x0fB85F6Ce3BfBcf0B6eeA3d8edeDDe79d672EEB6",
+ "txHash": "0x5123db951769a70cb9625cd7e5215cc67901574738871c47e227d9679dc0c89b",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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(IMidasAccessControl)37005",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:31",
+ "retypedFrom": "MidasAccessControl"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "1",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:36"
+ },
+ {
+ "label": "aggregator",
+ "offset": 0,
+ "slot": "51",
+ "type": "t_contract(AggregatorV3Interface)1801",
+ "contract": "DataFeed",
+ "src": "contracts/feeds/DataFeed.sol:31"
+ },
+ {
+ "label": "healthyDiff",
+ "offset": 0,
+ "slot": "52",
+ "type": "t_uint256",
+ "contract": "DataFeed",
+ "src": "contracts/feeds/DataFeed.sol:36"
+ },
+ {
+ "label": "minExpectedAnswer",
+ "offset": 0,
+ "slot": "53",
+ "type": "t_int256",
+ "contract": "DataFeed",
+ "src": "contracts/feeds/DataFeed.sol:41"
+ },
+ {
+ "label": "maxExpectedAnswer",
+ "offset": 0,
+ "slot": "54",
+ "type": "t_int256",
+ "contract": "DataFeed",
+ "src": "contracts/feeds/DataFeed.sol:46"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "55",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "DataFeed",
+ "src": "contracts/feeds/DataFeed.sol:51"
+ },
+ {
+ "label": "___gap",
+ "offset": 0,
+ "slot": "105",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "DataFeed",
+ "src": "contracts/feeds/DataFeed.sol:56"
+ }
+ ],
+ "types": {
+ "t_array(t_uint256)50_storage": {
+ "label": "uint256[50]",
+ "numberOfBytes": "1600"
+ },
+ "t_bool": {
+ "label": "bool",
+ "numberOfBytes": "1"
+ },
+ "t_contract(AggregatorV3Interface)1801": {
+ "label": "contract AggregatorV3Interface",
+ "numberOfBytes": "20"
+ },
+ "t_contract(IMidasAccessControl)37005": {
+ "label": "contract IMidasAccessControl",
+ "numberOfBytes": "20"
+ },
+ "t_int256": {
+ "label": "int256",
+ "numberOfBytes": "32"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "66b47cbe6d29772f8f04362972f20aaa003fae06fcb09071059795b4f94cb61f": {
+ "address": "0xEcD9cCBC0eaC2377ea44606487E4235Cb256f658",
+ "txHash": "0x78c6778f00719c3db56b749120c9127f23d7758981acda838a93ed09d727e22a",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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(IMidasAccessControl)22015",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:29",
+ "retypedFrom": "MidasAccessControl"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "1",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:34"
+ },
+ {
+ "label": "_status",
+ "offset": 0,
+ "slot": "51",
+ "type": "t_uint256",
+ "contract": "ReentrancyGuardUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:38"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "52",
+ "type": "t_array(t_uint256)49_storage",
+ "contract": "ReentrancyGuardUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:88"
+ },
+ {
+ "label": "dataHashIndexes",
+ "offset": 0,
+ "slot": "101",
+ "type": "t_mapping(t_bytes32,t_uint256)",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:84"
+ },
+ {
+ "label": "proposerPendingOperationsCount",
+ "offset": 0,
+ "slot": "102",
+ "type": "t_mapping(t_address,t_uint256)",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:89"
+ },
+ {
+ "label": "_securityCouncils",
+ "offset": 0,
+ "slot": "103",
+ "type": "t_mapping(t_uint256,t_struct(AddressSet)5013_storage)",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:94"
+ },
+ {
+ "label": "_operationDetails",
+ "offset": 0,
+ "slot": "104",
+ "type": "t_mapping(t_bytes32,t_struct(TimelockOperationDetails)16856_storage)",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:99"
+ },
+ {
+ "label": "_pendingOperations",
+ "offset": 0,
+ "slot": "105",
+ "type": "t_struct(Bytes32Set)4892_storage",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:104"
+ },
+ {
+ "label": "timelock",
+ "offset": 0,
+ "slot": "107",
+ "type": "t_address",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:109"
+ },
+ {
+ "label": "maxPendingOperationsPerProposer",
+ "offset": 0,
+ "slot": "108",
+ "type": "t_uint256",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:114"
+ },
+ {
+ "label": "securityCouncilVersion",
+ "offset": 0,
+ "slot": "109",
+ "type": "t_uint256",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:119"
+ },
+ {
+ "label": "pendingSetCouncilOperationId",
+ "offset": 0,
+ "slot": "110",
+ "type": "t_bytes32",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:124"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "111",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:129"
+ }
+ ],
+ "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_contract(IMidasAccessControl)22015": {
+ "label": "contract IMidasAccessControl",
+ "numberOfBytes": "20"
+ },
+ "t_enum(TimelockOperationStatus)22177": {
+ "label": "enum TimelockOperationStatus",
+ "members": [
+ "NotExist",
+ "Pending",
+ "Paused",
+ "ApprovedExecution",
+ "ReadyToExecute",
+ "ReadyToAbort",
+ "Expired",
+ "Aborted",
+ "Executed"
+ ],
+ "numberOfBytes": "1"
+ },
+ "t_mapping(t_address,t_uint256)": {
+ "label": "mapping(address => uint256)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_struct(TimelockOperationDetails)16856_storage)": {
+ "label": "mapping(bytes32 => struct MidasTimelockManager.TimelockOperationDetails)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_uint256)": {
+ "label": "mapping(bytes32 => uint256)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint256,t_struct(AddressSet)5013_storage)": {
+ "label": "mapping(uint256 => struct EnumerableSetUpgradeable.AddressSet)",
+ "numberOfBytes": "32"
+ },
+ "t_struct(AddressSet)5013_storage": {
+ "label": "struct EnumerableSetUpgradeable.AddressSet",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)4698_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(Bytes32Set)4892_storage": {
+ "label": "struct EnumerableSetUpgradeable.Bytes32Set",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)4698_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(Set)4698_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(TimelockOperationDetails)16856_storage": {
+ "label": "struct MidasTimelockManager.TimelockOperationDetails",
+ "members": [
+ {
+ "label": "votersForExecution",
+ "type": "t_struct(AddressSet)5013_storage",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "votersForVeto",
+ "type": "t_struct(AddressSet)5013_storage",
+ "offset": 0,
+ "slot": "2"
+ },
+ {
+ "label": "councilVersion",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "4"
+ },
+ {
+ "label": "dataHash",
+ "type": "t_bytes32",
+ "offset": 0,
+ "slot": "5"
+ },
+ {
+ "label": "status",
+ "type": "t_enum(TimelockOperationStatus)22177",
+ "offset": 0,
+ "slot": "6"
+ },
+ {
+ "label": "pauseReasonCode",
+ "type": "t_uint8",
+ "offset": 1,
+ "slot": "6"
+ },
+ {
+ "label": "isSetCouncilOperation",
+ "type": "t_bool",
+ "offset": 2,
+ "slot": "6"
+ },
+ {
+ "label": "createdAt",
+ "type": "t_uint32",
+ "offset": 3,
+ "slot": "6"
+ },
+ {
+ "label": "executionApprovedAt",
+ "type": "t_uint32",
+ "offset": 7,
+ "slot": "6"
+ },
+ {
+ "label": "operationProposer",
+ "type": "t_address",
+ "offset": 11,
+ "slot": "6"
+ },
+ {
+ "label": "pauser",
+ "type": "t_address",
+ "offset": 0,
+ "slot": "7"
+ }
+ ],
+ "numberOfBytes": "256"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint32": {
+ "label": "uint32",
+ "numberOfBytes": "4"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "dd58e5cb4bfe6ab8fe1683a1ab110d099190acb166ae597a147b812398a642e0": {
+ "address": "0xc9d7Ee788f941905C86AB055C8366615af866837",
+ "txHash": "0x9ecc59e5995abaed70345e446e02dd082b61afdfb03372b118603acf37859f7b",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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(IMidasAccessControl)22015",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:29",
+ "retypedFrom": "MidasAccessControl"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "1",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:34"
+ },
+ {
+ "label": "contractPaused",
+ "offset": 0,
+ "slot": "51",
+ "type": "t_mapping(t_address,t_bool)",
+ "contract": "MidasPauseManager",
+ "src": "contracts/access/MidasPauseManager.sol:31"
+ },
+ {
+ "label": "contractFnPaused",
+ "offset": 0,
+ "slot": "52",
+ "type": "t_mapping(t_address,t_mapping(t_bytes4,t_bool))",
+ "contract": "MidasPauseManager",
+ "src": "contracts/access/MidasPauseManager.sol:36"
+ },
+ {
+ "label": "pauseDelay",
+ "offset": 0,
+ "slot": "53",
+ "type": "t_uint32",
+ "contract": "MidasPauseManager",
+ "src": "contracts/access/MidasPauseManager.sol:41"
+ },
+ {
+ "label": "unpauseDelay",
+ "offset": 4,
+ "slot": "53",
+ "type": "t_uint32",
+ "contract": "MidasPauseManager",
+ "src": "contracts/access/MidasPauseManager.sol:46"
+ },
+ {
+ "label": "globalPaused",
+ "offset": 8,
+ "slot": "53",
+ "type": "t_bool",
+ "contract": "MidasPauseManager",
+ "src": "contracts/access/MidasPauseManager.sol:51"
+ }
+ ],
+ "types": {
+ "t_address": {
+ "label": "address",
+ "numberOfBytes": "20"
+ },
+ "t_array(t_uint256)50_storage": {
+ "label": "uint256[50]",
+ "numberOfBytes": "1600"
+ },
+ "t_bool": {
+ "label": "bool",
+ "numberOfBytes": "1"
+ },
+ "t_bytes4": {
+ "label": "bytes4",
+ "numberOfBytes": "4"
+ },
+ "t_contract(IMidasAccessControl)22015": {
+ "label": "contract IMidasAccessControl",
+ "numberOfBytes": "20"
+ },
+ "t_mapping(t_address,t_bool)": {
+ "label": "mapping(address => bool)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_address,t_mapping(t_bytes4,t_bool))": {
+ "label": "mapping(address => mapping(bytes4 => bool))",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes4,t_bool)": {
+ "label": "mapping(bytes4 => bool)",
+ "numberOfBytes": "32"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint32": {
+ "label": "uint32",
+ "numberOfBytes": "4"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "1433e2d788a6ee0c4a05fb51b733f4d6036a37ba376ab55e5cd1fe9a8390445c": {
+ "address": "0xDeAB4a23e6dC1CfF64ad6CD769E2683F0a37C584",
+ "txHash": "0x3560571b16fea02e1a8854e2d124640aae59647173311b69245fe2b23ddba8eb",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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(IMidasAccessControl)37060",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:29",
+ "retypedFrom": "MidasAccessControl"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "202",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:34"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "252",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "Blacklistable",
+ "src": "contracts/access/Blacklistable.sol:17"
+ },
+ {
+ "label": "metadata",
+ "offset": 0,
+ "slot": "302",
+ "type": "t_mapping(t_bytes32,t_bytes_storage)",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:49"
+ },
+ {
+ "label": "_mintRateLimits",
+ "offset": 0,
+ "slot": "303",
+ "type": "t_struct(WindowRateLimits)39129_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:54"
+ },
+ {
+ "label": "clawbackReceiver",
+ "offset": 0,
+ "slot": "306",
+ "type": "t_address",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:59"
+ },
+ {
+ "label": "_inClawback",
+ "offset": 20,
+ "slot": "306",
+ "type": "t_bool",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:64"
+ },
+ {
+ "label": "_name",
+ "offset": 0,
+ "slot": "307",
+ "type": "t_string_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:69"
+ },
+ {
+ "label": "_symbol",
+ "offset": 0,
+ "slot": "308",
+ "type": "t_string_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:74"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "309",
+ "type": "t_array(t_uint256)44_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:79"
+ },
+ {
+ "label": "___gap",
+ "offset": 0,
+ "slot": "353",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:84"
+ }
+ ],
+ "types": {
+ "t_address": {
+ "label": "address",
+ "numberOfBytes": "20"
+ },
+ "t_array(t_bytes32)dyn_storage": {
+ "label": "bytes32[]",
+ "numberOfBytes": "32"
+ },
+ "t_array(t_uint256)44_storage": {
+ "label": "uint256[44]",
+ "numberOfBytes": "1408"
+ },
+ "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(IMidasAccessControl)37060": {
+ "label": "contract IMidasAccessControl",
+ "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_mapping(t_bytes32,t_uint256)": {
+ "label": "mapping(bytes32 => uint256)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)39119_storage)": {
+ "label": "mapping(uint256 => struct RateLimitLibrary.WindowRateLimitConfig)",
+ "numberOfBytes": "32"
+ },
+ "t_string_storage": {
+ "label": "string",
+ "numberOfBytes": "32"
+ },
+ "t_struct(Set)14575_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(UintSet)15047_storage": {
+ "label": "struct EnumerableSetUpgradeable.UintSet",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)14575_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(WindowRateLimitConfig)39119_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimitConfig",
+ "members": [
+ {
+ "label": "limit",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "amountInFlight",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "1"
+ },
+ {
+ "label": "lastUpdated",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "2"
+ },
+ {
+ "label": "window",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "3"
+ }
+ ],
+ "numberOfBytes": "128"
+ },
+ "t_struct(WindowRateLimits)39129_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimits",
+ "members": [
+ {
+ "label": "windows",
+ "type": "t_struct(UintSet)15047_storage",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "configs",
+ "type": "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)39119_storage)",
+ "offset": 0,
+ "slot": "2"
+ }
+ ],
+ "numberOfBytes": "96"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "e72a3c989e2ac042e9cfc80fe6daffc264768e4ed7d0a8be7ee31b8bebcfe942": {
+ "address": "0x69237996971F483B2228aF23738B78EBEf4e7Da4",
+ "txHash": "0x72c5219b909742c9e066bce78681e4ff919a95e85c8379f4d0b8e789e894fb7e",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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(IMidasAccessControl)37060",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:29",
+ "retypedFrom": "MidasAccessControl"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "202",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:34"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "252",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "Blacklistable",
+ "src": "contracts/access/Blacklistable.sol:17"
+ },
+ {
+ "label": "metadata",
+ "offset": 0,
+ "slot": "302",
+ "type": "t_mapping(t_bytes32,t_bytes_storage)",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:49"
+ },
+ {
+ "label": "_mintRateLimits",
+ "offset": 0,
+ "slot": "303",
+ "type": "t_struct(WindowRateLimits)39129_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:54"
+ },
+ {
+ "label": "clawbackReceiver",
+ "offset": 0,
+ "slot": "306",
+ "type": "t_address",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:59"
+ },
+ {
+ "label": "_inClawback",
+ "offset": 20,
+ "slot": "306",
+ "type": "t_bool",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:64"
+ },
+ {
+ "label": "_name",
+ "offset": 0,
+ "slot": "307",
+ "type": "t_string_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:69"
+ },
+ {
+ "label": "_symbol",
+ "offset": 0,
+ "slot": "308",
+ "type": "t_string_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:74"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "309",
+ "type": "t_array(t_uint256)44_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:79"
+ },
+ {
+ "label": "___gap",
+ "offset": 0,
+ "slot": "353",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:84"
+ }
+ ],
+ "types": {
+ "t_address": {
+ "label": "address",
+ "numberOfBytes": "20"
+ },
+ "t_array(t_bytes32)dyn_storage": {
+ "label": "bytes32[]",
+ "numberOfBytes": "32"
+ },
+ "t_array(t_uint256)44_storage": {
+ "label": "uint256[44]",
+ "numberOfBytes": "1408"
+ },
+ "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(IMidasAccessControl)37060": {
+ "label": "contract IMidasAccessControl",
+ "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_mapping(t_bytes32,t_uint256)": {
+ "label": "mapping(bytes32 => uint256)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)39119_storage)": {
+ "label": "mapping(uint256 => struct RateLimitLibrary.WindowRateLimitConfig)",
+ "numberOfBytes": "32"
+ },
+ "t_string_storage": {
+ "label": "string",
+ "numberOfBytes": "32"
+ },
+ "t_struct(Set)14575_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(UintSet)15047_storage": {
+ "label": "struct EnumerableSetUpgradeable.UintSet",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)14575_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(WindowRateLimitConfig)39119_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimitConfig",
+ "members": [
+ {
+ "label": "limit",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "amountInFlight",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "1"
+ },
+ {
+ "label": "lastUpdated",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "2"
+ },
+ {
+ "label": "window",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "3"
+ }
+ ],
+ "numberOfBytes": "128"
+ },
+ "t_struct(WindowRateLimits)39129_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimits",
+ "members": [
+ {
+ "label": "windows",
+ "type": "t_struct(UintSet)15047_storage",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "configs",
+ "type": "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)39119_storage)",
+ "offset": 0,
+ "slot": "2"
+ }
+ ],
+ "numberOfBytes": "96"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "c518b0b692d61f7c9db1bc9da6cc655d25c957482487018d343f2cb778fe71f1": {
+ "address": "0xdEaA809C72b8Aabbe9037F497B2779Ad3f7d4f87",
+ "txHash": "0xd4470fc2dba4177ae6937ab4fa32b612f94b3b5e6936d2f26d7ce2ac5621b326",
+ "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)20077",
+ "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)20090_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": "MFOneDepositVault",
+ "src": "contracts/products/mFONE/MFOneDepositVault.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)20077": {
+ "label": "contract IMToken",
+ "numberOfBytes": "20"
+ },
+ "t_contract(MidasAccessControl)17280": {
+ "label": "contract MidasAccessControl",
+ "numberOfBytes": "20"
+ },
+ "t_enum(RequestStatus)20094": {
+ "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)20090_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)20094",
+ "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)20090_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"
+ }
+ }
+ }
+ },
+ "32d2bfbec0f244f9923d9e56192802e03bc377df02c1c4867d8d438b7af698a8": {
+ "address": "0x71572E94b80d68600979B1d874189da76E521a80",
+ "txHash": "0x3928b4266baeb1601f122e8138d1ed7f9ac8328c86dbc7944f2bc16cce111018",
+ "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)20077",
+ "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)20090_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)20358_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)20595",
+ "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": "MFOneRedemptionVaultWithSwapper",
+ "src": "contracts/products/mFONE/MFOneRedemptionVaultWithSwapper.sol:19"
}
],
"types": {
@@ -46815,164 +53119,5346 @@
"label": "bytes4",
"numberOfBytes": "4"
},
- "t_contract(IDataFeed)19792": {
- "label": "contract IDataFeed",
- "numberOfBytes": "20"
+ "t_contract(IDataFeed)19792": {
+ "label": "contract IDataFeed",
+ "numberOfBytes": "20"
+ },
+ "t_contract(IMToken)20077": {
+ "label": "contract IMToken",
+ "numberOfBytes": "20"
+ },
+ "t_contract(IRedemptionVault)20595": {
+ "label": "contract IRedemptionVault",
+ "numberOfBytes": "20"
+ },
+ "t_contract(MidasAccessControl)17280": {
+ "label": "contract MidasAccessControl",
+ "numberOfBytes": "20"
+ },
+ "t_enum(RequestStatus)20094": {
+ "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)20090_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)20358_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)20358_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)20094",
+ "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)20090_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"
+ }
+ }
+ }
+ },
+ "91c32c785bd973802aff801d3d888b6f14cc40183e5e66e570e7bfdb7bd37c8c": {
+ "address": "0xe66B5Be1a08764907501f5670D04c3e1Aa77d63f",
+ "txHash": "0xd0eee676991bb204e34436ea226919ec52eece8930414ced96741608b0e534f4",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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(IMidasAccessControl)6445",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:29",
+ "retypedFrom": "MidasAccessControl"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "1",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:34"
+ },
+ {
+ "label": "_status",
+ "offset": 0,
+ "slot": "51",
+ "type": "t_uint256",
+ "contract": "ReentrancyGuardUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:38"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "52",
+ "type": "t_array(t_uint256)49_storage",
+ "contract": "ReentrancyGuardUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:88"
+ },
+ {
+ "label": "dataHashIndexes",
+ "offset": 0,
+ "slot": "101",
+ "type": "t_mapping(t_bytes32,t_uint256)",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:84"
+ },
+ {
+ "label": "proposerPendingOperationsCount",
+ "offset": 0,
+ "slot": "102",
+ "type": "t_mapping(t_address,t_uint256)",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:89"
+ },
+ {
+ "label": "_securityCouncils",
+ "offset": 0,
+ "slot": "103",
+ "type": "t_mapping(t_uint256,t_struct(AddressSet)3763_storage)",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:94"
+ },
+ {
+ "label": "_operationDetails",
+ "offset": 0,
+ "slot": "104",
+ "type": "t_mapping(t_bytes32,t_struct(TimelockOperationDetails)4181_storage)",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:99"
+ },
+ {
+ "label": "_pendingOperations",
+ "offset": 0,
+ "slot": "105",
+ "type": "t_struct(Bytes32Set)3642_storage",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:104"
+ },
+ {
+ "label": "timelock",
+ "offset": 0,
+ "slot": "107",
+ "type": "t_address",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:109"
+ },
+ {
+ "label": "maxPendingOperationsPerProposer",
+ "offset": 0,
+ "slot": "108",
+ "type": "t_uint256",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:114"
+ },
+ {
+ "label": "securityCouncilVersion",
+ "offset": 0,
+ "slot": "109",
+ "type": "t_uint256",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:119"
+ },
+ {
+ "label": "pendingSetCouncilOperationId",
+ "offset": 0,
+ "slot": "110",
+ "type": "t_bytes32",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:124"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "111",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:129"
+ }
+ ],
+ "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_contract(IMidasAccessControl)6445": {
+ "label": "contract IMidasAccessControl",
+ "numberOfBytes": "20"
+ },
+ "t_enum(TimelockOperationStatus)6608": {
+ "label": "enum TimelockOperationStatus",
+ "members": [
+ "NotExist",
+ "Pending",
+ "Paused",
+ "ApprovedExecution",
+ "ReadyToExecute",
+ "ReadyToAbort",
+ "Expired",
+ "Aborted",
+ "Executed",
+ "ExecutedWithFailure"
+ ],
+ "numberOfBytes": "1"
+ },
+ "t_mapping(t_address,t_uint256)": {
+ "label": "mapping(address => uint256)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_struct(TimelockOperationDetails)4181_storage)": {
+ "label": "mapping(bytes32 => struct MidasTimelockManager.TimelockOperationDetails)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_uint256)": {
+ "label": "mapping(bytes32 => uint256)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint256,t_struct(AddressSet)3763_storage)": {
+ "label": "mapping(uint256 => struct EnumerableSetUpgradeable.AddressSet)",
+ "numberOfBytes": "32"
+ },
+ "t_struct(AddressSet)3763_storage": {
+ "label": "struct EnumerableSetUpgradeable.AddressSet",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)3448_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(Bytes32Set)3642_storage": {
+ "label": "struct EnumerableSetUpgradeable.Bytes32Set",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)3448_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(Set)3448_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(TimelockOperationDetails)4181_storage": {
+ "label": "struct MidasTimelockManager.TimelockOperationDetails",
+ "members": [
+ {
+ "label": "votersForExecution",
+ "type": "t_struct(AddressSet)3763_storage",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "votersForVeto",
+ "type": "t_struct(AddressSet)3763_storage",
+ "offset": 0,
+ "slot": "2"
+ },
+ {
+ "label": "councilVersion",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "4"
+ },
+ {
+ "label": "dataHash",
+ "type": "t_bytes32",
+ "offset": 0,
+ "slot": "5"
+ },
+ {
+ "label": "status",
+ "type": "t_enum(TimelockOperationStatus)6608",
+ "offset": 0,
+ "slot": "6"
+ },
+ {
+ "label": "pauseReasonCode",
+ "type": "t_uint8",
+ "offset": 1,
+ "slot": "6"
+ },
+ {
+ "label": "isSetCouncilOperation",
+ "type": "t_bool",
+ "offset": 2,
+ "slot": "6"
+ },
+ {
+ "label": "createdAt",
+ "type": "t_uint32",
+ "offset": 3,
+ "slot": "6"
+ },
+ {
+ "label": "executionApprovedAt",
+ "type": "t_uint32",
+ "offset": 7,
+ "slot": "6"
+ },
+ {
+ "label": "operationProposer",
+ "type": "t_address",
+ "offset": 11,
+ "slot": "6"
+ },
+ {
+ "label": "pauser",
+ "type": "t_address",
+ "offset": 0,
+ "slot": "7"
+ }
+ ],
+ "numberOfBytes": "256"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint32": {
+ "label": "uint32",
+ "numberOfBytes": "4"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "45714fb7b97ff94bf165b540a948931141cd631861d453ce4d73554c6c6a55d1": {
+ "address": "0xC1Cc0Dd7e6605AC2909475B20ae466EBE8Bee029",
+ "txHash": "0x1d4054af890b450accda5e9dafd047403636de91b3a78466029ee4645d8cc41b",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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": "__gap",
+ "offset": 0,
+ "slot": "51",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "ERC165Upgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol:41"
+ },
+ {
+ "label": "_roles",
+ "offset": 0,
+ "slot": "101",
+ "type": "t_mapping(t_bytes32,t_struct(RoleData)80_storage)",
+ "contract": "AccessControlUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:62"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "102",
+ "type": "t_array(t_uint256)49_storage",
+ "contract": "AccessControlUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:260"
+ },
+ {
+ "label": "isUserFacingRole",
+ "offset": 0,
+ "slot": "151",
+ "type": "t_mapping(t_bytes32,t_bool)",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:39"
+ },
+ {
+ "label": "_grantOperatorRoles",
+ "offset": 0,
+ "slot": "152",
+ "type": "t_mapping(t_bytes32,t_mapping(t_address,t_bool))",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:44"
+ },
+ {
+ "label": "_permissionRoles",
+ "offset": 0,
+ "slot": "153",
+ "type": "t_mapping(t_bytes32,t_mapping(t_address,t_bool))",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:49"
+ },
+ {
+ "label": "_roleTimelockDelays",
+ "offset": 0,
+ "slot": "154",
+ "type": "t_mapping(t_bytes32,t_uint32)",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:54"
+ },
+ {
+ "label": "timelockManager",
+ "offset": 0,
+ "slot": "155",
+ "type": "t_address",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:59"
+ },
+ {
+ "label": "pauseManager",
+ "offset": 0,
+ "slot": "156",
+ "type": "t_address",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:64"
+ },
+ {
+ "label": "defaultDelay",
+ "offset": 20,
+ "slot": "156",
+ "type": "t_uint32",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:69"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "157",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:74"
+ }
+ ],
+ "types": {
+ "t_address": {
+ "label": "address",
+ "numberOfBytes": "20"
+ },
+ "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_mapping(t_address,t_bool)": {
+ "label": "mapping(address => bool)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_bool)": {
+ "label": "mapping(bytes32 => bool)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_mapping(t_address,t_bool))": {
+ "label": "mapping(bytes32 => mapping(address => bool))",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_struct(RoleData)80_storage)": {
+ "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_uint32)": {
+ "label": "mapping(bytes32 => uint32)",
+ "numberOfBytes": "32"
+ },
+ "t_struct(RoleData)80_storage": {
+ "label": "struct AccessControlUpgradeable.RoleData",
+ "members": [
+ {
+ "label": "members",
+ "type": "t_mapping(t_address,t_bool)",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "adminRole",
+ "type": "t_bytes32",
+ "offset": 0,
+ "slot": "1"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint32": {
+ "label": "uint32",
+ "numberOfBytes": "4"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "f4778de4805e3f2dd59e1bfeb947c26b6740ff7226fc05c2fad08df354e26399": {
+ "address": "0xCcBfb42dBc94cDC6f9386eF9D9b3eA0f72603789",
+ "txHash": "0xad28fc89050eb07ec27b5b59afaf24086a6a6ccad6306045c1247e83d84a467f",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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(IMidasAccessControl)22201",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:29",
+ "retypedFrom": "MidasAccessControl"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "1",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:34"
+ },
+ {
+ "label": "description",
+ "offset": 0,
+ "slot": "51",
+ "type": "t_string_storage",
+ "contract": "CustomAggregatorV3CompatibleFeed",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:38"
+ },
+ {
+ "label": "latestRound",
+ "offset": 0,
+ "slot": "52",
+ "type": "t_uint80",
+ "contract": "CustomAggregatorV3CompatibleFeed",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:43"
+ },
+ {
+ "label": "maxAnswerDeviation",
+ "offset": 0,
+ "slot": "53",
+ "type": "t_uint256",
+ "contract": "CustomAggregatorV3CompatibleFeed",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:49"
+ },
+ {
+ "label": "minAnswer",
+ "offset": 0,
+ "slot": "54",
+ "type": "t_int192",
+ "contract": "CustomAggregatorV3CompatibleFeed",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:54"
+ },
+ {
+ "label": "maxAnswer",
+ "offset": 0,
+ "slot": "55",
+ "type": "t_int192",
+ "contract": "CustomAggregatorV3CompatibleFeed",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:59"
+ },
+ {
+ "label": "_roundData",
+ "offset": 0,
+ "slot": "56",
+ "type": "t_mapping(t_uint80,t_struct(RoundData)19295_storage)",
+ "contract": "CustomAggregatorV3CompatibleFeed",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:64"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "57",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "CustomAggregatorV3CompatibleFeed",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeed.sol:69"
+ }
+ ],
+ "types": {
+ "t_array(t_uint256)50_storage": {
+ "label": "uint256[50]",
+ "numberOfBytes": "1600"
+ },
+ "t_bool": {
+ "label": "bool",
+ "numberOfBytes": "1"
+ },
+ "t_contract(IMidasAccessControl)22201": {
+ "label": "contract IMidasAccessControl",
+ "numberOfBytes": "20"
+ },
+ "t_int192": {
+ "label": "int192",
+ "numberOfBytes": "24"
+ },
+ "t_int256": {
+ "label": "int256",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint80,t_struct(RoundData)19295_storage)": {
+ "label": "mapping(uint80 => struct CustomAggregatorV3CompatibleFeed.RoundData)",
+ "numberOfBytes": "32"
+ },
+ "t_string_storage": {
+ "label": "string",
+ "numberOfBytes": "32"
+ },
+ "t_struct(RoundData)19295_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"
+ }
+ }
+ }
+ },
+ "5264f4709b1cb604d748bed29c33bf9823240773dcce61d0b7159a7c415c654c": {
+ "address": "0xEAA94CCA005917463c3d7443CCE85A315aB9AaC4",
+ "txHash": "0xdc32562965aae5fc4a8dcb5a3b9cbe9ea62dbe79ec482c1821ebf183010dee76",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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(IMidasAccessControl)22201",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:29",
+ "retypedFrom": "MidasAccessControl"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "1",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:34"
+ },
+ {
+ "label": "description",
+ "offset": 0,
+ "slot": "51",
+ "type": "t_string_storage",
+ "contract": "CustomAggregatorV3CompatibleFeedGrowth",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol:49"
+ },
+ {
+ "label": "maxAnswerDeviation",
+ "offset": 0,
+ "slot": "52",
+ "type": "t_uint256",
+ "contract": "CustomAggregatorV3CompatibleFeedGrowth",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol:55"
+ },
+ {
+ "label": "minAnswer",
+ "offset": 0,
+ "slot": "53",
+ "type": "t_int192",
+ "contract": "CustomAggregatorV3CompatibleFeedGrowth",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol:60"
+ },
+ {
+ "label": "maxAnswer",
+ "offset": 0,
+ "slot": "54",
+ "type": "t_int192",
+ "contract": "CustomAggregatorV3CompatibleFeedGrowth",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol:65"
+ },
+ {
+ "label": "minGrowthApr",
+ "offset": 0,
+ "slot": "55",
+ "type": "t_int80",
+ "contract": "CustomAggregatorV3CompatibleFeedGrowth",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol:70"
+ },
+ {
+ "label": "maxGrowthApr",
+ "offset": 10,
+ "slot": "55",
+ "type": "t_int80",
+ "contract": "CustomAggregatorV3CompatibleFeedGrowth",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol:75"
+ },
+ {
+ "label": "latestRound",
+ "offset": 20,
+ "slot": "55",
+ "type": "t_uint80",
+ "contract": "CustomAggregatorV3CompatibleFeedGrowth",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol:80"
+ },
+ {
+ "label": "onlyUp",
+ "offset": 30,
+ "slot": "55",
+ "type": "t_bool",
+ "contract": "CustomAggregatorV3CompatibleFeedGrowth",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol:86"
+ },
+ {
+ "label": "_roundData",
+ "offset": 0,
+ "slot": "56",
+ "type": "t_mapping(t_uint80,t_struct(RoundDataWithGrowth)19738_storage)",
+ "contract": "CustomAggregatorV3CompatibleFeedGrowth",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol:91"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "57",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "CustomAggregatorV3CompatibleFeedGrowth",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol:96"
+ },
+ {
+ "label": "___gap",
+ "offset": 0,
+ "slot": "107",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "CustomAggregatorV3CompatibleFeedGrowth",
+ "src": "contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol:101"
+ }
+ ],
+ "types": {
+ "t_array(t_uint256)50_storage": {
+ "label": "uint256[50]",
+ "numberOfBytes": "1600"
+ },
+ "t_bool": {
+ "label": "bool",
+ "numberOfBytes": "1"
+ },
+ "t_contract(IMidasAccessControl)22201": {
+ "label": "contract IMidasAccessControl",
+ "numberOfBytes": "20"
+ },
+ "t_int192": {
+ "label": "int192",
+ "numberOfBytes": "24"
+ },
+ "t_int256": {
+ "label": "int256",
+ "numberOfBytes": "32"
+ },
+ "t_int80": {
+ "label": "int80",
+ "numberOfBytes": "10"
+ },
+ "t_mapping(t_uint80,t_struct(RoundDataWithGrowth)19738_storage)": {
+ "label": "mapping(uint80 => struct CustomAggregatorV3CompatibleFeedGrowth.RoundDataWithGrowth)",
+ "numberOfBytes": "32"
+ },
+ "t_string_storage": {
+ "label": "string",
+ "numberOfBytes": "32"
+ },
+ "t_struct(RoundDataWithGrowth)19738_storage": {
+ "label": "struct CustomAggregatorV3CompatibleFeedGrowth.RoundDataWithGrowth",
+ "members": [
+ {
+ "label": "roundId",
+ "type": "t_uint80",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "answeredInRound",
+ "type": "t_uint80",
+ "offset": 10,
+ "slot": "0"
+ },
+ {
+ "label": "growthApr",
+ "type": "t_int80",
+ "offset": 20,
+ "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"
+ }
+ ],
+ "numberOfBytes": "128"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ },
+ "t_uint80": {
+ "label": "uint80",
+ "numberOfBytes": "10"
+ }
+ }
+ }
+ },
+ "51c30f76ce8e4692507549d49a950597389b9ec8ab70b92cd4c1ed3b64593168": {
+ "address": "0x54dCCa1a7A9178EB7Bd9545706e510AEa7ad7C00",
+ "txHash": "0xca0af94bcc3cffe24fbbc39ab04cc44cdd03a79030e566b005f3ddc9ce8a99d4",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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(IMidasAccessControl)22201",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:29",
+ "retypedFrom": "MidasAccessControl"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "1",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:34"
+ },
+ {
+ "label": "aggregator",
+ "offset": 0,
+ "slot": "51",
+ "type": "t_contract(AggregatorV3Interface)45",
+ "contract": "DataFeed",
+ "src": "contracts/feeds/DataFeed.sol:29"
+ },
+ {
+ "label": "healthyDiff",
+ "offset": 0,
+ "slot": "52",
+ "type": "t_uint256",
+ "contract": "DataFeed",
+ "src": "contracts/feeds/DataFeed.sol:34"
+ },
+ {
+ "label": "minExpectedAnswer",
+ "offset": 0,
+ "slot": "53",
+ "type": "t_int256",
+ "contract": "DataFeed",
+ "src": "contracts/feeds/DataFeed.sol:39"
+ },
+ {
+ "label": "maxExpectedAnswer",
+ "offset": 0,
+ "slot": "54",
+ "type": "t_int256",
+ "contract": "DataFeed",
+ "src": "contracts/feeds/DataFeed.sol:44"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "55",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "DataFeed",
+ "src": "contracts/feeds/DataFeed.sol:49"
+ },
+ {
+ "label": "___gap",
+ "offset": 0,
+ "slot": "105",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "DataFeed",
+ "src": "contracts/feeds/DataFeed.sol:54"
+ }
+ ],
+ "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(IMidasAccessControl)22201": {
+ "label": "contract IMidasAccessControl",
+ "numberOfBytes": "20"
+ },
+ "t_int256": {
+ "label": "int256",
+ "numberOfBytes": "32"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "3b20331aca23803b962de691d174fb2b0bfbc902ecec76741ab1f2118abf3f39": {
+ "address": "0x31a4D966DB9f39C9B85ACA3057D90b1C3dc8F0D4",
+ "txHash": "0x1659cae1b0a48c6be639ce3e17fb6d24778f46a580e15fee136cbe86de4fc2d9",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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(IMidasAccessControl)22201",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:29",
+ "retypedFrom": "MidasAccessControl"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "202",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:34"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "252",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "Blacklistable",
+ "src": "contracts/access/Blacklistable.sol:17"
+ },
+ {
+ "label": "metadata",
+ "offset": 0,
+ "slot": "302",
+ "type": "t_mapping(t_bytes32,t_bytes_storage)",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:62"
+ },
+ {
+ "label": "_mintRateLimits",
+ "offset": 0,
+ "slot": "303",
+ "type": "t_struct(WindowRateLimits)24054_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:67"
+ },
+ {
+ "label": "clawbackReceiver",
+ "offset": 0,
+ "slot": "306",
+ "type": "t_address",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:72"
+ },
+ {
+ "label": "_inClawback",
+ "offset": 20,
+ "slot": "306",
+ "type": "t_bool",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:77"
+ },
+ {
+ "label": "_name",
+ "offset": 0,
+ "slot": "307",
+ "type": "t_string_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:82"
+ },
+ {
+ "label": "_symbol",
+ "offset": 0,
+ "slot": "308",
+ "type": "t_string_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:87"
+ },
+ {
+ "label": "isPermissioned",
+ "offset": 0,
+ "slot": "309",
+ "type": "t_bool",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:92"
+ },
+ {
+ "label": "isMinHoldingBalanceEnforced",
+ "offset": 1,
+ "slot": "309",
+ "type": "t_bool",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:97"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "310",
+ "type": "t_array(t_uint256)43_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:102"
+ },
+ {
+ "label": "___gap",
+ "offset": 0,
+ "slot": "353",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:107"
+ },
+ {
+ "label": "____gap",
+ "offset": 0,
+ "slot": "403",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:112"
+ }
+ ],
+ "types": {
+ "t_address": {
+ "label": "address",
+ "numberOfBytes": "20"
+ },
+ "t_array(t_bytes32)dyn_storage": {
+ "label": "bytes32[]",
+ "numberOfBytes": "32"
+ },
+ "t_array(t_uint256)43_storage": {
+ "label": "uint256[43]",
+ "numberOfBytes": "1376"
+ },
+ "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(IMidasAccessControl)22201": {
+ "label": "contract IMidasAccessControl",
+ "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_mapping(t_bytes32,t_uint256)": {
+ "label": "mapping(bytes32 => uint256)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)24044_storage)": {
+ "label": "mapping(uint256 => struct RateLimitLibrary.WindowRateLimitConfig)",
+ "numberOfBytes": "32"
+ },
+ "t_string_storage": {
+ "label": "string",
+ "numberOfBytes": "32"
+ },
+ "t_struct(Set)4808_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(UintSet)5280_storage": {
+ "label": "struct EnumerableSetUpgradeable.UintSet",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)4808_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(WindowRateLimitConfig)24044_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimitConfig",
+ "members": [
+ {
+ "label": "limit",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "amountInFlight",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "1"
+ },
+ {
+ "label": "lastUpdated",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "2"
+ },
+ {
+ "label": "window",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "3"
+ }
+ ],
+ "numberOfBytes": "128"
+ },
+ "t_struct(WindowRateLimits)24054_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimits",
+ "members": [
+ {
+ "label": "windows",
+ "type": "t_struct(UintSet)5280_storage",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "configs",
+ "type": "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)24044_storage)",
+ "offset": 0,
+ "slot": "2"
+ }
+ ],
+ "numberOfBytes": "96"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "0858acca55e1098f4b781e7e3559c2854a19799dc32c8e945ebc2b11945521a9": {
+ "address": "0x9520BA822BC775bA8E51437E9600427C3e1F266e",
+ "txHash": "0xe08800b0d670dcbbc3d776eb9d88432559c9df3440308e9d437551d73285b035",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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(IMidasAccessControl)22201",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:29",
+ "retypedFrom": "MidasAccessControl"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "202",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:34"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "252",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "Blacklistable",
+ "src": "contracts/access/Blacklistable.sol:17"
+ },
+ {
+ "label": "metadata",
+ "offset": 0,
+ "slot": "302",
+ "type": "t_mapping(t_bytes32,t_bytes_storage)",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:62"
+ },
+ {
+ "label": "_mintRateLimits",
+ "offset": 0,
+ "slot": "303",
+ "type": "t_struct(WindowRateLimits)24054_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:67"
+ },
+ {
+ "label": "clawbackReceiver",
+ "offset": 0,
+ "slot": "306",
+ "type": "t_address",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:72"
+ },
+ {
+ "label": "_inClawback",
+ "offset": 20,
+ "slot": "306",
+ "type": "t_bool",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:77"
+ },
+ {
+ "label": "_name",
+ "offset": 0,
+ "slot": "307",
+ "type": "t_string_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:82"
+ },
+ {
+ "label": "_symbol",
+ "offset": 0,
+ "slot": "308",
+ "type": "t_string_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:87"
+ },
+ {
+ "label": "isPermissioned",
+ "offset": 0,
+ "slot": "309",
+ "type": "t_bool",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:92"
+ },
+ {
+ "label": "isMinHoldingBalanceEnforced",
+ "offset": 1,
+ "slot": "309",
+ "type": "t_bool",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:97"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "310",
+ "type": "t_array(t_uint256)43_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:102"
+ },
+ {
+ "label": "___gap",
+ "offset": 0,
+ "slot": "353",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:107"
+ },
+ {
+ "label": "____gap",
+ "offset": 0,
+ "slot": "403",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:112"
+ }
+ ],
+ "types": {
+ "t_address": {
+ "label": "address",
+ "numberOfBytes": "20"
+ },
+ "t_array(t_bytes32)dyn_storage": {
+ "label": "bytes32[]",
+ "numberOfBytes": "32"
+ },
+ "t_array(t_uint256)43_storage": {
+ "label": "uint256[43]",
+ "numberOfBytes": "1376"
+ },
+ "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(IMidasAccessControl)22201": {
+ "label": "contract IMidasAccessControl",
+ "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_mapping(t_bytes32,t_uint256)": {
+ "label": "mapping(bytes32 => uint256)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)24044_storage)": {
+ "label": "mapping(uint256 => struct RateLimitLibrary.WindowRateLimitConfig)",
+ "numberOfBytes": "32"
+ },
+ "t_string_storage": {
+ "label": "string",
+ "numberOfBytes": "32"
+ },
+ "t_struct(Set)4808_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(UintSet)5280_storage": {
+ "label": "struct EnumerableSetUpgradeable.UintSet",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)4808_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(WindowRateLimitConfig)24044_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimitConfig",
+ "members": [
+ {
+ "label": "limit",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "amountInFlight",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "1"
+ },
+ {
+ "label": "lastUpdated",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "2"
+ },
+ {
+ "label": "window",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "3"
+ }
+ ],
+ "numberOfBytes": "128"
+ },
+ "t_struct(WindowRateLimits)24054_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimits",
+ "members": [
+ {
+ "label": "windows",
+ "type": "t_struct(UintSet)5280_storage",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "configs",
+ "type": "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)24044_storage)",
+ "offset": 0,
+ "slot": "2"
+ }
+ ],
+ "numberOfBytes": "96"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "f2e3747f1273ad960b54934074e57cd12d6ffb5ed0cbf21a3521ef5e2d6a17f8": {
+ "address": "0x6cA50C89e2F5b72723aE12f4588e0097C6CBeeC5",
+ "txHash": "0xb15ba116135a243ec449fbaa429119bbba761bdcb76767050197ef817646d18b",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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(IMidasAccessControl)22201",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:29",
+ "retypedFrom": "MidasAccessControl"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "1",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:34"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "51",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "Blacklistable",
+ "src": "contracts/access/Blacklistable.sol:17"
+ },
+ {
+ "label": "greenlistEnabled",
+ "offset": 0,
+ "slot": "101",
+ "type": "t_bool",
+ "contract": "Greenlistable",
+ "src": "contracts/access/Greenlistable.sol:17"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "102",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "Greenlistable",
+ "src": "contracts/access/Greenlistable.sol:22"
+ },
+ {
+ "label": "sanctionsList",
+ "offset": 0,
+ "slot": "152",
+ "type": "t_address",
+ "contract": "WithSanctionsList",
+ "src": "contracts/abstract/WithSanctionsList.sol:17"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "153",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithSanctionsList",
+ "src": "contracts/abstract/WithSanctionsList.sol:22"
+ },
+ {
+ "label": "tokensConfig",
+ "offset": 0,
+ "slot": "203",
+ "type": "t_mapping(t_address,t_struct(TokenConfig)21442_storage)",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:73"
+ },
+ {
+ "label": "isFreeFromMinAmount",
+ "offset": 0,
+ "slot": "204",
+ "type": "t_mapping(t_address,t_bool)",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:78"
+ },
+ {
+ "label": "waivedFeeRestriction",
+ "offset": 0,
+ "slot": "205",
+ "type": "t_mapping(t_address,t_bool)",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:83"
+ },
+ {
+ "label": "_paymentTokens",
+ "offset": 0,
+ "slot": "206",
+ "type": "t_struct(AddressSet)5123_storage",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:88"
+ },
+ {
+ "label": "_instantRateLimits",
+ "offset": 0,
+ "slot": "208",
+ "type": "t_struct(WindowRateLimits)24054_storage",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:93"
+ },
+ {
+ "label": "currentRequestId",
+ "offset": 0,
+ "slot": "211",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:98"
+ },
+ {
+ "label": "nextExpectedRequestIdToProcess",
+ "offset": 0,
+ "slot": "212",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:103"
+ },
+ {
+ "label": "maxApproveRequestId",
+ "offset": 0,
+ "slot": "213",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:108"
+ },
+ {
+ "label": "mToken",
+ "offset": 0,
+ "slot": "214",
+ "type": "t_contract(IMToken)21422",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:113"
+ },
+ {
+ "label": "mTokenDataFeed",
+ "offset": 0,
+ "slot": "215",
+ "type": "t_contract(IDataFeed)20984",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:118"
+ },
+ {
+ "label": "tokensReceiver",
+ "offset": 0,
+ "slot": "216",
+ "type": "t_address",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:123"
+ },
+ {
+ "label": "variationTolerance",
+ "offset": 0,
+ "slot": "217",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:128"
+ },
+ {
+ "label": "minAmount",
+ "offset": 0,
+ "slot": "218",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:133"
+ },
+ {
+ "label": "instantFee",
+ "offset": 0,
+ "slot": "219",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:138"
+ },
+ {
+ "label": "minInstantFee",
+ "offset": 0,
+ "slot": "220",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:143"
+ },
+ {
+ "label": "maxInstantFee",
+ "offset": 0,
+ "slot": "221",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:148"
+ },
+ {
+ "label": "maxInstantShare",
+ "offset": 0,
+ "slot": "222",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:153"
+ },
+ {
+ "label": "sequentialRequestProcessing",
+ "offset": 0,
+ "slot": "223",
+ "type": "t_bool",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:158"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "224",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:163"
+ },
+ {
+ "label": "mintRequests",
+ "offset": 0,
+ "slot": "274",
+ "type": "t_mapping(t_uint256,t_struct(Request)21019_storage)",
+ "contract": "DepositVault",
+ "src": "contracts/DepositVault.sol:45"
+ },
+ {
+ "label": "totalMinted",
+ "offset": 0,
+ "slot": "275",
+ "type": "t_mapping(t_address,t_uint256)",
+ "contract": "DepositVault",
+ "src": "contracts/DepositVault.sol:51"
+ },
+ {
+ "label": "minMTokenAmountForFirstDeposit",
+ "offset": 0,
+ "slot": "276",
+ "type": "t_uint256",
+ "contract": "DepositVault",
+ "src": "contracts/DepositVault.sol:56"
+ },
+ {
+ "label": "maxSupplyCap",
+ "offset": 0,
+ "slot": "277",
+ "type": "t_uint256",
+ "contract": "DepositVault",
+ "src": "contracts/DepositVault.sol:63"
+ },
+ {
+ "label": "maxAmountPerRequest",
+ "offset": 0,
+ "slot": "278",
+ "type": "t_uint256",
+ "contract": "DepositVault",
+ "src": "contracts/DepositVault.sol:68"
+ },
+ {
+ "label": "upcomingSupply",
+ "offset": 0,
+ "slot": "279",
+ "type": "t_uint256",
+ "contract": "DepositVault",
+ "src": "contracts/DepositVault.sol:74"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "280",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "DepositVault",
+ "src": "contracts/DepositVault.sol:79"
+ }
+ ],
+ "types": {
+ "t_address": {
+ "label": "address",
+ "numberOfBytes": "20"
+ },
+ "t_array(t_bytes32)dyn_storage": {
+ "label": "bytes32[]",
+ "numberOfBytes": "32"
+ },
+ "t_array(t_uint256)50_storage": {
+ "label": "uint256[50]",
+ "numberOfBytes": "1600"
+ },
+ "t_bool": {
+ "label": "bool",
+ "numberOfBytes": "1"
+ },
+ "t_bytes32": {
+ "label": "bytes32",
+ "numberOfBytes": "32"
+ },
+ "t_contract(IDataFeed)20984": {
+ "label": "contract IDataFeed",
+ "numberOfBytes": "20"
+ },
+ "t_contract(IMToken)21422": {
+ "label": "contract IMToken",
+ "numberOfBytes": "20"
+ },
+ "t_contract(IMidasAccessControl)22201": {
+ "label": "contract IMidasAccessControl",
+ "numberOfBytes": "20"
+ },
+ "t_enum(RequestStatus)21446": {
+ "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)21442_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_uint256,t_struct(Request)21019_storage)": {
+ "label": "mapping(uint256 => struct Request)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)24044_storage)": {
+ "label": "mapping(uint256 => struct RateLimitLibrary.WindowRateLimitConfig)",
+ "numberOfBytes": "32"
+ },
+ "t_struct(AddressSet)5123_storage": {
+ "label": "struct EnumerableSetUpgradeable.AddressSet",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)4808_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(Request)21019_storage": {
+ "label": "struct Request",
+ "members": [
+ {
+ "label": "recipient",
+ "type": "t_address",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "tokenIn",
+ "type": "t_address",
+ "offset": 0,
+ "slot": "1"
+ },
+ {
+ "label": "status",
+ "type": "t_enum(RequestStatus)21446",
+ "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"
+ },
+ {
+ "label": "depositedInstantUsdAmount",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "5"
+ },
+ {
+ "label": "approvedTokenOutRate",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "6"
+ },
+ {
+ "label": "amountMToken",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "7"
+ }
+ ],
+ "numberOfBytes": "256"
+ },
+ "t_struct(Set)4808_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)21442_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_struct(UintSet)5280_storage": {
+ "label": "struct EnumerableSetUpgradeable.UintSet",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)4808_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(WindowRateLimitConfig)24044_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimitConfig",
+ "members": [
+ {
+ "label": "limit",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "amountInFlight",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "1"
+ },
+ {
+ "label": "lastUpdated",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "2"
+ },
+ {
+ "label": "window",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "3"
+ }
+ ],
+ "numberOfBytes": "128"
+ },
+ "t_struct(WindowRateLimits)24054_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimits",
+ "members": [
+ {
+ "label": "windows",
+ "type": "t_struct(UintSet)5280_storage",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "configs",
+ "type": "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)24044_storage)",
+ "offset": 0,
+ "slot": "2"
+ }
+ ],
+ "numberOfBytes": "96"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "7e51511eb101baf2b0c82bab8846b1999725ca4d13117fedf690b7eb0aad24fc": {
+ "address": "0xa797221d7090bE155Dd79d82125AFb0Fd100fAdC",
+ "txHash": "0x9984cbe3e7d7d3a37f76dda53659ecc2caf2297b7bc5c25ce4e3e7e36ce474d9",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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(IMidasAccessControl)22201",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:29",
+ "retypedFrom": "MidasAccessControl"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "1",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:34"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "51",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "Blacklistable",
+ "src": "contracts/access/Blacklistable.sol:17"
+ },
+ {
+ "label": "greenlistEnabled",
+ "offset": 0,
+ "slot": "101",
+ "type": "t_bool",
+ "contract": "Greenlistable",
+ "src": "contracts/access/Greenlistable.sol:17"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "102",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "Greenlistable",
+ "src": "contracts/access/Greenlistable.sol:22"
+ },
+ {
+ "label": "sanctionsList",
+ "offset": 0,
+ "slot": "152",
+ "type": "t_address",
+ "contract": "WithSanctionsList",
+ "src": "contracts/abstract/WithSanctionsList.sol:17"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "153",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithSanctionsList",
+ "src": "contracts/abstract/WithSanctionsList.sol:22"
+ },
+ {
+ "label": "tokensConfig",
+ "offset": 0,
+ "slot": "203",
+ "type": "t_mapping(t_address,t_struct(TokenConfig)21442_storage)",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:73"
+ },
+ {
+ "label": "isFreeFromMinAmount",
+ "offset": 0,
+ "slot": "204",
+ "type": "t_mapping(t_address,t_bool)",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:78"
+ },
+ {
+ "label": "waivedFeeRestriction",
+ "offset": 0,
+ "slot": "205",
+ "type": "t_mapping(t_address,t_bool)",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:83"
+ },
+ {
+ "label": "_paymentTokens",
+ "offset": 0,
+ "slot": "206",
+ "type": "t_struct(AddressSet)5123_storage",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:88"
+ },
+ {
+ "label": "_instantRateLimits",
+ "offset": 0,
+ "slot": "208",
+ "type": "t_struct(WindowRateLimits)24054_storage",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:93"
+ },
+ {
+ "label": "currentRequestId",
+ "offset": 0,
+ "slot": "211",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:98"
+ },
+ {
+ "label": "nextExpectedRequestIdToProcess",
+ "offset": 0,
+ "slot": "212",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:103"
+ },
+ {
+ "label": "maxApproveRequestId",
+ "offset": 0,
+ "slot": "213",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:108"
+ },
+ {
+ "label": "mToken",
+ "offset": 0,
+ "slot": "214",
+ "type": "t_contract(IMToken)21422",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:113"
+ },
+ {
+ "label": "mTokenDataFeed",
+ "offset": 0,
+ "slot": "215",
+ "type": "t_contract(IDataFeed)20984",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:118"
+ },
+ {
+ "label": "tokensReceiver",
+ "offset": 0,
+ "slot": "216",
+ "type": "t_address",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:123"
+ },
+ {
+ "label": "variationTolerance",
+ "offset": 0,
+ "slot": "217",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:128"
+ },
+ {
+ "label": "minAmount",
+ "offset": 0,
+ "slot": "218",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:133"
+ },
+ {
+ "label": "instantFee",
+ "offset": 0,
+ "slot": "219",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:138"
+ },
+ {
+ "label": "minInstantFee",
+ "offset": 0,
+ "slot": "220",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:143"
+ },
+ {
+ "label": "maxInstantFee",
+ "offset": 0,
+ "slot": "221",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:148"
+ },
+ {
+ "label": "maxInstantShare",
+ "offset": 0,
+ "slot": "222",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:153"
+ },
+ {
+ "label": "sequentialRequestProcessing",
+ "offset": 0,
+ "slot": "223",
+ "type": "t_bool",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:158"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "224",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:163"
+ },
+ {
+ "label": "redeemRequests",
+ "offset": 0,
+ "slot": "274",
+ "type": "t_mapping(t_uint256,t_struct(Request)22776_storage)",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:45"
+ },
+ {
+ "label": "loanRequests",
+ "offset": 0,
+ "slot": "275",
+ "type": "t_mapping(t_uint256,t_struct(LiquidityProviderLoanRequest)22811_storage)",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:50"
+ },
+ {
+ "label": "requestRedeemer",
+ "offset": 0,
+ "slot": "276",
+ "type": "t_address",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:55"
+ },
+ {
+ "label": "loanLp",
+ "offset": 0,
+ "slot": "277",
+ "type": "t_address",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:60"
+ },
+ {
+ "label": "loanRepaymentAddress",
+ "offset": 0,
+ "slot": "278",
+ "type": "t_address",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:65"
+ },
+ {
+ "label": "loanApr",
+ "offset": 0,
+ "slot": "279",
+ "type": "t_uint256",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:70"
+ },
+ {
+ "label": "preferLoanLiquidity",
+ "offset": 0,
+ "slot": "280",
+ "type": "t_bool",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:75"
+ },
+ {
+ "label": "currentLoanRequestId",
+ "offset": 0,
+ "slot": "281",
+ "type": "t_uint256",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:80"
+ },
+ {
+ "label": "loanSwapperVault",
+ "offset": 0,
+ "slot": "282",
+ "type": "t_contract(IRedemptionVault)23098",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:85"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "283",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:90"
+ }
+ ],
+ "types": {
+ "t_address": {
+ "label": "address",
+ "numberOfBytes": "20"
+ },
+ "t_array(t_bytes32)dyn_storage": {
+ "label": "bytes32[]",
+ "numberOfBytes": "32"
+ },
+ "t_array(t_uint256)50_storage": {
+ "label": "uint256[50]",
+ "numberOfBytes": "1600"
+ },
+ "t_bool": {
+ "label": "bool",
+ "numberOfBytes": "1"
+ },
+ "t_bytes32": {
+ "label": "bytes32",
+ "numberOfBytes": "32"
+ },
+ "t_contract(IDataFeed)20984": {
+ "label": "contract IDataFeed",
+ "numberOfBytes": "20"
+ },
+ "t_contract(IMToken)21422": {
+ "label": "contract IMToken",
+ "numberOfBytes": "20"
+ },
+ "t_contract(IMidasAccessControl)22201": {
+ "label": "contract IMidasAccessControl",
+ "numberOfBytes": "20"
+ },
+ "t_contract(IRedemptionVault)23098": {
+ "label": "contract IRedemptionVault",
+ "numberOfBytes": "20"
+ },
+ "t_enum(RequestStatus)21446": {
+ "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)21442_storage)": {
+ "label": "mapping(address => struct TokenConfig)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_uint256)": {
+ "label": "mapping(bytes32 => uint256)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint256,t_struct(LiquidityProviderLoanRequest)22811_storage)": {
+ "label": "mapping(uint256 => struct LiquidityProviderLoanRequest)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint256,t_struct(Request)22776_storage)": {
+ "label": "mapping(uint256 => struct Request)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)24044_storage)": {
+ "label": "mapping(uint256 => struct RateLimitLibrary.WindowRateLimitConfig)",
+ "numberOfBytes": "32"
+ },
+ "t_struct(AddressSet)5123_storage": {
+ "label": "struct EnumerableSetUpgradeable.AddressSet",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)4808_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(LiquidityProviderLoanRequest)22811_storage": {
+ "label": "struct LiquidityProviderLoanRequest",
+ "members": [
+ {
+ "label": "tokenOut",
+ "type": "t_address",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "amountTokenOut",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "1"
+ },
+ {
+ "label": "amountFee",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "2"
+ },
+ {
+ "label": "createdAt",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "3"
+ },
+ {
+ "label": "status",
+ "type": "t_enum(RequestStatus)21446",
+ "offset": 0,
+ "slot": "4"
+ }
+ ],
+ "numberOfBytes": "160"
+ },
+ "t_struct(Request)22776_storage": {
+ "label": "struct Request",
+ "members": [
+ {
+ "label": "recipient",
+ "type": "t_address",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "tokenOut",
+ "type": "t_address",
+ "offset": 0,
+ "slot": "1"
+ },
+ {
+ "label": "status",
+ "type": "t_enum(RequestStatus)21446",
+ "offset": 20,
+ "slot": "1"
+ },
+ {
+ "label": "feePercent",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "2"
+ },
+ {
+ "label": "amountMToken",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "3"
+ },
+ {
+ "label": "mTokenRate",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "4"
+ },
+ {
+ "label": "tokenOutRate",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "5"
+ },
+ {
+ "label": "amountMTokenInstant",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "6"
+ },
+ {
+ "label": "approvedMTokenRate",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "7"
+ },
+ {
+ "label": "amountTokenOut",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "8"
+ }
+ ],
+ "numberOfBytes": "288"
+ },
+ "t_struct(Set)4808_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)21442_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_struct(UintSet)5280_storage": {
+ "label": "struct EnumerableSetUpgradeable.UintSet",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)4808_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(WindowRateLimitConfig)24044_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimitConfig",
+ "members": [
+ {
+ "label": "limit",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "amountInFlight",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "1"
+ },
+ {
+ "label": "lastUpdated",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "2"
+ },
+ {
+ "label": "window",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "3"
+ }
+ ],
+ "numberOfBytes": "128"
+ },
+ "t_struct(WindowRateLimits)24054_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimits",
+ "members": [
+ {
+ "label": "windows",
+ "type": "t_struct(UintSet)5280_storage",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "configs",
+ "type": "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)24044_storage)",
+ "offset": 0,
+ "slot": "2"
+ }
+ ],
+ "numberOfBytes": "96"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "2ecae9b5ef6ba02d93581d408bcd33b59f44f19232a7fb35d75ee98e45beaf8c": {
+ "address": "0xc330fec7207D8348DFaa0967C00495c91c17b056",
+ "txHash": "0x9b94cd8ad383b596aa17dbb7825c36296b3e68c19bae6b94105a6aaed2c50d15",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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(IMidasAccessControl)22201",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:29",
+ "retypedFrom": "MidasAccessControl"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "1",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:34"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "51",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "Blacklistable",
+ "src": "contracts/access/Blacklistable.sol:17"
+ },
+ {
+ "label": "greenlistEnabled",
+ "offset": 0,
+ "slot": "101",
+ "type": "t_bool",
+ "contract": "Greenlistable",
+ "src": "contracts/access/Greenlistable.sol:17"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "102",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "Greenlistable",
+ "src": "contracts/access/Greenlistable.sol:22"
+ },
+ {
+ "label": "sanctionsList",
+ "offset": 0,
+ "slot": "152",
+ "type": "t_address",
+ "contract": "WithSanctionsList",
+ "src": "contracts/abstract/WithSanctionsList.sol:17"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "153",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithSanctionsList",
+ "src": "contracts/abstract/WithSanctionsList.sol:22"
+ },
+ {
+ "label": "tokensConfig",
+ "offset": 0,
+ "slot": "203",
+ "type": "t_mapping(t_address,t_struct(TokenConfig)21442_storage)",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:73"
+ },
+ {
+ "label": "isFreeFromMinAmount",
+ "offset": 0,
+ "slot": "204",
+ "type": "t_mapping(t_address,t_bool)",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:78"
+ },
+ {
+ "label": "waivedFeeRestriction",
+ "offset": 0,
+ "slot": "205",
+ "type": "t_mapping(t_address,t_bool)",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:83"
+ },
+ {
+ "label": "_paymentTokens",
+ "offset": 0,
+ "slot": "206",
+ "type": "t_struct(AddressSet)5123_storage",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:88"
+ },
+ {
+ "label": "_instantRateLimits",
+ "offset": 0,
+ "slot": "208",
+ "type": "t_struct(WindowRateLimits)24054_storage",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:93"
+ },
+ {
+ "label": "currentRequestId",
+ "offset": 0,
+ "slot": "211",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:98"
+ },
+ {
+ "label": "nextExpectedRequestIdToProcess",
+ "offset": 0,
+ "slot": "212",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:103"
+ },
+ {
+ "label": "maxApproveRequestId",
+ "offset": 0,
+ "slot": "213",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:108"
+ },
+ {
+ "label": "mToken",
+ "offset": 0,
+ "slot": "214",
+ "type": "t_contract(IMToken)21422",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:113"
+ },
+ {
+ "label": "mTokenDataFeed",
+ "offset": 0,
+ "slot": "215",
+ "type": "t_contract(IDataFeed)20984",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:118"
+ },
+ {
+ "label": "tokensReceiver",
+ "offset": 0,
+ "slot": "216",
+ "type": "t_address",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:123"
+ },
+ {
+ "label": "variationTolerance",
+ "offset": 0,
+ "slot": "217",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:128"
+ },
+ {
+ "label": "minAmount",
+ "offset": 0,
+ "slot": "218",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:133"
+ },
+ {
+ "label": "instantFee",
+ "offset": 0,
+ "slot": "219",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:138"
+ },
+ {
+ "label": "minInstantFee",
+ "offset": 0,
+ "slot": "220",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:143"
+ },
+ {
+ "label": "maxInstantFee",
+ "offset": 0,
+ "slot": "221",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:148"
+ },
+ {
+ "label": "maxInstantShare",
+ "offset": 0,
+ "slot": "222",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:153"
+ },
+ {
+ "label": "sequentialRequestProcessing",
+ "offset": 0,
+ "slot": "223",
+ "type": "t_bool",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:158"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "224",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:163"
+ },
+ {
+ "label": "mintRequests",
+ "offset": 0,
+ "slot": "274",
+ "type": "t_mapping(t_uint256,t_struct(Request)21019_storage)",
+ "contract": "DepositVault",
+ "src": "contracts/DepositVault.sol:45"
+ },
+ {
+ "label": "totalMinted",
+ "offset": 0,
+ "slot": "275",
+ "type": "t_mapping(t_address,t_uint256)",
+ "contract": "DepositVault",
+ "src": "contracts/DepositVault.sol:51"
+ },
+ {
+ "label": "minMTokenAmountForFirstDeposit",
+ "offset": 0,
+ "slot": "276",
+ "type": "t_uint256",
+ "contract": "DepositVault",
+ "src": "contracts/DepositVault.sol:56"
+ },
+ {
+ "label": "maxSupplyCap",
+ "offset": 0,
+ "slot": "277",
+ "type": "t_uint256",
+ "contract": "DepositVault",
+ "src": "contracts/DepositVault.sol:63"
+ },
+ {
+ "label": "maxAmountPerRequest",
+ "offset": 0,
+ "slot": "278",
+ "type": "t_uint256",
+ "contract": "DepositVault",
+ "src": "contracts/DepositVault.sol:68"
+ },
+ {
+ "label": "upcomingSupply",
+ "offset": 0,
+ "slot": "279",
+ "type": "t_uint256",
+ "contract": "DepositVault",
+ "src": "contracts/DepositVault.sol:74"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "280",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "DepositVault",
+ "src": "contracts/DepositVault.sol:79"
+ }
+ ],
+ "types": {
+ "t_address": {
+ "label": "address",
+ "numberOfBytes": "20"
+ },
+ "t_array(t_bytes32)dyn_storage": {
+ "label": "bytes32[]",
+ "numberOfBytes": "32"
+ },
+ "t_array(t_uint256)50_storage": {
+ "label": "uint256[50]",
+ "numberOfBytes": "1600"
+ },
+ "t_bool": {
+ "label": "bool",
+ "numberOfBytes": "1"
+ },
+ "t_bytes32": {
+ "label": "bytes32",
+ "numberOfBytes": "32"
+ },
+ "t_contract(IDataFeed)20984": {
+ "label": "contract IDataFeed",
+ "numberOfBytes": "20"
+ },
+ "t_contract(IMToken)21422": {
+ "label": "contract IMToken",
+ "numberOfBytes": "20"
+ },
+ "t_contract(IMidasAccessControl)22201": {
+ "label": "contract IMidasAccessControl",
+ "numberOfBytes": "20"
+ },
+ "t_enum(RequestStatus)21446": {
+ "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)21442_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_uint256,t_struct(Request)21019_storage)": {
+ "label": "mapping(uint256 => struct Request)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)24044_storage)": {
+ "label": "mapping(uint256 => struct RateLimitLibrary.WindowRateLimitConfig)",
+ "numberOfBytes": "32"
+ },
+ "t_struct(AddressSet)5123_storage": {
+ "label": "struct EnumerableSetUpgradeable.AddressSet",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)4808_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(Request)21019_storage": {
+ "label": "struct Request",
+ "members": [
+ {
+ "label": "recipient",
+ "type": "t_address",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "tokenIn",
+ "type": "t_address",
+ "offset": 0,
+ "slot": "1"
+ },
+ {
+ "label": "status",
+ "type": "t_enum(RequestStatus)21446",
+ "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"
+ },
+ {
+ "label": "depositedInstantUsdAmount",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "5"
+ },
+ {
+ "label": "approvedTokenOutRate",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "6"
+ },
+ {
+ "label": "amountMToken",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "7"
+ }
+ ],
+ "numberOfBytes": "256"
+ },
+ "t_struct(Set)4808_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)21442_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_struct(UintSet)5280_storage": {
+ "label": "struct EnumerableSetUpgradeable.UintSet",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)4808_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(WindowRateLimitConfig)24044_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimitConfig",
+ "members": [
+ {
+ "label": "limit",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "amountInFlight",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "1"
+ },
+ {
+ "label": "lastUpdated",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "2"
+ },
+ {
+ "label": "window",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "3"
+ }
+ ],
+ "numberOfBytes": "128"
+ },
+ "t_struct(WindowRateLimits)24054_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimits",
+ "members": [
+ {
+ "label": "windows",
+ "type": "t_struct(UintSet)5280_storage",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "configs",
+ "type": "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)24044_storage)",
+ "offset": 0,
+ "slot": "2"
+ }
+ ],
+ "numberOfBytes": "96"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "d0484900fa3560b996ab526ce20fa265b205486f5439890aca0b25bff4bc628b": {
+ "address": "0x3f1201A73Bf3047930cDF1b860947c602ee55705",
+ "txHash": "0xfb06c0b753d959d7cc588ec52fdea76819e72f71ec8fb7a25b3ae685bd7a3ad5",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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(IMidasAccessControl)22201",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:29",
+ "retypedFrom": "MidasAccessControl"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "1",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:34"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "51",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "Blacklistable",
+ "src": "contracts/access/Blacklistable.sol:17"
+ },
+ {
+ "label": "greenlistEnabled",
+ "offset": 0,
+ "slot": "101",
+ "type": "t_bool",
+ "contract": "Greenlistable",
+ "src": "contracts/access/Greenlistable.sol:17"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "102",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "Greenlistable",
+ "src": "contracts/access/Greenlistable.sol:22"
+ },
+ {
+ "label": "sanctionsList",
+ "offset": 0,
+ "slot": "152",
+ "type": "t_address",
+ "contract": "WithSanctionsList",
+ "src": "contracts/abstract/WithSanctionsList.sol:17"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "153",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithSanctionsList",
+ "src": "contracts/abstract/WithSanctionsList.sol:22"
+ },
+ {
+ "label": "tokensConfig",
+ "offset": 0,
+ "slot": "203",
+ "type": "t_mapping(t_address,t_struct(TokenConfig)21442_storage)",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:73"
+ },
+ {
+ "label": "isFreeFromMinAmount",
+ "offset": 0,
+ "slot": "204",
+ "type": "t_mapping(t_address,t_bool)",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:78"
+ },
+ {
+ "label": "waivedFeeRestriction",
+ "offset": 0,
+ "slot": "205",
+ "type": "t_mapping(t_address,t_bool)",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:83"
+ },
+ {
+ "label": "_paymentTokens",
+ "offset": 0,
+ "slot": "206",
+ "type": "t_struct(AddressSet)5123_storage",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:88"
+ },
+ {
+ "label": "_instantRateLimits",
+ "offset": 0,
+ "slot": "208",
+ "type": "t_struct(WindowRateLimits)24054_storage",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:93"
+ },
+ {
+ "label": "currentRequestId",
+ "offset": 0,
+ "slot": "211",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:98"
+ },
+ {
+ "label": "nextExpectedRequestIdToProcess",
+ "offset": 0,
+ "slot": "212",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:103"
+ },
+ {
+ "label": "maxApproveRequestId",
+ "offset": 0,
+ "slot": "213",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:108"
+ },
+ {
+ "label": "mToken",
+ "offset": 0,
+ "slot": "214",
+ "type": "t_contract(IMToken)21422",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:113"
+ },
+ {
+ "label": "mTokenDataFeed",
+ "offset": 0,
+ "slot": "215",
+ "type": "t_contract(IDataFeed)20984",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:118"
+ },
+ {
+ "label": "tokensReceiver",
+ "offset": 0,
+ "slot": "216",
+ "type": "t_address",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:123"
+ },
+ {
+ "label": "variationTolerance",
+ "offset": 0,
+ "slot": "217",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:128"
+ },
+ {
+ "label": "minAmount",
+ "offset": 0,
+ "slot": "218",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:133"
+ },
+ {
+ "label": "instantFee",
+ "offset": 0,
+ "slot": "219",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:138"
+ },
+ {
+ "label": "minInstantFee",
+ "offset": 0,
+ "slot": "220",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:143"
+ },
+ {
+ "label": "maxInstantFee",
+ "offset": 0,
+ "slot": "221",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:148"
+ },
+ {
+ "label": "maxInstantShare",
+ "offset": 0,
+ "slot": "222",
+ "type": "t_uint256",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:153"
+ },
+ {
+ "label": "sequentialRequestProcessing",
+ "offset": 0,
+ "slot": "223",
+ "type": "t_bool",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:158"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "224",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "ManageableVault",
+ "src": "contracts/abstract/ManageableVault.sol:163"
+ },
+ {
+ "label": "redeemRequests",
+ "offset": 0,
+ "slot": "274",
+ "type": "t_mapping(t_uint256,t_struct(Request)22776_storage)",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:45"
+ },
+ {
+ "label": "loanRequests",
+ "offset": 0,
+ "slot": "275",
+ "type": "t_mapping(t_uint256,t_struct(LiquidityProviderLoanRequest)22811_storage)",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:50"
+ },
+ {
+ "label": "requestRedeemer",
+ "offset": 0,
+ "slot": "276",
+ "type": "t_address",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:55"
+ },
+ {
+ "label": "loanLp",
+ "offset": 0,
+ "slot": "277",
+ "type": "t_address",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:60"
+ },
+ {
+ "label": "loanRepaymentAddress",
+ "offset": 0,
+ "slot": "278",
+ "type": "t_address",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:65"
+ },
+ {
+ "label": "loanApr",
+ "offset": 0,
+ "slot": "279",
+ "type": "t_uint256",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:70"
+ },
+ {
+ "label": "preferLoanLiquidity",
+ "offset": 0,
+ "slot": "280",
+ "type": "t_bool",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:75"
+ },
+ {
+ "label": "currentLoanRequestId",
+ "offset": 0,
+ "slot": "281",
+ "type": "t_uint256",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:80"
+ },
+ {
+ "label": "loanSwapperVault",
+ "offset": 0,
+ "slot": "282",
+ "type": "t_contract(IRedemptionVault)23098",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:85"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "283",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "RedemptionVault",
+ "src": "contracts/RedemptionVault.sol:90"
+ }
+ ],
+ "types": {
+ "t_address": {
+ "label": "address",
+ "numberOfBytes": "20"
+ },
+ "t_array(t_bytes32)dyn_storage": {
+ "label": "bytes32[]",
+ "numberOfBytes": "32"
+ },
+ "t_array(t_uint256)50_storage": {
+ "label": "uint256[50]",
+ "numberOfBytes": "1600"
+ },
+ "t_bool": {
+ "label": "bool",
+ "numberOfBytes": "1"
+ },
+ "t_bytes32": {
+ "label": "bytes32",
+ "numberOfBytes": "32"
+ },
+ "t_contract(IDataFeed)20984": {
+ "label": "contract IDataFeed",
+ "numberOfBytes": "20"
+ },
+ "t_contract(IMToken)21422": {
+ "label": "contract IMToken",
+ "numberOfBytes": "20"
+ },
+ "t_contract(IMidasAccessControl)22201": {
+ "label": "contract IMidasAccessControl",
+ "numberOfBytes": "20"
+ },
+ "t_contract(IRedemptionVault)23098": {
+ "label": "contract IRedemptionVault",
+ "numberOfBytes": "20"
+ },
+ "t_enum(RequestStatus)21446": {
+ "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)21442_storage)": {
+ "label": "mapping(address => struct TokenConfig)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_uint256)": {
+ "label": "mapping(bytes32 => uint256)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint256,t_struct(LiquidityProviderLoanRequest)22811_storage)": {
+ "label": "mapping(uint256 => struct LiquidityProviderLoanRequest)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint256,t_struct(Request)22776_storage)": {
+ "label": "mapping(uint256 => struct Request)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)24044_storage)": {
+ "label": "mapping(uint256 => struct RateLimitLibrary.WindowRateLimitConfig)",
+ "numberOfBytes": "32"
+ },
+ "t_struct(AddressSet)5123_storage": {
+ "label": "struct EnumerableSetUpgradeable.AddressSet",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)4808_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(LiquidityProviderLoanRequest)22811_storage": {
+ "label": "struct LiquidityProviderLoanRequest",
+ "members": [
+ {
+ "label": "tokenOut",
+ "type": "t_address",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "amountTokenOut",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "1"
+ },
+ {
+ "label": "amountFee",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "2"
+ },
+ {
+ "label": "createdAt",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "3"
+ },
+ {
+ "label": "status",
+ "type": "t_enum(RequestStatus)21446",
+ "offset": 0,
+ "slot": "4"
+ }
+ ],
+ "numberOfBytes": "160"
+ },
+ "t_struct(Request)22776_storage": {
+ "label": "struct Request",
+ "members": [
+ {
+ "label": "recipient",
+ "type": "t_address",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "tokenOut",
+ "type": "t_address",
+ "offset": 0,
+ "slot": "1"
+ },
+ {
+ "label": "status",
+ "type": "t_enum(RequestStatus)21446",
+ "offset": 20,
+ "slot": "1"
+ },
+ {
+ "label": "feePercent",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "2"
+ },
+ {
+ "label": "amountMToken",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "3"
+ },
+ {
+ "label": "mTokenRate",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "4"
+ },
+ {
+ "label": "tokenOutRate",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "5"
+ },
+ {
+ "label": "amountMTokenInstant",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "6"
+ },
+ {
+ "label": "approvedMTokenRate",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "7"
+ },
+ {
+ "label": "amountTokenOut",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "8"
+ }
+ ],
+ "numberOfBytes": "288"
+ },
+ "t_struct(Set)4808_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)21442_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_struct(UintSet)5280_storage": {
+ "label": "struct EnumerableSetUpgradeable.UintSet",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)4808_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(WindowRateLimitConfig)24044_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimitConfig",
+ "members": [
+ {
+ "label": "limit",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "amountInFlight",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "1"
+ },
+ {
+ "label": "lastUpdated",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "2"
+ },
+ {
+ "label": "window",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "3"
+ }
+ ],
+ "numberOfBytes": "128"
+ },
+ "t_struct(WindowRateLimits)24054_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimits",
+ "members": [
+ {
+ "label": "windows",
+ "type": "t_struct(UintSet)5280_storage",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "configs",
+ "type": "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)24044_storage)",
+ "offset": 0,
+ "slot": "2"
+ }
+ ],
+ "numberOfBytes": "96"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "f2d5c2fcac186eea5c0169b2c036d6057ab313128c7c2903bd75a838207db519": {
+ "address": "0xE8162806b02D77ca71F6f2b82362913f6B1fDAC0",
+ "txHash": "0xa88c55219a756d6c0e2792201734dbbc7c177515becffb867402ac4053a3d1ca",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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(IMidasAccessControl)22226",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:29",
+ "retypedFrom": "MidasAccessControl"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "1",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:34"
+ },
+ {
+ "label": "_status",
+ "offset": 0,
+ "slot": "51",
+ "type": "t_uint256",
+ "contract": "ReentrancyGuardUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:38"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "52",
+ "type": "t_array(t_uint256)49_storage",
+ "contract": "ReentrancyGuardUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:88"
+ },
+ {
+ "label": "dataHashIndexes",
+ "offset": 0,
+ "slot": "101",
+ "type": "t_mapping(t_bytes32,t_uint256)",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:84"
+ },
+ {
+ "label": "proposerPendingOperationsCount",
+ "offset": 0,
+ "slot": "102",
+ "type": "t_mapping(t_address,t_uint256)",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:89"
+ },
+ {
+ "label": "_securityCouncils",
+ "offset": 0,
+ "slot": "103",
+ "type": "t_mapping(t_uint256,t_struct(AddressSet)5123_storage)",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:94"
+ },
+ {
+ "label": "_operationDetails",
+ "offset": 0,
+ "slot": "104",
+ "type": "t_mapping(t_bytes32,t_struct(TimelockOperationDetails)17044_storage)",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:99"
+ },
+ {
+ "label": "_pendingOperations",
+ "offset": 0,
+ "slot": "105",
+ "type": "t_struct(Bytes32Set)5002_storage",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:104"
+ },
+ {
+ "label": "timelock",
+ "offset": 0,
+ "slot": "107",
+ "type": "t_address",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:109"
+ },
+ {
+ "label": "maxPendingOperationsPerProposer",
+ "offset": 0,
+ "slot": "108",
+ "type": "t_uint256",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:114"
+ },
+ {
+ "label": "securityCouncilVersion",
+ "offset": 0,
+ "slot": "109",
+ "type": "t_uint256",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:119"
+ },
+ {
+ "label": "pendingSetCouncilOperationId",
+ "offset": 0,
+ "slot": "110",
+ "type": "t_bytes32",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:124"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "111",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "MidasTimelockManager",
+ "src": "contracts/access/MidasTimelockManager.sol:129"
+ }
+ ],
+ "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_contract(IMidasAccessControl)22226": {
+ "label": "contract IMidasAccessControl",
+ "numberOfBytes": "20"
+ },
+ "t_enum(TimelockOperationStatus)22389": {
+ "label": "enum TimelockOperationStatus",
+ "members": [
+ "NotExist",
+ "Pending",
+ "Paused",
+ "ApprovedExecution",
+ "ReadyToExecute",
+ "ReadyToAbort",
+ "Expired",
+ "Aborted",
+ "Executed",
+ "ExecutedWithFailure"
+ ],
+ "numberOfBytes": "1"
+ },
+ "t_mapping(t_address,t_uint256)": {
+ "label": "mapping(address => uint256)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_struct(TimelockOperationDetails)17044_storage)": {
+ "label": "mapping(bytes32 => struct MidasTimelockManager.TimelockOperationDetails)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_uint256)": {
+ "label": "mapping(bytes32 => uint256)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint256,t_struct(AddressSet)5123_storage)": {
+ "label": "mapping(uint256 => struct EnumerableSetUpgradeable.AddressSet)",
+ "numberOfBytes": "32"
+ },
+ "t_struct(AddressSet)5123_storage": {
+ "label": "struct EnumerableSetUpgradeable.AddressSet",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)4808_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(Bytes32Set)5002_storage": {
+ "label": "struct EnumerableSetUpgradeable.Bytes32Set",
+ "members": [
+ {
+ "label": "_inner",
+ "type": "t_struct(Set)4808_storage",
+ "offset": 0,
+ "slot": "0"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_struct(Set)4808_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(TimelockOperationDetails)17044_storage": {
+ "label": "struct MidasTimelockManager.TimelockOperationDetails",
+ "members": [
+ {
+ "label": "votersForExecution",
+ "type": "t_struct(AddressSet)5123_storage",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "votersForVeto",
+ "type": "t_struct(AddressSet)5123_storage",
+ "offset": 0,
+ "slot": "2"
+ },
+ {
+ "label": "councilVersion",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "4"
+ },
+ {
+ "label": "dataHash",
+ "type": "t_bytes32",
+ "offset": 0,
+ "slot": "5"
+ },
+ {
+ "label": "status",
+ "type": "t_enum(TimelockOperationStatus)22389",
+ "offset": 0,
+ "slot": "6"
+ },
+ {
+ "label": "pauseReasonCode",
+ "type": "t_uint8",
+ "offset": 1,
+ "slot": "6"
+ },
+ {
+ "label": "isSetCouncilOperation",
+ "type": "t_bool",
+ "offset": 2,
+ "slot": "6"
+ },
+ {
+ "label": "createdAt",
+ "type": "t_uint32",
+ "offset": 3,
+ "slot": "6"
+ },
+ {
+ "label": "executionApprovedAt",
+ "type": "t_uint32",
+ "offset": 7,
+ "slot": "6"
+ },
+ {
+ "label": "operationProposer",
+ "type": "t_address",
+ "offset": 11,
+ "slot": "6"
+ },
+ {
+ "label": "pauser",
+ "type": "t_address",
+ "offset": 0,
+ "slot": "7"
+ }
+ ],
+ "numberOfBytes": "256"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint32": {
+ "label": "uint32",
+ "numberOfBytes": "4"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "51bc7672fb49fea45c922ccce0de6669b9816ab6bc7f0c9b8286ae5d8bb5fbf4": {
+ "address": "0x6C498e5BF9085995eD195985c8c60257e3579d2e",
+ "txHash": "0x29f598712afad439b1146c9b3d67eed885f84512b9ecb7a9353be7b4f2930238",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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": "__gap",
+ "offset": 0,
+ "slot": "51",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "ERC165Upgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol:41"
+ },
+ {
+ "label": "_roles",
+ "offset": 0,
+ "slot": "101",
+ "type": "t_mapping(t_bytes32,t_struct(RoleData)9957_storage)",
+ "contract": "AccessControlUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:62"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "102",
+ "type": "t_array(t_uint256)49_storage",
+ "contract": "AccessControlUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:260"
},
- "t_contract(IMToken)20077": {
- "label": "contract IMToken",
+ {
+ "label": "isUserFacingRole",
+ "offset": 0,
+ "slot": "151",
+ "type": "t_mapping(t_bytes32,t_bool)",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:39"
+ },
+ {
+ "label": "_grantOperatorRoles",
+ "offset": 0,
+ "slot": "152",
+ "type": "t_mapping(t_bytes32,t_mapping(t_address,t_bool))",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:44"
+ },
+ {
+ "label": "_permissionRoles",
+ "offset": 0,
+ "slot": "153",
+ "type": "t_mapping(t_bytes32,t_mapping(t_address,t_bool))",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:49"
+ },
+ {
+ "label": "_roleTimelockDelays",
+ "offset": 0,
+ "slot": "154",
+ "type": "t_mapping(t_bytes32,t_uint32)",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:54"
+ },
+ {
+ "label": "timelockManager",
+ "offset": 0,
+ "slot": "155",
+ "type": "t_address",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:59"
+ },
+ {
+ "label": "pauseManager",
+ "offset": 0,
+ "slot": "156",
+ "type": "t_address",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:64"
+ },
+ {
+ "label": "defaultDelay",
+ "offset": 20,
+ "slot": "156",
+ "type": "t_uint32",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:69"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "157",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:74"
+ }
+ ],
+ "types": {
+ "t_address": {
+ "label": "address",
"numberOfBytes": "20"
},
- "t_contract(MidasAccessControl)17280": {
- "label": "contract MidasAccessControl",
+ "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_mapping(t_address,t_bool)": {
+ "label": "mapping(address => bool)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_bool)": {
+ "label": "mapping(bytes32 => bool)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_mapping(t_address,t_bool))": {
+ "label": "mapping(bytes32 => mapping(address => bool))",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_struct(RoleData)9957_storage)": {
+ "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_uint32)": {
+ "label": "mapping(bytes32 => uint32)",
+ "numberOfBytes": "32"
+ },
+ "t_struct(RoleData)9957_storage": {
+ "label": "struct AccessControlUpgradeable.RoleData",
+ "members": [
+ {
+ "label": "members",
+ "type": "t_mapping(t_address,t_bool)",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "adminRole",
+ "type": "t_bytes32",
+ "offset": 0,
+ "slot": "1"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint32": {
+ "label": "uint32",
+ "numberOfBytes": "4"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "37ce19d7cbf41d5ce92a73bc58153268576e9e7cb9c1fcbdce23879b02b878db": {
+ "address": "0xa45a5279c424aea828f6eA583aB1cE445C0B75Fc",
+ "txHash": "0x1b5a09de05a05ff3eb905a98c21f25d81d063e883a896ee727e3e8c14f94cea7",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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": "__gap",
+ "offset": 0,
+ "slot": "51",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "ERC165Upgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol:41"
+ },
+ {
+ "label": "_roles",
+ "offset": 0,
+ "slot": "101",
+ "type": "t_mapping(t_bytes32,t_struct(RoleData)80_storage)",
+ "contract": "AccessControlUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:62"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "102",
+ "type": "t_array(t_uint256)49_storage",
+ "contract": "AccessControlUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:260"
+ },
+ {
+ "label": "isUserFacingRole",
+ "offset": 0,
+ "slot": "151",
+ "type": "t_mapping(t_bytes32,t_bool)",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:39"
+ },
+ {
+ "label": "_grantOperatorRoles",
+ "offset": 0,
+ "slot": "152",
+ "type": "t_mapping(t_bytes32,t_mapping(t_address,t_bool))",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:44"
+ },
+ {
+ "label": "_permissionRoles",
+ "offset": 0,
+ "slot": "153",
+ "type": "t_mapping(t_bytes32,t_mapping(t_address,t_bool))",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:49"
+ },
+ {
+ "label": "_roleTimelockDelays",
+ "offset": 0,
+ "slot": "154",
+ "type": "t_mapping(t_bytes32,t_uint32)",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:54"
+ },
+ {
+ "label": "timelockManager",
+ "offset": 0,
+ "slot": "155",
+ "type": "t_address",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:59"
+ },
+ {
+ "label": "pauseManager",
+ "offset": 0,
+ "slot": "156",
+ "type": "t_address",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:64"
+ },
+ {
+ "label": "defaultDelay",
+ "offset": 20,
+ "slot": "156",
+ "type": "t_uint32",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:69"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "157",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "MidasAccessControl",
+ "src": "contracts/access/MidasAccessControl.sol:74"
+ }
+ ],
+ "types": {
+ "t_address": {
+ "label": "address",
"numberOfBytes": "20"
},
- "t_enum(RequestStatus)20094": {
- "label": "enum RequestStatus",
- "members": ["Pending", "Processed", "Canceled"],
+ "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_mapping(t_address,t_bool)": {
"label": "mapping(address => bool)",
"numberOfBytes": "32"
},
- "t_mapping(t_address,t_struct(TokenConfig)20090_storage)": {
- "label": "mapping(address => struct TokenConfig)",
+ "t_mapping(t_bytes32,t_bool)": {
+ "label": "mapping(bytes32 => bool)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_mapping(t_address,t_bool))": {
+ "label": "mapping(bytes32 => mapping(address => bool))",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_struct(RoleData)80_storage)": {
+ "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_uint32)": {
+ "label": "mapping(bytes32 => uint32)",
+ "numberOfBytes": "32"
+ },
+ "t_struct(RoleData)80_storage": {
+ "label": "struct AccessControlUpgradeable.RoleData",
+ "members": [
+ {
+ "label": "members",
+ "type": "t_mapping(t_address,t_bool)",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "adminRole",
+ "type": "t_bytes32",
+ "offset": 0,
+ "slot": "1"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint32": {
+ "label": "uint32",
+ "numberOfBytes": "4"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ }
+ }
+ },
+ "2329e0ac3748a627bc96db790363607f4bd15b6f27ec16df977ab31de92a946f": {
+ "address": "0x6B2F768cA9195937c6A793132acba74837939B42",
+ "txHash": "0x5dd53b518344f42d6ced13bd3b0e6b314900016a12521e91df5054fa4c73bd07",
+ "layout": {
+ "solcVersion": "0.8.34",
+ "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(IMidasAccessControl)37428",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:29",
+ "retypedFrom": "MidasAccessControl"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "202",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:34"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "252",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "Blacklistable",
+ "src": "contracts/access/Blacklistable.sol:17"
+ },
+ {
+ "label": "metadata",
+ "offset": 0,
+ "slot": "302",
+ "type": "t_mapping(t_bytes32,t_bytes_storage)",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:62"
+ },
+ {
+ "label": "_mintRateLimits",
+ "offset": 0,
+ "slot": "303",
+ "type": "t_struct(WindowRateLimits)39503_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:67"
+ },
+ {
+ "label": "clawbackReceiver",
+ "offset": 0,
+ "slot": "306",
+ "type": "t_address",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:72"
+ },
+ {
+ "label": "_inClawback",
+ "offset": 20,
+ "slot": "306",
+ "type": "t_bool",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:77"
+ },
+ {
+ "label": "_name",
+ "offset": 0,
+ "slot": "307",
+ "type": "t_string_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:82"
+ },
+ {
+ "label": "_symbol",
+ "offset": 0,
+ "slot": "308",
+ "type": "t_string_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:87"
+ },
+ {
+ "label": "isPermissioned",
+ "offset": 0,
+ "slot": "309",
+ "type": "t_bool",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:92"
+ },
+ {
+ "label": "isMinHoldingBalanceEnforced",
+ "offset": 1,
+ "slot": "309",
+ "type": "t_bool",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:97"
+ },
+ {
+ "label": "maxSupplyCap",
+ "offset": 0,
+ "slot": "310",
+ "type": "t_uint256",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:102"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "311",
+ "type": "t_array(t_uint256)42_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:107"
+ },
+ {
+ "label": "___gap",
+ "offset": 0,
+ "slot": "353",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:112"
+ },
+ {
+ "label": "____gap",
+ "offset": 0,
+ "slot": "403",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:117"
+ }
+ ],
+ "types": {
+ "t_address": {
+ "label": "address",
+ "numberOfBytes": "20"
+ },
+ "t_array(t_bytes32)dyn_storage": {
+ "label": "bytes32[]",
+ "numberOfBytes": "32"
+ },
+ "t_array(t_uint256)42_storage": {
+ "label": "uint256[42]",
+ "numberOfBytes": "1344"
+ },
+ "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(IMidasAccessControl)37428": {
+ "label": "contract IMidasAccessControl",
+ "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_uint256)": {
- "label": "mapping(bytes32 => uint256)",
+ "t_mapping(t_bytes32,t_bytes_storage)": {
+ "label": "mapping(bytes32 => bytes)",
"numberOfBytes": "32"
},
- "t_mapping(t_bytes4,t_bool)": {
- "label": "mapping(bytes4 => bool)",
+ "t_mapping(t_bytes32,t_uint256)": {
+ "label": "mapping(bytes32 => uint256)",
"numberOfBytes": "32"
},
- "t_mapping(t_uint256,t_struct(Request)19809_storage)": {
- "label": "mapping(uint256 => struct Request)",
+ "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)39493_storage)": {
+ "label": "mapping(uint256 => struct RateLimitLibrary.WindowRateLimitConfig)",
"numberOfBytes": "32"
},
- "t_mapping(t_uint256,t_uint256)": {
- "label": "mapping(uint256 => uint256)",
+ "t_string_storage": {
+ "label": "string",
"numberOfBytes": "32"
- },
- "t_struct(AddressSet)3891_storage": {
- "label": "struct EnumerableSetUpgradeable.AddressSet",
+ },
+ "t_struct(Set)14685_storage": {
+ "label": "struct EnumerableSetUpgradeable.Set",
"members": [
{
- "label": "_inner",
- "type": "t_struct(Set)3576_storage",
+ "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(Counter)8079_storage": {
- "label": "struct Counters.Counter",
+ "t_struct(UintSet)15157_storage": {
+ "label": "struct EnumerableSetUpgradeable.UintSet",
"members": [
{
- "label": "_value",
- "type": "t_uint256",
+ "label": "_inner",
+ "type": "t_struct(Set)14685_storage",
"offset": 0,
"slot": "0"
}
],
- "numberOfBytes": "32"
+ "numberOfBytes": "64"
},
- "t_struct(Request)19809_storage": {
- "label": "struct Request",
+ "t_struct(WindowRateLimitConfig)39493_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimitConfig",
"members": [
{
- "label": "sender",
- "type": "t_address",
+ "label": "limit",
+ "type": "t_uint256",
"offset": 0,
"slot": "0"
},
{
- "label": "tokenIn",
- "type": "t_address",
+ "label": "amountInFlight",
+ "type": "t_uint256",
"offset": 0,
"slot": "1"
},
{
- "label": "status",
- "type": "t_enum(RequestStatus)20094",
- "offset": 20,
- "slot": "1"
- },
- {
- "label": "depositedUsdAmount",
+ "label": "lastUpdated",
"type": "t_uint256",
"offset": 0,
"slot": "2"
},
{
- "label": "usdAmountWithoutFees",
+ "label": "window",
"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"
+ "numberOfBytes": "128"
},
- "t_struct(TokenConfig)20090_storage": {
- "label": "struct TokenConfig",
+ "t_struct(WindowRateLimits)39503_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimits",
"members": [
{
- "label": "dataFeed",
- "type": "t_address",
+ "label": "windows",
+ "type": "t_struct(UintSet)15157_storage",
"offset": 0,
"slot": "0"
},
{
- "label": "fee",
- "type": "t_uint256",
- "offset": 0,
- "slot": "1"
- },
- {
- "label": "allowance",
- "type": "t_uint256",
+ "label": "configs",
+ "type": "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)39493_storage)",
"offset": 0,
"slot": "2"
- },
- {
- "label": "stable",
- "type": "t_bool",
- "offset": 0,
- "slot": "3"
}
],
- "numberOfBytes": "128"
+ "numberOfBytes": "96"
},
"t_uint256": {
"label": "uint256",
@@ -46985,11 +58471,11 @@
}
}
},
- "32d2bfbec0f244f9923d9e56192802e03bc377df02c1c4867d8d438b7af698a8": {
- "address": "0x71572E94b80d68600979B1d874189da76E521a80",
- "txHash": "0x3928b4266baeb1601f122e8138d1ed7f9ac8328c86dbc7944f2bc16cce111018",
+ "7d462e30d2b1053bf823e6e0fb7220198c4d81b9193f2cc0337a7231ec5de0c5": {
+ "address": "0x84FE160268ec2CB371D21452DE6E98363A5fa21D",
+ "txHash": "0x3a248e7b83c8b9e27e3938a2b0c9174903e475d0a1ce42eb11a6cfcf7be28bea",
"layout": {
- "solcVersion": "0.8.9",
+ "solcVersion": "0.8.34",
"storage": [
{
"label": "_initialized",
@@ -47008,309 +58494,206 @@
"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"
+ "contract": "ContextUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36"
},
{
- "label": "__gap",
+ "label": "_balances",
"offset": 0,
"slot": "51",
- "type": "t_array(t_uint256)50_storage",
- "contract": "ContextUpgradeable",
- "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36"
+ "type": "t_mapping(t_address,t_uint256)",
+ "contract": "ERC20Upgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:40"
},
{
- "label": "_paused",
+ "label": "_allowances",
"offset": 0,
- "slot": "101",
- "type": "t_bool",
- "contract": "PausableUpgradeable",
- "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:29"
+ "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": "__gap",
+ "label": "_totalSupply",
"offset": 0,
- "slot": "102",
- "type": "t_array(t_uint256)49_storage",
- "contract": "PausableUpgradeable",
- "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:116"
+ "slot": "53",
+ "type": "t_uint256",
+ "contract": "ERC20Upgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:44"
},
{
- "label": "fnPaused",
+ "label": "_name",
"offset": 0,
- "slot": "151",
- "type": "t_mapping(t_bytes4,t_bool)",
- "contract": "Pausable",
- "src": "contracts/access/Pausable.sol:14"
+ "slot": "54",
+ "type": "t_string_storage",
+ "contract": "ERC20Upgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:46"
},
{
- "label": "__gap",
+ "label": "_symbol",
"offset": 0,
- "slot": "152",
- "type": "t_array(t_uint256)50_storage",
- "contract": "Pausable",
- "src": "contracts/access/Pausable.sol:19"
+ "slot": "55",
+ "type": "t_string_storage",
+ "contract": "ERC20Upgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:47"
},
{
"label": "__gap",
"offset": 0,
- "slot": "202",
- "type": "t_array(t_uint256)50_storage",
- "contract": "Blacklistable",
- "src": "contracts/access/Blacklistable.sol:16"
+ "slot": "56",
+ "type": "t_array(t_uint256)45_storage",
+ "contract": "ERC20Upgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:376"
},
{
- "label": "greenlistEnabled",
+ "label": "_paused",
"offset": 0,
- "slot": "252",
+ "slot": "101",
"type": "t_bool",
- "contract": "Greenlistable",
- "src": "contracts/access/Greenlistable.sol:16"
+ "contract": "PausableUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:29"
},
{
"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"
+ "slot": "102",
+ "type": "t_array(t_uint256)49_storage",
+ "contract": "PausableUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:116"
},
{
"label": "__gap",
"offset": 0,
- "slot": "304",
+ "slot": "151",
"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)20077",
- "contract": "ManageableVault",
- "src": "contracts/abstract/ManageableVault.sol:66"
+ "contract": "ERC20PausableUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PausableUpgradeable.sol:48"
},
{
- "label": "mTokenDataFeed",
+ "label": "accessControl",
"offset": 0,
- "slot": "356",
- "type": "t_contract(IDataFeed)19792",
- "contract": "ManageableVault",
- "src": "contracts/abstract/ManageableVault.sol:71"
+ "slot": "201",
+ "type": "t_contract(IMidasAccessControl)37428",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:29",
+ "retypedFrom": "MidasAccessControl"
},
{
- "label": "tokensReceiver",
+ "label": "__gap",
"offset": 0,
- "slot": "357",
- "type": "t_address",
- "contract": "ManageableVault",
- "src": "contracts/abstract/ManageableVault.sol:76"
+ "slot": "202",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "WithMidasAccessControl",
+ "src": "contracts/access/WithMidasAccessControl.sol:34"
},
{
- "label": "instantFee",
+ "label": "__gap",
"offset": 0,
- "slot": "358",
- "type": "t_uint256",
- "contract": "ManageableVault",
- "src": "contracts/abstract/ManageableVault.sol:81"
+ "slot": "252",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "Blacklistable",
+ "src": "contracts/access/Blacklistable.sol:17"
},
{
- "label": "instantDailyLimit",
+ "label": "metadata",
"offset": 0,
- "slot": "359",
- "type": "t_uint256",
- "contract": "ManageableVault",
- "src": "contracts/abstract/ManageableVault.sol:88"
+ "slot": "302",
+ "type": "t_mapping(t_bytes32,t_bytes_storage)",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:62"
},
{
- "label": "dailyLimits",
+ "label": "_mintRateLimits",
"offset": 0,
- "slot": "360",
- "type": "t_mapping(t_uint256,t_uint256)",
- "contract": "ManageableVault",
- "src": "contracts/abstract/ManageableVault.sol:93"
+ "slot": "303",
+ "type": "t_struct(WindowRateLimits)39503_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:67"
},
{
- "label": "feeReceiver",
+ "label": "clawbackReceiver",
"offset": 0,
- "slot": "361",
+ "slot": "306",
"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)20090_storage)",
- "contract": "ManageableVault",
- "src": "contracts/abstract/ManageableVault.sol:118"
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:72"
},
{
- "label": "minAmount",
- "offset": 0,
- "slot": "367",
- "type": "t_uint256",
- "contract": "ManageableVault",
- "src": "contracts/abstract/ManageableVault.sol:123"
+ "label": "_inClawback",
+ "offset": 20,
+ "slot": "306",
+ "type": "t_bool",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:77"
},
{
- "label": "isFreeFromMinAmount",
+ "label": "_name",
"offset": 0,
- "slot": "368",
- "type": "t_mapping(t_address,t_bool)",
- "contract": "ManageableVault",
- "src": "contracts/abstract/ManageableVault.sol:128"
+ "slot": "307",
+ "type": "t_string_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:82"
},
{
- "label": "__gap",
+ "label": "_symbol",
"offset": 0,
- "slot": "369",
- "type": "t_array(t_uint256)50_storage",
- "contract": "ManageableVault",
- "src": "contracts/abstract/ManageableVault.sol:133"
+ "slot": "308",
+ "type": "t_string_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:87"
},
{
- "label": "minFiatRedeemAmount",
+ "label": "isPermissioned",
"offset": 0,
- "slot": "419",
- "type": "t_uint256",
- "contract": "RedemptionVault",
- "src": "contracts/RedemptionVault.sol:69"
+ "slot": "309",
+ "type": "t_bool",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:92"
},
{
- "label": "fiatAdditionalFee",
- "offset": 0,
- "slot": "420",
- "type": "t_uint256",
- "contract": "RedemptionVault",
- "src": "contracts/RedemptionVault.sol:74"
+ "label": "isMinHoldingBalanceEnforced",
+ "offset": 1,
+ "slot": "309",
+ "type": "t_bool",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:97"
},
{
- "label": "fiatFlatFee",
+ "label": "maxSupplyCap",
"offset": 0,
- "slot": "421",
+ "slot": "310",
"type": "t_uint256",
- "contract": "RedemptionVault",
- "src": "contracts/RedemptionVault.sol:79"
- },
- {
- "label": "redeemRequests",
- "offset": 0,
- "slot": "422",
- "type": "t_mapping(t_uint256,t_struct(Request)20358_storage)",
- "contract": "RedemptionVault",
- "src": "contracts/RedemptionVault.sol:84"
- },
- {
- "label": "requestRedeemer",
- "offset": 0,
- "slot": "423",
- "type": "t_address",
- "contract": "RedemptionVault",
- "src": "contracts/RedemptionVault.sol:89"
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:102"
},
{
"label": "__gap",
"offset": 0,
- "slot": "424",
- "type": "t_array(t_uint256)50_storage",
- "contract": "RedemptionVault",
- "src": "contracts/RedemptionVault.sol:94"
+ "slot": "311",
+ "type": "t_array(t_uint256)42_storage",
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:107"
},
{
"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)20595",
- "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",
+ "slot": "353",
"type": "t_array(t_uint256)50_storage",
- "contract": "RedemptionVaultWithSwapper",
- "src": "contracts/RedemptionVaultWithSwapper.sol:48"
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:112"
},
{
- "label": "__gap",
+ "label": "____gap",
"offset": 0,
- "slot": "576",
+ "slot": "403",
"type": "t_array(t_uint256)50_storage",
- "contract": "MFOneRedemptionVaultWithSwapper",
- "src": "contracts/products/mFONE/MFOneRedemptionVaultWithSwapper.sol:19"
+ "contract": "mToken",
+ "src": "contracts/mToken.sol:117"
}
],
"types": {
@@ -47322,6 +58705,14 @@
"label": "bytes32[]",
"numberOfBytes": "32"
},
+ "t_array(t_uint256)42_storage": {
+ "label": "uint256[42]",
+ "numberOfBytes": "1344"
+ },
+ "t_array(t_uint256)45_storage": {
+ "label": "uint256[45]",
+ "numberOfBytes": "1440"
+ },
"t_array(t_uint256)49_storage": {
"label": "uint256[49]",
"numberOfBytes": "1568"
@@ -47338,168 +58729,115 @@
"label": "bytes32",
"numberOfBytes": "32"
},
- "t_bytes4": {
- "label": "bytes4",
- "numberOfBytes": "4"
- },
- "t_contract(IDataFeed)19792": {
- "label": "contract IDataFeed",
- "numberOfBytes": "20"
- },
- "t_contract(IMToken)20077": {
- "label": "contract IMToken",
- "numberOfBytes": "20"
- },
- "t_contract(IRedemptionVault)20595": {
- "label": "contract IRedemptionVault",
- "numberOfBytes": "20"
+ "t_bytes_storage": {
+ "label": "bytes",
+ "numberOfBytes": "32"
},
- "t_contract(MidasAccessControl)17280": {
- "label": "contract MidasAccessControl",
+ "t_contract(IMidasAccessControl)37428": {
+ "label": "contract IMidasAccessControl",
"numberOfBytes": "20"
},
- "t_enum(RequestStatus)20094": {
- "label": "enum RequestStatus",
- "members": ["Pending", "Processed", "Canceled"],
- "numberOfBytes": "1"
+ "t_mapping(t_address,t_mapping(t_address,t_uint256))": {
+ "label": "mapping(address => mapping(address => uint256))",
+ "numberOfBytes": "32"
},
- "t_mapping(t_address,t_bool)": {
- "label": "mapping(address => bool)",
+ "t_mapping(t_address,t_uint256)": {
+ "label": "mapping(address => uint256)",
"numberOfBytes": "32"
},
- "t_mapping(t_address,t_struct(TokenConfig)20090_storage)": {
- "label": "mapping(address => struct TokenConfig)",
+ "t_mapping(t_bytes32,t_bytes_storage)": {
+ "label": "mapping(bytes32 => bytes)",
"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)20358_storage)": {
- "label": "mapping(uint256 => struct Request)",
+ "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)39493_storage)": {
+ "label": "mapping(uint256 => struct RateLimitLibrary.WindowRateLimitConfig)",
"numberOfBytes": "32"
},
- "t_mapping(t_uint256,t_uint256)": {
- "label": "mapping(uint256 => uint256)",
+ "t_string_storage": {
+ "label": "string",
"numberOfBytes": "32"
},
- "t_struct(AddressSet)3891_storage": {
- "label": "struct EnumerableSetUpgradeable.AddressSet",
+ "t_struct(Set)14685_storage": {
+ "label": "struct EnumerableSetUpgradeable.Set",
"members": [
{
- "label": "_inner",
- "type": "t_struct(Set)3576_storage",
+ "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(Counter)8079_storage": {
- "label": "struct Counters.Counter",
+ "t_struct(UintSet)15157_storage": {
+ "label": "struct EnumerableSetUpgradeable.UintSet",
"members": [
{
- "label": "_value",
- "type": "t_uint256",
+ "label": "_inner",
+ "type": "t_struct(Set)14685_storage",
"offset": 0,
"slot": "0"
}
],
- "numberOfBytes": "32"
+ "numberOfBytes": "64"
},
- "t_struct(Request)20358_storage": {
- "label": "struct Request",
+ "t_struct(WindowRateLimitConfig)39493_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimitConfig",
"members": [
{
- "label": "sender",
- "type": "t_address",
+ "label": "limit",
+ "type": "t_uint256",
"offset": 0,
"slot": "0"
},
{
- "label": "tokenOut",
- "type": "t_address",
+ "label": "amountInFlight",
+ "type": "t_uint256",
"offset": 0,
"slot": "1"
},
{
- "label": "status",
- "type": "t_enum(RequestStatus)20094",
- "offset": 20,
- "slot": "1"
- },
- {
- "label": "amountMToken",
+ "label": "lastUpdated",
"type": "t_uint256",
"offset": 0,
"slot": "2"
},
{
- "label": "mTokenRate",
+ "label": "window",
"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"
+ "numberOfBytes": "128"
},
- "t_struct(TokenConfig)20090_storage": {
- "label": "struct TokenConfig",
+ "t_struct(WindowRateLimits)39503_storage": {
+ "label": "struct RateLimitLibrary.WindowRateLimits",
"members": [
{
- "label": "dataFeed",
- "type": "t_address",
+ "label": "windows",
+ "type": "t_struct(UintSet)15157_storage",
"offset": 0,
"slot": "0"
},
{
- "label": "fee",
- "type": "t_uint256",
- "offset": 0,
- "slot": "1"
- },
- {
- "label": "allowance",
- "type": "t_uint256",
+ "label": "configs",
+ "type": "t_mapping(t_uint256,t_struct(WindowRateLimitConfig)39493_storage)",
"offset": 0,
"slot": "2"
- },
- {
- "label": "stable",
- "type": "t_bool",
- "offset": 0,
- "slot": "3"
}
],
- "numberOfBytes": "128"
+ "numberOfBytes": "96"
},
"t_uint256": {
"label": "uint256",
diff --git a/ERRORS.md b/ERRORS.md
new file mode 100644
index 00000000..2b8a4b77
--- /dev/null
+++ b/ERRORS.md
@@ -0,0 +1,109 @@
+# Custom Errors
+
+Auto-generated by `scripts/generate-errors-readme.ts` from compiled artifacts.
+Total: 102 errors.
+
+| Selector | Error | Declared in |
+| --- | --- | --- |
+| `0xb90f05dc` | `AddressHasEntityId()` | IAllowListV2 |
+| `0x2eb252a6` | `AddressHasProtocolPermissions()` | IAllowListV2 |
+| `0xa9248256` | `AllowanceExceeded(uint256,uint256)` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, IDepositVault, IManageableVault, IRedemptionVault, ManageableVault, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB |
+| `0xa741a045` | `AlreadySet()` | IAllowListV2 |
+| `0x7c9a1cf9` | `AlreadyVoted()` | IMidasTimelockManager, MidasTimelockManager |
+| `0x5bb53877` | `AmountLessThanMin(uint256,uint256)` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, IDepositVault, IManageableVault, IRedemptionVault, ManageableVault, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB |
+| `0xe2ce9413` | `AmountSDOverflowed(uint256)` | MidasLzMintBurnOFTAdapter, MidasLzOFT, MidasLzOFTAdapter |
+| `0x4e83a9b9` | `AssetMismatch(address,address)` | DepositVaultWithMorpho, RedemptionVaultWithMorpho |
+| `0xf3d3036a` | `AutoInvestFailed(bytes)` | DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho |
+| `0xa554dcdf` | `BadData()` | IAllowListV2 |
+| `0x31ff61e9` | `Blacklisted(bytes32,address)` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, MidasAuthLibrary, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB, mToken |
+| `0x129ea30c` | `CannotRevokeFromSelf(bytes32,address)` | IMidasAccessControl, MidasAccessControl |
+| `0x5e393b26` | `CodeSizeZero()` | IAllowListV2 |
+| `0xef5315f6` | `DelayIsAlreadySet()` | IMidasAccessControl, MidasAccessControl |
+| `0xc73b9d7c` | `Deprecated()` | IAllowListV2 |
+| `0x521299a9` | `EmptyArray()` | IMidasAccessControl, MidasAccessControl |
+| `0x5534f9b3` | `FeeExceedsAmount(uint256,uint256)` | IRedemptionVault, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB |
+| `0xee90c468` | `Forbidden()` | IMidasAccessControl, MidasAccessControl |
+| `0x2e48edb8` | `InstantFeeOutOfBounds(uint256)` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, IDepositVault, IManageableVault, IRedemptionVault, ManageableVault, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB |
+| `0x5438233f` | `InstantShareTooHigh(uint256,uint256)` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, IDepositVault, IManageableVault, IRedemptionVault, ManageableVault, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB |
+| `0x7cb769dc` | `InsufficientMsgValue(uint256,uint256)` | IMidasLzVaultComposerSync, MidasLzVaultComposerSync |
+| `0x9ea78c15` | `InsufficientWithdrawnAmount(uint256,uint256)` | RedemptionVaultWithAave |
+| `0x8e4c8aa6` | `InvalidAddress(address)` | Blacklistable, CompositeDataFeed, CompositeDataFeedMultiply, CustomAggregatorV3CompatibleFeed, CustomAggregatorV3CompatibleFeedGrowth, DataFeed, DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, Greenlistable, ManageableVault, MidasAccessControl, MidasAccessControlTimelockController, MidasAxelarVaultExecutable, MidasInitializable, MidasLzVaultComposerSync, MidasPauseManager, MidasTimelockManager, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB, WithMidasAccessControl, WithSanctionsList, mToken |
+| `0x2c5211c6` | `InvalidAmount()` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, IDepositVault, IManageableVault, IRedemptionVault, ManageableVault, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB |
+| `0x4fbe5dba` | `InvalidDelay()` | CompositeDataFeed, CompositeDataFeedMultiply, CustomAggregatorV3CompatibleFeed, CustomAggregatorV3CompatibleFeedGrowth, DataFeed, DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, Greenlistable, ManageableVault, MidasAccessControl, MidasAuthLibrary, MidasPauseManager, MidasTimelockManager, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB, WithSanctionsList, mToken |
+| `0xb5863604` | `InvalidDelegate()` | MidasLzMintBurnOFTAdapter, MidasLzOFT, MidasLzOFTAdapter |
+| `0x0fbdec0a` | `InvalidEndpointCall()` | MidasLzMintBurnOFTAdapter, MidasLzOFT, MidasLzOFTAdapter |
+| `0x2f38c6ee` | `InvalidFee(uint256)` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, IDepositVault, IManageableVault, IRedemptionVault, ManageableVault, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB |
+| `0x1e9714b0` | `InvalidLocalDecimals()` | MidasLzMintBurnOFTAdapter, MidasLzOFT, MidasLzOFTAdapter |
+| `0x531febf4` | `InvalidMaxPendingOperationsPerProposer()` | IMidasTimelockManager, MidasTimelockManager |
+| `0xd706201a` | `InvalidMinMaxInstantFee(uint256,uint256)` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, IDepositVault, IManageableVault, IRedemptionVault, ManageableVault, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB |
+| `0x2ea32193` | `InvalidNewLimit(uint256,uint256)` | IMToken, mToken |
+| `0xb19e5c8b` | `InvalidNewMTokenRate()` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, IDepositVault, IManageableVault, IRedemptionVault, ManageableVault, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB |
+| `0x9a6d49cd` | `InvalidOptions(bytes)` | MidasLzMintBurnOFTAdapter, MidasLzOFT, MidasLzOFTAdapter |
+| `0x0d240b73` | `InvalidPreflightError(bytes)` | IMidasTimelockManager, MidasTimelockManager |
+| `0x347e7b4a` | `InvalidRequestSequence(uint256,uint256)` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, IDepositVault, IManageableVault, IRedemptionVault, ManageableVault, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB |
+| `0x09c8cebf` | `InvalidRounding(uint256,uint256)` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, IDepositVault, IManageableVault, IRedemptionVault, ManageableVault, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB |
+| `0x8f82da5a` | `InvalidSecurityCouncilMembersLength()` | IMidasTimelockManager, MidasTimelockManager |
+| `0x147b28ba` | `InvalidTokenRate(address)` | MidasLzVaultComposerSync |
+| `0xa75b64ce` | `InvalidTokenRate(uint256)` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, IDepositVault, IManageableVault, IRedemptionVault, ManageableVault, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB |
+| `0xc3bcd0b5` | `LessThanMinAmountFirstDeposit(uint256,uint256)` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, IDepositVault |
+| `0x5373352a` | `LzTokenUnavailable()` | MidasLzMintBurnOFTAdapter, MidasLzOFT, MidasLzOFTAdapter |
+| `0x02af5858` | `MaxAmountPerRequestExceeded(uint256)` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, IDepositVault |
+| `0xf0bb59bd` | `MinBalanceNotMet(uint256)` | IMToken, mToken |
+| `0xa1cca21b` | `MismatchArrays(uint256,uint256)` | IMidasAccessControl, MidasAccessControl |
+| `0x47298962` | `NoFunctionPermission(bytes32,bytes4,address)` | CompositeDataFeed, CompositeDataFeedMultiply, CustomAggregatorV3CompatibleFeed, CustomAggregatorV3CompatibleFeedGrowth, DataFeed, DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, Greenlistable, ManageableVault, MidasAccessControl, MidasAuthLibrary, MidasPauseManager, MidasTimelockManager, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB, WithSanctionsList, mToken |
+| `0x7578d2bd` | `NoMsgValueExpected()` | IMidasLzVaultComposerSync, MidasLzVaultComposerSync |
+| `0xcfe2af83` | `NonZeroEntityIdMustBeChangedToZero()` | IAllowListV2 |
+| `0xf6ff4fb7` | `NoPeer(uint32)` | MidasLzMintBurnOFTAdapter, MidasLzOFT, MidasLzOFTAdapter |
+| `0x9f704120` | `NotEnoughNative(uint256)` | MidasLzMintBurnOFTAdapter, MidasLzOFT, MidasLzOFTAdapter |
+| `0xd3659815` | `NotGreenlisted(address,bytes32)` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, MidasAuthLibrary, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB, mToken |
+| `0x341da969` | `NoTimelockDelayForRole()` | IMidasTimelockManager, MidasTimelockManager |
+| `0x48cfb14d` | `NotInSecurityCouncil()` | IMidasTimelockManager, MidasTimelockManager |
+| `0xec7cdc0a` | `NotSelfCall()` | IRedemptionVault, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB |
+| `0x0d6c7be9` | `NotService(address)` | MidasAxelarVaultExecutable |
+| `0x91ac5e4f` | `OnlyEndpoint(address)` | IMidasLzVaultComposerSync, MidasLzMintBurnOFTAdapter, MidasLzOFT, MidasLzOFTAdapter, MidasLzVaultComposerSync |
+| `0xc26bebcc` | `OnlyPeer(uint32,bytes32)` | MidasLzMintBurnOFTAdapter, MidasLzOFT, MidasLzOFTAdapter |
+| `0x14d4a4e8` | `OnlySelf()` | MidasLzMintBurnOFTAdapter, MidasLzOFT, MidasLzOFTAdapter |
+| `0xa19dbf00` | `OnlySelf(address)` | IMidasAxelarVaultExecutable, IMidasLzVaultComposerSync, MidasAxelarVaultExecutable, MidasLzVaultComposerSync |
+| `0x84fb3f0d` | `OnlyValidComposeCaller(address)` | IMidasLzVaultComposerSync, MidasLzVaultComposerSync |
+| `0x0f428110` | `OnlyValidExecutableTokenId(bytes32)` | IMidasAxelarVaultExecutable, MidasAxelarVaultExecutable |
+| `0xfad6ff9f` | `OperationAlreadyPending()` | IMidasTimelockManager, MidasTimelockManager |
+| `0xe436bddf` | `OperationNotPending()` | IMidasTimelockManager, MidasTimelockManager |
+| `0x31224282` | `Paused(address,bytes4)` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, ManageableVault, PauseGuardsLibrary, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB, mToken |
+| `0xe72b517a` | `PaymentTokenAlreadyAdded(address)` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, IDepositVault, IManageableVault, IRedemptionVault, ManageableVault, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB |
+| `0x9b53c3d1` | `PaymentTokenNotExists(address)` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, IDepositVault, IManageableVault, IRedemptionVault, ManageableVault, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB |
+| `0xcc9fca97` | `PendingSetCouncilOperationExists()` | IMidasTimelockManager, MidasTimelockManager |
+| `0x7e18bb60` | `PoolNotSet(address)` | DepositVaultWithAave, RedemptionVaultWithAave |
+| `0x0c3c0dd1` | `PreflightCallUnexpectedSuccess()` | IMidasTimelockManager, MidasTimelockManager |
+| `0x2918eb1d` | `PriceVariationExceeded(uint256,uint256)` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, IDepositVault, IManageableVault, IRedemptionVault, ManageableVault, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB |
+| `0xa74c1c5f` | `RateLimitExceeded()` | MidasLzMintBurnOFTAdapter |
+| `0xffc0fd93` | `RenounceOwnershipDisabled()` | IAllowListV2 |
+| `0xe8ada359` | `RequestIdTooHigh(uint256,uint256)` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, IDepositVault, IManageableVault, IRedemptionVault, ManageableVault, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB |
+| `0xc568fc41` | `RequestNotExists(uint256)` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, IDepositVault, IManageableVault, IRedemptionVault, ManageableVault, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB |
+| `0x3aa00981` | `RoleAdminMismatch(bytes32,bytes32)` | IMidasAccessControl, MidasAccessControl |
+| `0xc7529dd2` | `RolePreflightSucceeded(bytes32,uint32,bool,bool)` | CompositeDataFeed, CompositeDataFeedMultiply, CustomAggregatorV3CompatibleFeed, CustomAggregatorV3CompatibleFeedGrowth, DataFeed, DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, Greenlistable, IMidasTimelockManager, ManageableVault, MidasAccessControl, MidasPauseManager, MidasTimelockManager, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB, WithSanctionsList, mToken |
+| `0x95b6beba` | `SameAddressValue(address)` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, IDepositVault, IManageableVault, IRedemptionVault, ManageableVault, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB |
+| `0x738bd136` | `SameBoolValue(bool)` | Blacklistable, CompositeDataFeed, CompositeDataFeedMultiply, CustomAggregatorV3CompatibleFeed, CustomAggregatorV3CompatibleFeedGrowth, DataFeed, DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, Greenlistable, ManageableVault, MidasPauseManager, MidasTimelockManager, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB, WithMidasAccessControl, WithSanctionsList, mToken |
+| `0x63d72e07` | `Sanctioned(address)` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, ManageableVault, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB, WithSanctionsList |
+| `0x1ad26fd3` | `SenderIsNotTimelock(bytes32,bytes4,address)` | CompositeDataFeed, CompositeDataFeedMultiply, CustomAggregatorV3CompatibleFeed, CustomAggregatorV3CompatibleFeedGrowth, DataFeed, DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, Greenlistable, ManageableVault, MidasAccessControl, MidasAuthLibrary, MidasPauseManager, MidasTimelockManager, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB, WithSanctionsList, mToken |
+| `0x5bbcc482` | `SenderNotProxyAdmin()` | Blacklistable, CompositeDataFeed, CompositeDataFeedMultiply, CustomAggregatorV3CompatibleFeed, CustomAggregatorV3CompatibleFeedGrowth, DataFeed, DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, Greenlistable, ManageableVault, MidasAccessControl, MidasAccessControlTimelockController, MidasAxelarVaultExecutable, MidasInitializable, MidasLzVaultComposerSync, MidasPauseManager, MidasTimelockManager, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB, WithMidasAccessControl, WithSanctionsList, mToken |
+| `0xfab1aa4d` | `SenderNotThis(address)` | MidasLzMintBurnOFTAdapter |
+| `0x8351eea7` | `SimulationResult(bytes)` | MidasLzMintBurnOFTAdapter, MidasLzOFT, MidasLzOFTAdapter |
+| `0x71c4efed` | `SlippageExceeded(uint256,uint256)` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, IDepositVault, IManageableVault, IRedemptionVault, ManageableVault, MidasLzMintBurnOFTAdapter, MidasLzOFT, MidasLzOFTAdapter, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB |
+| `0xf58f733a` | `SupplyCapExceeded()` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, IDepositVault |
+| `0x76148ac4` | `TimelockAlreadySet()` | IMidasTimelockManager, MidasTimelockManager |
+| `0x774bf8ce` | `TimelockOperationNotReady()` | IMidasTimelockManager, MidasTimelockManager |
+| `0xa0e8eaef` | `TokenAddressMismatch(address,address,address)` | MidasAxelarVaultExecutable, MidasLzVaultComposerSync |
+| `0x6dfabd3f` | `TokenNotInPool(address,address)` | DepositVaultWithAave, RedemptionVaultWithAave |
+| `0x1778db9d` | `TooManyPendingOperations()` | IMidasTimelockManager, MidasTimelockManager |
+| `0x07d8b99c` | `UnexpectedOperationStatus(uint8)` | IMidasTimelockManager, MidasTimelockManager |
+| `0xae46e8c4` | `UnexpectedRequestStatus(uint256,uint8)` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, IDepositVault, IManageableVault, IRedemptionVault, ManageableVault, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB |
+| `0x24fc126b` | `UnknownPaymentToken(address)` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, IDepositVault, IManageableVault, IRedemptionVault, ManageableVault, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB |
+| `0x9629ca18` | `UnknownWindowLimit(uint256)` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, ManageableVault, RateLimitLibrary, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB, mToken |
+| `0xa4ea87cc` | `UnsupportedUSTBToken(address)` | DepositVaultWithUSTB |
+| `0xafa3b1d4` | `UserFacingRoleNotAllowed(bytes32)` | CompositeDataFeed, CompositeDataFeedMultiply, CustomAggregatorV3CompatibleFeed, CustomAggregatorV3CompatibleFeedGrowth, DataFeed, DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, Greenlistable, ManageableVault, MidasAccessControl, MidasAuthLibrary, MidasPauseManager, MidasTimelockManager, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB, WithSanctionsList, mToken |
+| `0xfef1b6c4` | `USTBFeeNotZero(uint256)` | DepositVaultWithUSTB |
+| `0x34455819` | `VaultNotSet(address)` | DepositVaultWithMorpho, RedemptionVaultWithMorpho |
+| `0xa8827d28` | `WindowLimitExceeded(uint256,uint256,uint256)` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, ManageableVault, RateLimitLibrary, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB, mToken |
+| `0xfe84fc4a` | `WindowTooShort(uint256)` | DepositVault, DepositVaultWithAave, DepositVaultWithMToken, DepositVaultWithMorpho, DepositVaultWithUSTB, ManageableVault, RateLimitLibrary, RedemptionVault, RedemptionVaultWithAave, RedemptionVaultWithMToken, RedemptionVaultWithMorpho, RedemptionVaultWithUSTB, mToken |
+| `0x825c287a` | `ZeroMTokenReceived(uint256)` | DepositVaultWithMToken |
+| `0xdc6d336e` | `ZeroShares(uint256)` | DepositVaultWithMorpho |
diff --git a/LICENSE b/LICENSE
index e72bfdda..389c6837 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,674 +1,80 @@
- GNU GENERAL PUBLIC LICENSE
- Version 3, 29 June 2007
+Business Source License 1.1
- Copyright (C) 2007 Free Software Foundation, Inc.
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
+License text copyright © 2017 MariaDB Corporation Ab, All Rights Reserved.
+"Business Source License" is a trademark of MariaDB Corporation Ab.
- Preamble
+Parameters
- The GNU General Public License is a free, copyleft license for
-software and other kinds of works.
+Licensor: Midas
+Licensed Work: Midas EVM Contracts
+Change Date: 2036-01-01
+Change License: GPL-2.0 or any later version
- The licenses for most software and other practical works are designed
-to take away your freedom to share and change the works. By contrast,
-the GNU General Public License is intended to guarantee your freedom to
-share and change all versions of a program--to make sure it remains free
-software for all its users. We, the Free Software Foundation, use the
-GNU General Public License for most of our software; it applies also to
-any other work released this way by its authors. You can apply it to
-your programs, too.
+Additional Use Grant: None
- When we speak of free software, we are referring to freedom, not
-price. Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-them if you wish), that you receive source code or can get it if you
-want it, that you can change the software or use pieces of it in new
-free programs, and that you know you can do these things.
+Terms
- To protect your rights, we need to prevent others from denying you
-these rights or asking you to surrender the rights. Therefore, you have
-certain responsibilities if you distribute copies of the software, or if
-you modify it: responsibilities to respect the freedom of others.
+The Licensor hereby grants you the right to copy, modify, create derivative
+works, redistribute, and make non-production use of the Licensed Work. The
+Licensor may make an Additional Use Grant, above, permitting limited
+production use.
- For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must pass on to the recipients the same
-freedoms that you received. You must make sure that they, too, receive
-or can get the source code. And you must show them these terms so they
-know their rights.
+Effective on the Change Date, or the fourth anniversary of the first publicly
+available distribution of a specific version of the Licensed Work under this
+License, whichever comes first, the Licensor hereby grants you rights under
+the terms of the Change License, and the rights granted in the paragraph
+above terminate.
- Developers that use the GNU GPL protect your rights with two steps:
-(1) assert copyright on the software, and (2) offer you this License
-giving you legal permission to copy, distribute and/or modify it.
+If your use of the Licensed Work does not comply with the requirements
+currently in effect as described in this License, you must purchase a
+commercial license from the Licensor, its affiliated entities, or authorized
+resellers, or you must refrain from using the Licensed Work.
- For the developers' and authors' protection, the GPL clearly explains
-that there is no warranty for this free software. For both users' and
-authors' sake, the GPL requires that modified versions be marked as
-changed, so that their problems will not be attributed erroneously to
-authors of previous versions.
+All copies of the original and modified Licensed Work, and derivative works
+of the Licensed Work, are subject to this License. This License applies
+separately for each version of the Licensed Work and the Change Date may vary
+for each version of the Licensed Work released by Licensor.
- Some devices are designed to deny users access to install or run
-modified versions of the software inside them, although the manufacturer
-can do so. This is fundamentally incompatible with the aim of
-protecting users' freedom to change the software. The systematic
-pattern of such abuse occurs in the area of products for individuals to
-use, which is precisely where it is most unacceptable. Therefore, we
-have designed this version of the GPL to prohibit the practice for those
-products. If such problems arise substantially in other domains, we
-stand ready to extend this provision to those domains in future versions
-of the GPL, as needed to protect the freedom of users.
+You must conspicuously display this License on each original or modified copy
+of the Licensed Work. If you receive the Licensed Work in original or
+modified form from a third party, the terms and conditions set forth in this
+License apply to your use of that work.
- Finally, every program is threatened constantly by software patents.
-States should not allow patents to restrict development and use of
-software on general-purpose computers, but in those that do, we wish to
-avoid the special danger that patents applied to a free program could
-make it effectively proprietary. To prevent this, the GPL assures that
-patents cannot be used to render the program non-free.
+Any use of the Licensed Work in violation of this License will automatically
+terminate your rights under this License for the current and all other
+versions of the Licensed Work.
- The precise terms and conditions for copying, distribution and
-modification follow.
+This License does not grant you any right in any trademark or logo of
+Licensor or its affiliates (provided that you may use a trademark or logo of
+Licensor as expressly required by this License).
- TERMS AND CONDITIONS
+TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
+AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
+EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
+TITLE.
- 0. Definitions.
+MariaDB hereby grants you permission to use this License’s text to license
+your works, and to refer to it using the trademark “Business Source License”,
+as long as you comply with the Covenants of Licensor below.
- "This License" refers to version 3 of the GNU General Public License.
+Covenants of Licensor
- "Copyright" also means copyright-like laws that apply to other kinds of
-works, such as semiconductor masks.
+In consideration of the right to use this License’s text and the “Business
+Source License” name and trademark, Licensor covenants to MariaDB, and to all
+other recipients of the licensed work to be provided by Licensor:
- "The Program" refers to any copyrightable work licensed under this
-License. Each licensee is addressed as "you". "Licensees" and
-"recipients" may be individuals or organizations.
+1. To specify as the Change License the GPL Version 2.0 or any later version,
+ or a license that is compatible with GPL Version 2.0 or a later version,
+ where “compatible” means that software provided under the Change License can
+ be included in a program with software provided under GPL Version 2.0 or a
+ later version. Licensor may specify additional Change Licenses without
+ limitation.
- To "modify" a work means to copy from or adapt all or part of the work
-in a fashion requiring copyright permission, other than the making of an
-exact copy. The resulting work is called a "modified version" of the
-earlier work or a work "based on" the earlier work.
+2. To either: (a) specify an additional grant of rights to use that does not
+ impose any additional restriction on the right granted in this License, as
+ the Additional Use Grant; or (b) insert the text “None”.
- A "covered work" means either the unmodified Program or a work based
-on the Program.
+3. To specify a Change Date.
- To "propagate" a work means to do anything with it that, without
-permission, would make you directly or secondarily liable for
-infringement under applicable copyright law, except executing it on a
-computer or modifying a private copy. Propagation includes copying,
-distribution (with or without modification), making available to the
-public, and in some countries other activities as well.
-
- To "convey" a work means any kind of propagation that enables other
-parties to make or receive copies. Mere interaction with a user through
-a computer network, with no transfer of a copy, is not conveying.
-
- An interactive user interface displays "Appropriate Legal Notices"
-to the extent that it includes a convenient and prominently visible
-feature that (1) displays an appropriate copyright notice, and (2)
-tells the user that there is no warranty for the work (except to the
-extent that warranties are provided), that licensees may convey the
-work under this License, and how to view a copy of this License. If
-the interface presents a list of user commands or options, such as a
-menu, a prominent item in the list meets this criterion.
-
- 1. Source Code.
-
- The "source code" for a work means the preferred form of the work
-for making modifications to it. "Object code" means any non-source
-form of a work.
-
- A "Standard Interface" means an interface that either is an official
-standard defined by a recognized standards body, or, in the case of
-interfaces specified for a particular programming language, one that
-is widely used among developers working in that language.
-
- The "System Libraries" of an executable work include anything, other
-than the work as a whole, that (a) is included in the normal form of
-packaging a Major Component, but which is not part of that Major
-Component, and (b) serves only to enable use of the work with that
-Major Component, or to implement a Standard Interface for which an
-implementation is available to the public in source code form. A
-"Major Component", in this context, means a major essential component
-(kernel, window system, and so on) of the specific operating system
-(if any) on which the executable work runs, or a compiler used to
-produce the work, or an object code interpreter used to run it.
-
- The "Corresponding Source" for a work in object code form means all
-the source code needed to generate, install, and (for an executable
-work) run the object code and to modify the work, including scripts to
-control those activities. However, it does not include the work's
-System Libraries, or general-purpose tools or generally available free
-programs which are used unmodified in performing those activities but
-which are not part of the work. For example, Corresponding Source
-includes interface definition files associated with source files for
-the work, and the source code for shared libraries and dynamically
-linked subprograms that the work is specifically designed to require,
-such as by intimate data communication or control flow between those
-subprograms and other parts of the work.
-
- The Corresponding Source need not include anything that users
-can regenerate automatically from other parts of the Corresponding
-Source.
-
- The Corresponding Source for a work in source code form is that
-same work.
-
- 2. Basic Permissions.
-
- All rights granted under this License are granted for the term of
-copyright on the Program, and are irrevocable provided the stated
-conditions are met. This License explicitly affirms your unlimited
-permission to run the unmodified Program. The output from running a
-covered work is covered by this License only if the output, given its
-content, constitutes a covered work. This License acknowledges your
-rights of fair use or other equivalent, as provided by copyright law.
-
- You may make, run and propagate covered works that you do not
-convey, without conditions so long as your license otherwise remains
-in force. You may convey covered works to others for the sole purpose
-of having them make modifications exclusively for you, or provide you
-with facilities for running those works, provided that you comply with
-the terms of this License in conveying all material for which you do
-not control copyright. Those thus making or running the covered works
-for you must do so exclusively on your behalf, under your direction
-and control, on terms that prohibit them from making any copies of
-your copyrighted material outside their relationship with you.
-
- Conveying under any other circumstances is permitted solely under
-the conditions stated below. Sublicensing is not allowed; section 10
-makes it unnecessary.
-
- 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
-
- No covered work shall be deemed part of an effective technological
-measure under any applicable law fulfilling obligations under article
-11 of the WIPO copyright treaty adopted on 20 December 1996, or
-similar laws prohibiting or restricting circumvention of such
-measures.
-
- When you convey a covered work, you waive any legal power to forbid
-circumvention of technological measures to the extent such circumvention
-is effected by exercising rights under this License with respect to
-the covered work, and you disclaim any intention to limit operation or
-modification of the work as a means of enforcing, against the work's
-users, your or third parties' legal rights to forbid circumvention of
-technological measures.
-
- 4. Conveying Verbatim Copies.
-
- You may convey verbatim copies of the Program's source code as you
-receive it, in any medium, provided that you conspicuously and
-appropriately publish on each copy an appropriate copyright notice;
-keep intact all notices stating that this License and any
-non-permissive terms added in accord with section 7 apply to the code;
-keep intact all notices of the absence of any warranty; and give all
-recipients a copy of this License along with the Program.
-
- You may charge any price or no price for each copy that you convey,
-and you may offer support or warranty protection for a fee.
-
- 5. Conveying Modified Source Versions.
-
- You may convey a work based on the Program, or the modifications to
-produce it from the Program, in the form of source code under the
-terms of section 4, provided that you also meet all of these conditions:
-
- a) The work must carry prominent notices stating that you modified
- it, and giving a relevant date.
-
- b) The work must carry prominent notices stating that it is
- released under this License and any conditions added under section
- 7. This requirement modifies the requirement in section 4 to
- "keep intact all notices".
-
- c) You must license the entire work, as a whole, under this
- License to anyone who comes into possession of a copy. This
- License will therefore apply, along with any applicable section 7
- additional terms, to the whole of the work, and all its parts,
- regardless of how they are packaged. This License gives no
- permission to license the work in any other way, but it does not
- invalidate such permission if you have separately received it.
-
- d) If the work has interactive user interfaces, each must display
- Appropriate Legal Notices; however, if the Program has interactive
- interfaces that do not display Appropriate Legal Notices, your
- work need not make them do so.
-
- A compilation of a covered work with other separate and independent
-works, which are not by their nature extensions of the covered work,
-and which are not combined with it such as to form a larger program,
-in or on a volume of a storage or distribution medium, is called an
-"aggregate" if the compilation and its resulting copyright are not
-used to limit the access or legal rights of the compilation's users
-beyond what the individual works permit. Inclusion of a covered work
-in an aggregate does not cause this License to apply to the other
-parts of the aggregate.
-
- 6. Conveying Non-Source Forms.
-
- You may convey a covered work in object code form under the terms
-of sections 4 and 5, provided that you also convey the
-machine-readable Corresponding Source under the terms of this License,
-in one of these ways:
-
- a) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by the
- Corresponding Source fixed on a durable physical medium
- customarily used for software interchange.
-
- b) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by a
- written offer, valid for at least three years and valid for as
- long as you offer spare parts or customer support for that product
- model, to give anyone who possesses the object code either (1) a
- copy of the Corresponding Source for all the software in the
- product that is covered by this License, on a durable physical
- medium customarily used for software interchange, for a price no
- more than your reasonable cost of physically performing this
- conveying of source, or (2) access to copy the
- Corresponding Source from a network server at no charge.
-
- c) Convey individual copies of the object code with a copy of the
- written offer to provide the Corresponding Source. This
- alternative is allowed only occasionally and noncommercially, and
- only if you received the object code with such an offer, in accord
- with subsection 6b.
-
- d) Convey the object code by offering access from a designated
- place (gratis or for a charge), and offer equivalent access to the
- Corresponding Source in the same way through the same place at no
- further charge. You need not require recipients to copy the
- Corresponding Source along with the object code. If the place to
- copy the object code is a network server, the Corresponding Source
- may be on a different server (operated by you or a third party)
- that supports equivalent copying facilities, provided you maintain
- clear directions next to the object code saying where to find the
- Corresponding Source. Regardless of what server hosts the
- Corresponding Source, you remain obligated to ensure that it is
- available for as long as needed to satisfy these requirements.
-
- e) Convey the object code using peer-to-peer transmission, provided
- you inform other peers where the object code and Corresponding
- Source of the work are being offered to the general public at no
- charge under subsection 6d.
-
- A separable portion of the object code, whose source code is excluded
-from the Corresponding Source as a System Library, need not be
-included in conveying the object code work.
-
- A "User Product" is either (1) a "consumer product", which means any
-tangible personal property which is normally used for personal, family,
-or household purposes, or (2) anything designed or sold for incorporation
-into a dwelling. In determining whether a product is a consumer product,
-doubtful cases shall be resolved in favor of coverage. For a particular
-product received by a particular user, "normally used" refers to a
-typical or common use of that class of product, regardless of the status
-of the particular user or of the way in which the particular user
-actually uses, or expects or is expected to use, the product. A product
-is a consumer product regardless of whether the product has substantial
-commercial, industrial or non-consumer uses, unless such uses represent
-the only significant mode of use of the product.
-
- "Installation Information" for a User Product means any methods,
-procedures, authorization keys, or other information required to install
-and execute modified versions of a covered work in that User Product from
-a modified version of its Corresponding Source. The information must
-suffice to ensure that the continued functioning of the modified object
-code is in no case prevented or interfered with solely because
-modification has been made.
-
- If you convey an object code work under this section in, or with, or
-specifically for use in, a User Product, and the conveying occurs as
-part of a transaction in which the right of possession and use of the
-User Product is transferred to the recipient in perpetuity or for a
-fixed term (regardless of how the transaction is characterized), the
-Corresponding Source conveyed under this section must be accompanied
-by the Installation Information. But this requirement does not apply
-if neither you nor any third party retains the ability to install
-modified object code on the User Product (for example, the work has
-been installed in ROM).
-
- The requirement to provide Installation Information does not include a
-requirement to continue to provide support service, warranty, or updates
-for a work that has been modified or installed by the recipient, or for
-the User Product in which it has been modified or installed. Access to a
-network may be denied when the modification itself materially and
-adversely affects the operation of the network or violates the rules and
-protocols for communication across the network.
-
- Corresponding Source conveyed, and Installation Information provided,
-in accord with this section must be in a format that is publicly
-documented (and with an implementation available to the public in
-source code form), and must require no special password or key for
-unpacking, reading or copying.
-
- 7. Additional Terms.
-
- "Additional permissions" are terms that supplement the terms of this
-License by making exceptions from one or more of its conditions.
-Additional permissions that are applicable to the entire Program shall
-be treated as though they were included in this License, to the extent
-that they are valid under applicable law. If additional permissions
-apply only to part of the Program, that part may be used separately
-under those permissions, but the entire Program remains governed by
-this License without regard to the additional permissions.
-
- When you convey a copy of a covered work, you may at your option
-remove any additional permissions from that copy, or from any part of
-it. (Additional permissions may be written to require their own
-removal in certain cases when you modify the work.) You may place
-additional permissions on material, added by you to a covered work,
-for which you have or can give appropriate copyright permission.
-
- Notwithstanding any other provision of this License, for material you
-add to a covered work, you may (if authorized by the copyright holders of
-that material) supplement the terms of this License with terms:
-
- a) Disclaiming warranty or limiting liability differently from the
- terms of sections 15 and 16 of this License; or
-
- b) Requiring preservation of specified reasonable legal notices or
- author attributions in that material or in the Appropriate Legal
- Notices displayed by works containing it; or
-
- c) Prohibiting misrepresentation of the origin of that material, or
- requiring that modified versions of such material be marked in
- reasonable ways as different from the original version; or
-
- d) Limiting the use for publicity purposes of names of licensors or
- authors of the material; or
-
- e) Declining to grant rights under trademark law for use of some
- trade names, trademarks, or service marks; or
-
- f) Requiring indemnification of licensors and authors of that
- material by anyone who conveys the material (or modified versions of
- it) with contractual assumptions of liability to the recipient, for
- any liability that these contractual assumptions directly impose on
- those licensors and authors.
-
- All other non-permissive additional terms are considered "further
-restrictions" within the meaning of section 10. If the Program as you
-received it, or any part of it, contains a notice stating that it is
-governed by this License along with a term that is a further
-restriction, you may remove that term. If a license document contains
-a further restriction but permits relicensing or conveying under this
-License, you may add to a covered work material governed by the terms
-of that license document, provided that the further restriction does
-not survive such relicensing or conveying.
-
- If you add terms to a covered work in accord with this section, you
-must place, in the relevant source files, a statement of the
-additional terms that apply to those files, or a notice indicating
-where to find the applicable terms.
-
- Additional terms, permissive or non-permissive, may be stated in the
-form of a separately written license, or stated as exceptions;
-the above requirements apply either way.
-
- 8. Termination.
-
- You may not propagate or modify a covered work except as expressly
-provided under this License. Any attempt otherwise to propagate or
-modify it is void, and will automatically terminate your rights under
-this License (including any patent licenses granted under the third
-paragraph of section 11).
-
- However, if you cease all violation of this License, then your
-license from a particular copyright holder is reinstated (a)
-provisionally, unless and until the copyright holder explicitly and
-finally terminates your license, and (b) permanently, if the copyright
-holder fails to notify you of the violation by some reasonable means
-prior to 60 days after the cessation.
-
- Moreover, your license from a particular copyright holder is
-reinstated permanently if the copyright holder notifies you of the
-violation by some reasonable means, this is the first time you have
-received notice of violation of this License (for any work) from that
-copyright holder, and you cure the violation prior to 30 days after
-your receipt of the notice.
-
- Termination of your rights under this section does not terminate the
-licenses of parties who have received copies or rights from you under
-this License. If your rights have been terminated and not permanently
-reinstated, you do not qualify to receive new licenses for the same
-material under section 10.
-
- 9. Acceptance Not Required for Having Copies.
-
- You are not required to accept this License in order to receive or
-run a copy of the Program. Ancillary propagation of a covered work
-occurring solely as a consequence of using peer-to-peer transmission
-to receive a copy likewise does not require acceptance. However,
-nothing other than this License grants you permission to propagate or
-modify any covered work. These actions infringe copyright if you do
-not accept this License. Therefore, by modifying or propagating a
-covered work, you indicate your acceptance of this License to do so.
-
- 10. Automatic Licensing of Downstream Recipients.
-
- Each time you convey a covered work, the recipient automatically
-receives a license from the original licensors, to run, modify and
-propagate that work, subject to this License. You are not responsible
-for enforcing compliance by third parties with this License.
-
- An "entity transaction" is a transaction transferring control of an
-organization, or substantially all assets of one, or subdividing an
-organization, or merging organizations. If propagation of a covered
-work results from an entity transaction, each party to that
-transaction who receives a copy of the work also receives whatever
-licenses to the work the party's predecessor in interest had or could
-give under the previous paragraph, plus a right to possession of the
-Corresponding Source of the work from the predecessor in interest, if
-the predecessor has it or can get it with reasonable efforts.
-
- You may not impose any further restrictions on the exercise of the
-rights granted or affirmed under this License. For example, you may
-not impose a license fee, royalty, or other charge for exercise of
-rights granted under this License, and you may not initiate litigation
-(including a cross-claim or counterclaim in a lawsuit) alleging that
-any patent claim is infringed by making, using, selling, offering for
-sale, or importing the Program or any portion of it.
-
- 11. Patents.
-
- A "contributor" is a copyright holder who authorizes use under this
-License of the Program or a work on which the Program is based. The
-work thus licensed is called the contributor's "contributor version".
-
- A contributor's "essential patent claims" are all patent claims
-owned or controlled by the contributor, whether already acquired or
-hereafter acquired, that would be infringed by some manner, permitted
-by this License, of making, using, or selling its contributor version,
-but do not include claims that would be infringed only as a
-consequence of further modification of the contributor version. For
-purposes of this definition, "control" includes the right to grant
-patent sublicenses in a manner consistent with the requirements of
-this License.
-
- Each contributor grants you a non-exclusive, worldwide, royalty-free
-patent license under the contributor's essential patent claims, to
-make, use, sell, offer for sale, import and otherwise run, modify and
-propagate the contents of its contributor version.
-
- In the following three paragraphs, a "patent license" is any express
-agreement or commitment, however denominated, not to enforce a patent
-(such as an express permission to practice a patent or covenant not to
-sue for patent infringement). To "grant" such a patent license to a
-party means to make such an agreement or commitment not to enforce a
-patent against the party.
-
- If you convey a covered work, knowingly relying on a patent license,
-and the Corresponding Source of the work is not available for anyone
-to copy, free of charge and under the terms of this License, through a
-publicly available network server or other readily accessible means,
-then you must either (1) cause the Corresponding Source to be so
-available, or (2) arrange to deprive yourself of the benefit of the
-patent license for this particular work, or (3) arrange, in a manner
-consistent with the requirements of this License, to extend the patent
-license to downstream recipients. "Knowingly relying" means you have
-actual knowledge that, but for the patent license, your conveying the
-covered work in a country, or your recipient's use of the covered work
-in a country, would infringe one or more identifiable patents in that
-country that you have reason to believe are valid.
-
- If, pursuant to or in connection with a single transaction or
-arrangement, you convey, or propagate by procuring conveyance of, a
-covered work, and grant a patent license to some of the parties
-receiving the covered work authorizing them to use, propagate, modify
-or convey a specific copy of the covered work, then the patent license
-you grant is automatically extended to all recipients of the covered
-work and works based on it.
-
- A patent license is "discriminatory" if it does not include within
-the scope of its coverage, prohibits the exercise of, or is
-conditioned on the non-exercise of one or more of the rights that are
-specifically granted under this License. You may not convey a covered
-work if you are a party to an arrangement with a third party that is
-in the business of distributing software, under which you make payment
-to the third party based on the extent of your activity of conveying
-the work, and under which the third party grants, to any of the
-parties who would receive the covered work from you, a discriminatory
-patent license (a) in connection with copies of the covered work
-conveyed by you (or copies made from those copies), or (b) primarily
-for and in connection with specific products or compilations that
-contain the covered work, unless you entered into that arrangement,
-or that patent license was granted, prior to 28 March 2007.
-
- Nothing in this License shall be construed as excluding or limiting
-any implied license or other defenses to infringement that may
-otherwise be available to you under applicable patent law.
-
- 12. No Surrender of Others' Freedom.
-
- If conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot convey a
-covered work so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you may
-not convey it at all. For example, if you agree to terms that obligate you
-to collect a royalty for further conveying from those to whom you convey
-the Program, the only way you could satisfy both those terms and this
-License would be to refrain entirely from conveying the Program.
-
- 13. Use with the GNU Affero General Public License.
-
- Notwithstanding any other provision of this License, you have
-permission to link or combine any covered work with a work licensed
-under version 3 of the GNU Affero General Public License into a single
-combined work, and to convey the resulting work. The terms of this
-License will continue to apply to the part which is the covered work,
-but the special requirements of the GNU Affero General Public License,
-section 13, concerning interaction through a network will apply to the
-combination as such.
-
- 14. Revised Versions of this License.
-
- The Free Software Foundation may publish revised and/or new versions of
-the GNU General Public License from time to time. Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
- Each version is given a distinguishing version number. If the
-Program specifies that a certain numbered version of the GNU General
-Public License "or any later version" applies to it, you have the
-option of following the terms and conditions either of that numbered
-version or of any later version published by the Free Software
-Foundation. If the Program does not specify a version number of the
-GNU General Public License, you may choose any version ever published
-by the Free Software Foundation.
-
- If the Program specifies that a proxy can decide which future
-versions of the GNU General Public License can be used, that proxy's
-public statement of acceptance of a version permanently authorizes you
-to choose that version for the Program.
-
- Later license versions may give you additional or different
-permissions. However, no additional obligations are imposed on any
-author or copyright holder as a result of your choosing to follow a
-later version.
-
- 15. Disclaimer of Warranty.
-
- THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
-APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
-IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
- 16. Limitation of Liability.
-
- IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGES.
-
- 17. Interpretation of Sections 15 and 16.
-
- If the disclaimer of warranty and limitation of liability provided
-above cannot be given local legal effect according to their terms,
-reviewing courts shall apply local law that most closely approximates
-an absolute waiver of all civil liability in connection with the
-Program, unless a warranty or assumption of liability accompanies a
-copy of the Program in return for a fee.
-
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Programs
-
- If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
- To do so, attach the following notices to the program. It is safest
-to attach them to the start of each source file to most effectively
-state the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
-
- Copyright (C)
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-
-Also add information on how to contact you by electronic and paper mail.
-
- If the program does terminal interaction, make it output a short
-notice like this when it starts in an interactive mode:
-
- Copyright (C)
- This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
- This is free software, and you are welcome to redistribute it
- under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License. Of course, your program's commands
-might be different; for a GUI interface, you would use an "about box".
-
- You should also get your employer (if you work as a programmer) or school,
-if any, to sign a "copyright disclaimer" for the program, if necessary.
-For more information on this, and how to apply and follow the GNU GPL, see
-.
-
- The GNU General Public License does not permit incorporating your program
-into proprietary programs. If your program is a subroutine library, you
-may consider it more useful to permit linking proprietary applications with
-the library. If this is what you want to do, use the GNU Lesser General
-Public License instead of this License. But first, please read
-.
\ No newline at end of file
+4. Not to modify this License in any other way.
diff --git a/README.md b/README.md
index 1f7b288a..dfbe5bcc 100644
--- a/README.md
+++ b/README.md
@@ -8,19 +8,23 @@ This repository contains EVM smart contracts for the Midas protocol.
- [Repository Structure](#repository-structure)
- [Building and Testing](#building-and-testing)
- [Architecture Overview](#architecture-overview)
-- [Core Contracts](#core-contracts)
-- [Crosschain Contracts](#crosschain-contracts-axelar-layerzero)
-- [Deployed Contract Addresses](#deployed-contract-addresses)
+- [Contracts Overview](#contracts-overview)
+ - [Access Control Contracts](#access-control-contracts)
+ - [mTokens](#mtokens)
+ - [Data Feeds and Aggregators](#data-feeds-and-aggregators)
+ - [Vaults](#vaults)
+ - [Miscellaneous Contracts](#miscellaneous-contracts)
- [Deployment](#deployment)
- [Upgradeability](#upgradability)
- [Documentation](#documentation)
- [Development](#development)
+- [Deployed Contract Addresses](#deployed-contract-addresses)
## Prerequisites
### Required Software
-- **Node.js**: >=22
+- **Node.js**: >=22 <23
- **Yarn**: Berry (check the [installation guide](https://yarnpkg.com/getting-started/install))
### Installation
@@ -40,15 +44,14 @@ In the root of the repo, create a `.env` file from [.env.example](./.env.example
midas-contracts/
├── contracts/ # Smart contract source code
│ ├── abstract/ # Abstract base contracts
-│ ├── access/ # Access control contracts
-│ ├── feeds/ # Price feed contracts
+│ ├── access/ # Access control, timelock and pause contracts
+│ ├── feeds/ # Price feed and aggregator contracts
│ ├── interfaces/ # Contract interfaces
│ ├── libraries/ # Contract libraries
-│ ├── misc/ # Miscellaneous contracts (LayerZero, Axelar, Custom price feed adapters)
-│ ├── products/ # Individual mToken implementations
+│ ├── misc/ # Miscellaneous contracts (LayerZero, Axelar, data feed adapters)
│ ├── testers/ # Non-production contracts that are used in tests
│ ├── mocks/ # Mocked contracts used in tests and some testnet deployments
-│ ├── *.sol # Core smart contracts
+│ ├── *.sol # Core smart contracts (mTokens and vaults)
│ └── ...
├── config/ # Configuration files
│ ├── constants/ # Addresses and constants
@@ -110,13 +113,22 @@ yarn slither:summary
## Architecture Overview
+The protocol is built around a few simple ideas:
+
+- **mTokens** are the ERC20 tokens issued by the protocol (e.g. mTBILL, mBASIS, mBTC).
+- **Vaults** are the entry points for users. They handle minting (deposits) and redemption (burns), calculate fees and exchange rates, and move funds.
+- **Data Feeds and Aggregators** provide the prices that vaults use to compute exchange rates.
+- **Access control contracts** sit underneath everything and control who is allowed to do what: roles, function-level permissions, timelock and pause management.
+- **Miscellaneous contracts** connect the protocol to external systems: cross-chain bridges and third-party price feed adapters.
+
### Core Components
1. **mTokens**: ERC20 tokens (e.g., mTBILL, mBASIS, mBTC)
2. **Vaults**: Smart contracts managing minting (deposits) and redemption processes
3. **Access Control**: Role-based permission system for managing protocol operations
-4. **Price Feeds**: Oracle integrations for conversion rates
-5. **Cross-Chain**: LayerZero and Axelar integrations for multi-chain operations
+4. **Timelock and Pause**: Delayed execution of admin actions and a global pause mechanism
+5. **Price Feeds**: Oracle integrations for conversion rates
+6. **Cross-Chain**: LayerZero and Axelar integrations for multi-chain operations
### System Flow
@@ -136,21 +148,30 @@ yarn slither:summary
4. For instant redemptions (sync): Payment tokens are transferred to user immediately
5. For request redemptions (async): Admin fulfills the request after off-chain processing
-## Core Contracts
+## Contracts Overview
-### **MidasAccessControl** ([`contracts/access/MidasAccessControl.sol`](contracts/access/MidasAccessControl.sol))
+### Access Control Contracts
+
+These contracts form the base layer of the protocol. They do not move user funds themselves, but they decide who is allowed to call protected functions, add a delay to sensitive admin actions, and allow the protocol to be paused.
+
+#### **MidasAccessControl** ([`contracts/access/MidasAccessControl.sol`](contracts/access/MidasAccessControl.sol))
Centralized access control contract managing all roles across the protocol
**Key Features:**
- Role-based access control
+- Function-level access control - on top of plain roles, access can be scoped to a specific function on a specific target contract. A function permission is identified by `(masterRole, targetContract, functionSelector)`, and only the accounts enabled for that permission can call the function. Grant operators manage who is enabled for each permission.
+- Per-role timelock delays - each role (and each function permission) can have its own timelock delay, so changes to it only take effect after a configured period
- Greenlist/blacklist management
**Key Functions:**
- `grantRole()` - grants role to a specific address. Can be invoked only by role admin
- `revokeRole()` - revokes role from a specific address. Can be invoked only by role admin
+- `setPermissionRoleMult()` - enables or disables accounts for a specific function-level permission `(masterRole, targetContract, functionSelector)`
+- `setGrantOperatorRoleMult()` - sets the operators that are allowed to manage a given function permission
+- `setRoleDelayMult()` / `setDefaultDelay()` - configure the timelock delay for individual roles or the default delay
The default role admin for all roles is `defaultAdmin`. Only exceptions are:
@@ -160,6 +181,7 @@ The default role admin for all roles is `defaultAdmin`. Only exceptions are:
All roles in the system are documented in [`ROLES.md`](./ROLES.md). This file is auto-generated and contains:
- **Common Roles**: Roles shared across all contracts:
+
- `defaultAdmin`: `0x0000000000000000000000000000000000000000000000000000000000000000`
- `greenlisted`: Allows access to vault operations (if greenlist is enforced)
- `blacklisted`: Prevents token transfers and vaults access
@@ -172,25 +194,115 @@ All roles in the system are documented in [`ROLES.md`](./ROLES.md). This file is
- `depositVaultAdmin` - Can manage deposit vault operations
- `redemptionVaultAdmin` - Can manage redemption vault operations
-### **mToken** ([`contracts/mToken.sol`](contracts/mToken.sol))
+#### **MidasTimelockManager** ([`contracts/access/MidasTimelockManager.sol`](contracts/access/MidasTimelockManager.sol))
+
+Manages timelock scheduling, security council votes and operation details. Sensitive admin actions (for example access control changes and upgrades) are not executed directly. Instead they are scheduled here, optionally reviewed by a security council, and only executed after a delay.
+
+**Key Features:**
+
+- Scheduling of timelock operations, single and in bulk
+- Security council with versioning, used to vote for or veto operations
+- Expiration and dispute periods for pending operations
+- A hard cap on the number of pending operations per proposer
+
+**Key Functions:**
+
+- `scheduleTimelockOperation()` - schedules a single operation to be executed after the delay
+- `bulkScheduleTimelockOperation()` - schedules multiple operations at once
+- `executeTimelockOperation()` - executes a scheduled operation once it is ready
+- `voteForExecution()` / `voteForVeto()` - security council voting to approve or block an operation
+- `setSecurityCouncil()` - updates the security council members
+
+#### **Timelock Controllers**
+
+The actual delayed execution is performed by OpenZeppelin based timelock controllers:
+
+- **MidasTimelockController** ([`contracts/access/MidasTimelockController.sol`](contracts/access/MidasTimelockController.sol)) - A standard OpenZeppelin `TimelockController` extended with getters for proposers and executors.
+- **MidasAccessControlTimelockController** ([`contracts/access/MidasAccessControlTimelockController.sol`](contracts/access/MidasAccessControlTimelockController.sol)) - A `TimelockController` that is driven by `MidasTimelockManager`. It is used to apply access control changes through the timelock flow.
+
+#### **MidasPauseManager** ([`contracts/access/MidasPauseManager.sol`](contracts/access/MidasPauseManager.sol))
+
+Global manager for pausing and unpausing protocol functions. Pausing can be applied globally, per contract, or per individual function, which gives fine-grained control during incidents.
+
+**Key Features:**
+
+- Global pause, per-contract pause and per-function pause
+- Separate roles for pausing and unpausing
+- Configurable delays for pause and unpause
+
+**Key Functions:**
+
+- `globalPause()` / `globalUnpause()` - pause or unpause the whole protocol
+- `bulkPauseContract()` / `bulkUnpauseContract()` - pause or unpause entire contracts
+- `bulkPauseContractFn()` / `bulkUnpauseContractFn()` - pause or unpause individual functions (by selector), so only the affected operations are stopped
+- `isPaused()` / `isFunctionPaused()` - view the current pause status
-Abstract base contract for all mToken implementations
+### mTokens
+
+#### **mToken** ([`contracts/mToken.sol`](contracts/mToken.sol))
+
+Base contract for all mToken implementations
**Key Features:**
- Minting and burning functionality (role-protected)
-- Pausable transfers (inherited from `ERC20PausableUpgradeable`)
+- Pausable transfers - transfers are gated by the global [`MidasPauseManager`](contracts/access/MidasPauseManager.sol) (function-level pause), on top of `ERC20PausableUpgradeable`
- Blacklist functionality - blacklisted users cannot send or receive any tokens
+- Configurable maximum supply cap (`maxSupplyCap`), enforced on every mint
+- Per-token mint rate limits (windowed)
+- Optional permissioned transfers and minimum holding balance enforcement (toggled via flags, see below)
+- Clawback of tokens by a contract admin to a configured `clawbackReceiver`
**Key Functions:**
- `mint(address to, uint256 amount)` - Mint tokens to any address (requires minter role)
- `burn(address from, uint256 amount)` - Burn tokens from any address (requires burner role)
-- `pause()` / `unpause()` - Pause/unpause transfers (requires pauser role)
+- `mintGoverned()` / `burnGoverned()` - Mint/burn variants callable by the contract admin
+- `clawback(uint256 amount, address from)` - Move tokens from an address to the `clawbackReceiver` (requires contract admin)
+- `setMaxSupplyCap()`, `increaseMintRateLimit()` / `decreaseMintRateLimit()` - Supply cap and rate limit management
+
+**Configurable behavior (single contract, no separate variations):**
+
+Permissioning is a built-in flag rather than a separate contract:
+
+- `isPermissioned` - when enabled, transfers are only allowed between greenlisted addresses. Toggled via `setIsPermissioned()`.
+- `isMinHoldingBalanceEnforced` - when enabled, non-exempt holders must keep a minimum balance (or zero). Toggled via `setMinHoldingBalanceEnforced()`.
+
+### Data Feeds and Aggregators
+
+Vaults need prices to compute exchange rates. The protocol separates this into two layers: **aggregators** that publish or expose a price using the Chainlink `AggregatorV3` interface, and **data feeds** that wrap an aggregator, validate the price and convert it to a common 18 decimals format.
-### **Vaults**
+#### **DataFeed** ([`contracts/feeds/DataFeed.sol`](contracts/feeds/DataFeed.sol))
+
+Wraps a Chainlink `AggregatorV3` price feed, validates the price (max/min/staleness) and converts answers to 18 decimals format
+
+**Key Functions:**
+
+- `getDataInBase18()` - View function, returns the validated and converted price with 18 decimals. Checks price for min/max allowed values, checks that it is not stale
+
+**DataFeed Variations:**
-There are 2 types of vaults - Deposit vaults and Redemption vaults. Also each type of vault have different variations (like USTB, Swapper, BUIDL etc.)
+- **CompositeDataFeed** ([`contracts/feeds/CompositeDataFeed.sol`](contracts/feeds/CompositeDataFeed.sol)) - Computes the ratio of two underlying data feeds (numerator ÷ denominator).
+- **CompositeDataFeedMultiply** ([`contracts/feeds/CompositeDataFeedMultiply.sol`](contracts/feeds/CompositeDataFeedMultiply.sol)) - Computes the product of two underlying data feeds (numerator × denominator).
+
+#### **CustomAggregatorV3CompatibleFeed** ([`contracts/feeds/CustomAggregatorV3CompatibleFeed.sol`](contracts/feeds/CustomAggregatorV3CompatibleFeed.sol))
+
+Custom price aggregator compatible with Chainlink's `AggregatorV3` interface. Used to publish mToken prices on-chain
+
+**Key Functions:**
+
+- `setRoundData()` - function to push the price on-chain
+- `setRoundDataSafe()` - same as `setRoundData()` but also performs a deviation check by comparing current and new prices
+- `latestRoundData()` - View function, returns latest submitted price with submission details (check [AggregatorV3Interface.sol](@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol))
+
+**CustomAggregatorV3CompatibleFeed Variations:**
+
+- **CustomAggregatorV3CompatibleFeedGrowth** ([`contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol`](contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol)) - Includes a growth parameter that automatically increases the feed price over time.
+- **CustomAggregatorV3CompatibleFeedAdjusted** ([`contracts/feeds/CustomAggregatorV3CompatibleFeedAdjusted.sol`](contracts/feeds/CustomAggregatorV3CompatibleFeedAdjusted.sol)) - A proxy feed that adjusts the price of an underlying Chainlink compatible feed by a signed percentage. A positive percentage raises the reported price, a negative percentage lowers it.
+
+### Vaults
+
+There are 2 types of vaults - Deposit vaults and Redemption vaults. Also each type of vault have different variations (like USTB, Swapper etc.)
**Common Key Features:**
@@ -217,9 +329,9 @@ Both vaults support two execution modes:
- Two-step process
- User submits request on-chain
- Admin approves or rejects after off-chain processing
-- Used when instant liquidity is insufficient or instant operations are disabled. Also for fiat operations
+- Used when instant liquidity is insufficient or instant operations are disabled
-### **DepositVault** ([`contracts/DepositVault.sol`](contracts/DepositVault.sol))
+#### **DepositVault** ([`contracts/DepositVault.sol`](contracts/DepositVault.sol))
Manages the minting process for mTokens. Users deposit payment tokens to receive mTokens.
@@ -227,7 +339,7 @@ Manages the minting process for mTokens. Users deposit payment tokens to receive
- Instant deposits - minting happens atomically
- Request-based deposits - user pays in one transaction and tokens are minted in a second tx initiated by vault admin
-- Supply cap management
+- Supply cap enforcement - deposits are validated against the mToken's `maxSupplyCap` (the cap lives on the token, not the vault)
- Minimal mTokens amount for first mint transaction
**Key Functions:**
@@ -239,9 +351,12 @@ Manages the minting process for mTokens. Users deposit payment tokens to receive
**Vault Variations:**
-- USTB ([`contracts/DepositVaultWithUSTB.sol`](contracts/DepositVaultWithUSTB.sol)) - Automatically invests USDC into USTB tokens before transferring proceeds to the recipient.
+- **USTB** ([`contracts/DepositVaultWithUSTB.sol`](contracts/DepositVaultWithUSTB.sol)) - Automatically invests USDC into USTB tokens before transferring proceeds to the recipient.
+- **Aave** ([`contracts/DepositVaultWithAave.sol`](contracts/DepositVaultWithAave.sol)) - Invests deposited proceeds into an Aave V3 Pool.
+- **Morpho** ([`contracts/DepositVaultWithMorpho.sol`](contracts/DepositVaultWithMorpho.sol)) - Invests deposited proceeds into a Morpho Vault.
+- **mToken** ([`contracts/DepositVaultWithMToken.sol`](contracts/DepositVaultWithMToken.sol)) - Invests deposited proceeds into another mToken.
-### **RedemptionVault** ([`contracts/RedemptionVault.sol`](contracts/RedemptionVault.sol))
+#### **RedemptionVault** ([`contracts/RedemptionVault.sol`](contracts/RedemptionVault.sol))
Manages the redemption process for mTokens. Burns mTokens from a user and transfers payment tokens in exchange
@@ -249,87 +364,59 @@ Manages the redemption process for mTokens. Burns mTokens from a user and transf
- Instant redemptions - user receive payment tokens atomically
- Request-based redemptions - tokens are burned from user in one transaction and payment tokens are transferred in a second tx initiated by vault admin
-- Fiat redemption support
- Separate liquidity source for request-based operations
**Key Functions:**
- `redeemInstant()` - Instant redemption with immediate token transfer
- `redeemRequest()` - Create redemption request
-- `redeemFiatRequest()` - Create fiat redemption request
- `approveRequest()` - Admin fulfills redemption request
- `rejectRequest()` - Admin rejects redemption request
**Vault Variations:**
-- Swapper ([`contracts/RedemptionVaultWithSwapper.sol`](contracts/RedemptionVaultWithSwapper.sol)) - Uses an external liquidity source to exchange one mToken for another and redeems the obtained mTokens through a different Midas redemption vault. This flow is activated only when there is insufficient liquidity in the current Redemption Vault.
-- BUIDL ([`contracts/RedemptionVaultWithBUIDL.sol`](contracts/RedemptionVaultWithBUIDL.sol)) (*deprecated*) - Stores pending liquidity as BUIDL tokens. When the vault has insufficient USDC liquidity to fulfill an instant redemption, BUIDL tokens are redeemed for USDC and used to complete the redemption.
-- USTB ([`contracts/RedemptionVaultWithUSTB.sol`](contracts/RedemptionVaultWithUSTB.sol)) - Stores pending liquidity as USTB tokens. When the vault has insufficient USDC liquidity to fulfill an instant redemption, USTB tokens are redeemed for USDC and used to complete the redemption.
+- **USTB** ([`contracts/RedemptionVaultWithUSTB.sol`](contracts/RedemptionVaultWithUSTB.sol)) - Stores pending liquidity as USTB tokens. When the vault has insufficient USDC liquidity to fulfill an instant redemption, USTB tokens are redeemed for USDC and used to complete the redemption.
+- **Aave** ([`contracts/RedemptionVaultWithAave.sol`](contracts/RedemptionVaultWithAave.sol)) - Sources liquidity by withdrawing from an Aave V3 Pool.
+- **Morpho** ([`contracts/RedemptionVaultWithMorpho.sol`](contracts/RedemptionVaultWithMorpho.sol)) - Sources liquidity by withdrawing from a Morpho Vault.
+- **mToken** ([`contracts/RedemptionVaultWithMToken.sol`](contracts/RedemptionVaultWithMToken.sol)) - Uses an external liquidity source to exchange one mToken for another and redeems the obtained mTokens through a different Midas redemption vault. This flow is activated only when there is insufficient liquidity in the current Redemption Vault. (This contract replaces the former `RedemptionVaultWithSwapper`; its storage layout is preserved for safe upgrades.)
-### **DataFeed** ([`contracts/feeds/DataFeed.sol`](contracts/feeds/DataFeed.sol))
+### Miscellaneous Contracts
-Wraps Chainlink AggregatorV3 price feeds, validates the price (max/min/staleness) and converts answers to 18 decimals format
+These contracts are supporting pieces. Most of them do not hold protocol state or user funds. They connect Midas to external price oracles and to other chains.
-**Key Functions:**
+#### Data Feed Adapters (`contracts/misc/adapters/`)
-- `getDataInBase18()`- View function, returns the validated and converted price with 18 decimals. Checks price for min/max allowed values, checks that its not stale
+Adapters expose third-party price sources through the Chainlink `AggregatorV3` interface, so they can be consumed by the protocol `DataFeed` contracts in a uniform way. They share a common base ([`ChainlinkAdapterBase.sol`](contracts/misc/adapters/ChainlinkAdapterBase.sol)). Available adapters include:
-**DataFeed Variations:**
-
-- CompositeDataFeed ([`contracts/feeds/CompositeDataFeed.sol`](contracts/feeds/CompositeDataFeed.sol)) - computing the ratio of two underlying data feeds (numerator ÷ denominator)
+- Generic protocol ports: `PythChainlinkAdapter`, `StorkChainlinkAdapter`, `BandStdChailinkAdapter`
+- ERC4626 vaults: `ERC4626ChainlinkAdapter`
+- Liquid staking and specific assets: `WstEthChainlinkAdapter`, `WrappedEEthChainlinkAdapter`, `RsEthChainlinkAdapter`, `BeHypeChainlinkAdapter`, `MantleLspStakingChainlinkAdapter`, `SyrupChainlinkAdapter`, `YInjChainlinkAdapter`
+- Cross-format bridges: `DataFeedToBandStdAdapter`, `CompositeDataFeedToBandStdAdapter`
-### **CustomAggregatorV3CompatibleFeed** ([`contracts/feeds/CustomAggregatorV3CompatibleFeed.sol`](contracts/feeds/CustomAggregatorV3CompatibleFeed.sol))
+#### Acre Adapter ([`contracts/misc/acre/AcreAdapter.sol`](contracts/misc/acre/AcreAdapter.sol))
-Custom price aggregator compatible with Chainlink's AggregatorV3 interface. Used to publish mToken prices on-chain
-
-**Key Functions:**
+Wrapper around Midas vaults so they can be used by the Acre protocol.
-- `setRoundData()`- function to push the price on-chain
-- `setRoundDataSafe()`- same as `setRoundData()` but also performs a deviation check by comparing current and new prices
-- `latestRoundData()` - View function, returns latest submitted price with submission details (check [AggregatorV3Interface.sol](@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol))
-
-**CustomAggregatorV3CompatibleFeed Variations:**
+#### Cross-Chain Contracts (Axelar, LayerZero)
-- CustomAggregatorV3CompatibleFeedGrowth ([`contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol`](contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol)) - includes a growth parameter that automatically increases the feed price over time
-
-## Crosschain Contracts (Axelar, LayerZero)
-
-### **LayerZero Integration** (`contracts/misc/layerzero/`)
+**LayerZero Integration** (`contracts/misc/layerzero/`)
Contracts for LayerZero cross-chain messaging:
+- `MidasLzOFT.sol` - Native OFT that mints/burns its own supply, deployed on remote chains
+- `MidasLzOFTAdapter.sol` - Lock/unlock OFT adapter wrapping an existing ERC20 on its home chain
- `MidasLzMintBurnOFTAdapter.sol` - OFT adapter that uses native mint/burn on mTokens
- `MidasLzVaultComposerSync.sol` - Vault composer for instant-only operations
-### **Axelar Integration** (`contracts/misc/axelar/`)
+**Axelar Integration** (`contracts/misc/axelar/`)
Contracts for Axelar cross-chain functionality:
- `MidasAxelarVaultExecutable.sol` - Executable contract for cross-chain operations
-## Deployed Contract Addresses
-
-All deployed contract addresses are stored in [`config/constants/addresses.ts`](./config/constants/addresses.ts).
-
-The addresses are organized by network and token. For example:
-
-```typescript
-main: {
- accessControl: '0x0312A9D1Ff2372DDEdCBB21e4B6389aFc919aC4B',
- timelock: '0xE3EEe3e0D2398799C884a47FC40C029C8e241852',
- mTBILL: {
- token: '0xDD629E5241CbC5919847783e6C96B2De4754e438',
- depositVault: '0x99361435420711723aF805F08187c9E6bF796683',
- redemptionVault: '0xF6e51d24F4793Ac5e71e0502213a9BBE3A6d4517',
- // ...
- },
- // ...
-}
-```
-
## Deployment
-Please fefer to [this deployment README](./scripts/deploy/README.md)
+Please refer to [this deployment README](./scripts/deploy/README.md)
## Upgradability
@@ -391,3 +478,23 @@ yarn format:sol
yarn format:ts:fix
yarn format:sol:fix
```
+
+## Deployed Contract Addresses
+
+All deployed contract addresses are stored in [`config/constants/addresses.ts`](./config/constants/addresses.ts).
+
+The addresses are organized by network and token. For example:
+
+```typescript
+main: {
+ accessControl: '0x0312A9D1Ff2372DDEdCBB21e4B6389aFc919aC4B',
+ timelock: '0xE3EEe3e0D2398799C884a47FC40C029C8e241852',
+ mTBILL: {
+ token: '0xDD629E5241CbC5919847783e6C96B2De4754e438',
+ depositVault: '0x99361435420711723aF805F08187c9E6bF796683',
+ redemptionVault: '0xF6e51d24F4793Ac5e71e0502213a9BBE3A6d4517',
+ // ...
+ },
+ // ...
+}
+```
diff --git a/config/constants/addresses.ts b/config/constants/addresses.ts
index 9a3beb49..046d59ed 100644
--- a/config/constants/addresses.ts
+++ b/config/constants/addresses.ts
@@ -84,6 +84,9 @@ export type DataFeedAddresses =
export type MidasAddresses = Partial> & {
accessControl?: string;
timelock?: string;
+ timelockManager?: string;
+ timelockController?: string;
+ pauseManager?: string;
paymentTokens?: Partial>;
};
@@ -1621,6 +1624,11 @@ export const midasAddressesPerNetwork: ConfigPerNetwork<
},
},
sepolia: {
+ timelock: '0x74e0a55Ea3Db85F6106FFD69Ef7c9829fd130888',
+ pauseManager: '0xbf21e448410BAA6039e73033F530027143c0c280',
+ timelockManager: '0xc6Ad27a2446Aa6223512D9FF8A6f3440a20ccd12',
+ timelockController: '0x982550a433239C23BFe6C57005A7396D2Ed706d2',
+ accessControl: '0xbf25b58cB8DfaD688F7BcB2b87D71C23A6600AaC',
paymentTokens: {
usdc: {
dataFeed: '0x0e0eb6cdad90174f1Db606EC186ddD0B5eD80847',
@@ -1656,12 +1664,11 @@ export const midasAddressesPerNetwork: ConfigPerNetwork<
},
},
mTBILL: {
- dataFeed: '0x4E677F7FE252DE44682a913f609EA3eb6F29DC3E',
+ dataFeed: '0x9393811adDd7F7Ff60D1be11DbD29025A15bf630',
customFeedGrowth: '0x1E2165801d84865587252155Fb4580381f7A3FC4',
- depositVault: '0x1615cBC603192ae8A9FF20E98dd0e40a405d76e4',
- redemptionVault: '0x2fD18B0878967E19292E9a8BF38Bb1415F6ad653',
- redemptionVaultBuidl: '0x6B35F2E4C9D4c1da0eDaf7fd7Dc90D9bCa4b0873',
token: '0xefED40D1eb1577d1073e9C4F277463486D39b084',
+ depositVault: '0x2Abf7B5766Fc75bdb99e7aD76d6B539D08F3f8E1',
+ redemptionVault: '0x4e830D858c253C81ACF225E7101DF820D0e98415',
layerZero: {
oft: '0x0Ca81704F5df52E06205fe427653e661a4b6043c',
composers: {
@@ -1718,8 +1725,8 @@ export const midasAddressesPerNetwork: ConfigPerNetwork<
token: '0x19569a89fEf7276a7f5967b6F6910c0573616f07',
customFeed: '0xb64C014307622eB15046C66fF71D04258F5963DC',
dataFeed: '0xffd462e0602Dd9FF3F038fd4e77a533f8c474b65',
- depositVault: '0x56814399caaEDCEE4F58D2e55DA058A81DDE744f',
- redemptionVaultSwapper: '0xFeB770Ae942ef5ed377c6D4BbC50f9d3b25Cf69b',
+ depositVault: '0x95059CaD850a0531dd9b086F019C7f1ACB15955c',
+ redemptionVault: '0x95AACf1bAD48336B0a62E90C2799fA8623CD9128',
},
mFONE: {
token: '0x6Ee5Bcb946499a926332cdE1993986bE76BE58Ea',
@@ -1791,8 +1798,6 @@ export const midasAddressesPerNetwork: ConfigPerNetwork<
depositVault: '0x807f2CF75EC43b11De43a529A0Dd9FEF754a9801',
redemptionVaultSwapper: '0x313C76eCd990B728681f29464978D5637Cb78164',
},
- timelock: '0x74e0a55Ea3Db85F6106FFD69Ef7c9829fd130888',
- accessControl: '0xbf25b58cB8DfaD688F7BcB2b87D71C23A6600AaC',
},
arbitrumSepolia: {
paymentTokens: {
diff --git a/contracts/DepositVault.sol b/contracts/DepositVault.sol
index 65844480..e14d0a42 100644
--- a/contracts/DepositVault.sol
+++ b/contracts/DepositVault.sol
@@ -1,15 +1,14 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import {IERC20Upgradeable as IERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
-import {IERC20MetadataUpgradeable as IERC20Metadata} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol";
+import {SafeERC20Upgradeable as SafeERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
-import {Counters} from "@openzeppelin/contracts/utils/Counters.sol";
+import {IDepositVault, DepositVaultInitParams, Request} from "./interfaces/IDepositVault.sol";
+import {CommonVaultInitParams, RequestStatus} from "./interfaces/IManageableVault.sol";
-import "./interfaces/IDepositVault.sol";
-import "./interfaces/IDataFeed.sol";
-
-import "./abstract/ManageableVault.sol";
+import {ManageableVault} from "./abstract/ManageableVault.sol";
+import {Greenlistable} from "./access/Greenlistable.sol";
/**
* @title DepositVault
@@ -17,7 +16,7 @@ import "./abstract/ManageableVault.sol";
* @author RedDuck Software
*/
contract DepositVault is ManageableVault, IDepositVault {
- using Counters for Counters.Counter;
+ using SafeERC20 for IERC20;
/**
* @notice return data of _calcAndValidateDeposit
@@ -41,36 +40,15 @@ contract DepositVault is ManageableVault, IDepositVault {
}
/**
- * @dev default role that grants admin rights to the contract
- */
- bytes32 private constant _DEFAULT_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @dev selector for deposit instant
- */
- bytes4 private constant _DEPOSIT_INSTANT_SELECTOR =
- bytes4(keccak256("depositInstant(address,uint256,uint256,bytes32)"));
-
- /**
- * @dev selector for deposit instant with custom recipient
+ * @notice request data storage
*/
- bytes4 private constant _DEPOSIT_INSTANT_WITH_CUSTOM_RECIPIENT_SELECTOR =
- bytes4(
- keccak256("depositInstant(address,uint256,uint256,bytes32,address)")
- );
-
- /**
- * @dev selector for deposit request
- */
- bytes4 private constant _DEPOSIT_REQUEST_SELECTOR =
- bytes4(keccak256("depositRequest(address,uint256,bytes32)"));
+ mapping(uint256 => Request) public mintRequests;
/**
- * @dev selector for deposit request with custom recipient
+ * @dev how much mTokens were minted by the depositor
+ * @dev depositor address => amount minted
*/
- bytes4 private constant _DEPOSIT_REQUEST_WITH_CUSTOM_RECIPIENT_SELECTOR =
- bytes4(keccak256("depositRequest(address,uint256,bytes32,address)"));
+ mapping(address => uint256) public totalMinted;
/**
* @notice minimal USD amount for first user`s deposit
@@ -78,110 +56,45 @@ contract DepositVault is ManageableVault, IDepositVault {
uint256 public minMTokenAmountForFirstDeposit;
/**
- * @notice mapping, requestId => request data
+ * @notice max amount per request in mToken
*/
- mapping(uint256 => Request) public mintRequests;
+ uint256 public maxAmountPerRequest;
/**
- * @dev depositor address => amount minted
+ * @notice pending supply in mToken that will be released
+ * after the deposit request is processed
*/
- mapping(address => uint256) public totalMinted;
+ uint256 public upcomingSupply;
/**
- * @notice max supply cap value in mToken
- * @dev if after the deposit, mToken.totalSupply() > maxSupplyCap,
- * the tx will be reverted
+ * @dev leaving a storage gap for futures updates
*/
- uint256 public maxSupplyCap;
+ uint256[50] private __gap;
/**
- * @dev leaving a storage gap for futures updates
- *
- * used slots:
- * 50 - `maxSupplyCap`
+ * @notice Passes role identifiers to the base ManageableVault constructor
+ * @param _contractAdminRole contract admin role identifier
+ * @param _greenlistedRole greenlisted role identifier
+ * @custom:oz-upgrades-unsafe-allow constructor
*/
- uint256[49] private __gap;
+ constructor(bytes32 _contractAdminRole, bytes32 _greenlistedRole)
+ ManageableVault(_contractAdminRole, _greenlistedRole)
+ {}
/**
* @notice upgradeable pattern contract`s initializer
- * @dev Calls all versioned initializers (V1, V2, ...) in chronological order.
- * This ensures that every deployment, whether fresh or upgraded, ends up
- * initialized to the latest contract state without breaking the
- * initializer/reinitializer versioning rules.
- * @param _ac address of MidasAccessControll contract
- * @param _mTokenInitParams init params for mToken
- * @param _receiversInitParams init params for receivers
- * @param _instantInitParams init params for instant operations
- * @param _sanctionsList address of sanctionsList contract
- * @param _variationTolerance percent of prices diviation 1% = 100
- * @param _minAmount basic min amount for operations in mToken
- * @param _minMTokenAmountForFirstDeposit min amount for first deposit in mToken
- * @param _maxSupplyCap max supply cap for mToken
+ * @param _commonVaultInitParams init params for common vault
+ * @param _depositVaultInitParams init params for deposit vault
*/
function initialize(
- address _ac,
- MTokenInitParams calldata _mTokenInitParams,
- ReceiversInitParams calldata _receiversInitParams,
- InstantInitParams calldata _instantInitParams,
- address _sanctionsList,
- uint256 _variationTolerance,
- uint256 _minAmount,
- uint256 _minMTokenAmountForFirstDeposit,
- uint256 _maxSupplyCap
- ) public {
- _initializeV1(
- _ac,
- _mTokenInitParams,
- _receiversInitParams,
- _instantInitParams,
- _sanctionsList,
- _variationTolerance,
- _minAmount,
- _minMTokenAmountForFirstDeposit
- );
-
- _initializeV2(_maxSupplyCap);
- }
-
- /**
- * @notice v1 initializer
- * @param _ac address of MidasAccessControll contract
- * @param _mTokenInitParams init params for mToken
- * @param _receiversInitParams init params for receivers
- * @param _instantInitParams init params for instant operations
- * @param _sanctionsList address of sanctionsList contract
- * @param _variationTolerance percent of prices diviation 1% = 100
- * @param _minAmount basic min amount for operations in mToken
- * @param _minMTokenAmountForFirstDeposit min amount for first deposit in mToken
- */
- function _initializeV1(
- address _ac,
- MTokenInitParams calldata _mTokenInitParams,
- ReceiversInitParams calldata _receiversInitParams,
- InstantInitParams calldata _instantInitParams,
- address _sanctionsList,
- uint256 _variationTolerance,
- uint256 _minAmount,
- uint256 _minMTokenAmountForFirstDeposit
- ) private initializer {
- __ManageableVault_init(
- _ac,
- _mTokenInitParams,
- _receiversInitParams,
- _instantInitParams,
- _sanctionsList,
- _variationTolerance,
- _minAmount
- );
- minMTokenAmountForFirstDeposit = _minMTokenAmountForFirstDeposit;
- }
-
- /**
- * @notice v2 initializer
- * @param _maxSupplyCap max supply cap for mToken
- */
- function _initializeV2(uint256 _maxSupplyCap) private reinitializer(2) {
- maxSupplyCap = _maxSupplyCap;
+ CommonVaultInitParams calldata _commonVaultInitParams,
+ DepositVaultInitParams calldata _depositVaultInitParams
+ ) public initializer {
+ __ManageableVault_init(_commonVaultInitParams);
+
+ minMTokenAmountForFirstDeposit = _depositVaultInitParams
+ .minMTokenAmountForFirstDeposit;
+ maxAmountPerRequest = _depositVaultInitParams.maxAmountPerRequest;
}
/**
@@ -192,25 +105,21 @@ contract DepositVault is ManageableVault, IDepositVault {
uint256 amountToken,
uint256 minReceiveAmount,
bytes32 referrerId
- ) external whenFnNotPaused(_DEPOSIT_INSTANT_SELECTOR) {
- _validateUserAccess(msg.sender);
-
- CalcAndValidateDepositResult memory result = _depositInstant(
- tokenIn,
- amountToken,
- minReceiveAmount,
- msg.sender
- );
-
- emit DepositInstant(
- msg.sender,
- tokenIn,
- result.tokenAmountInUsd,
- amountToken,
- result.feeTokenAmount,
- result.mintAmount,
- referrerId
- );
+ )
+ external
+ returns (
+ uint256 /* mintAmount */
+ )
+ {
+ return
+ _depositInstantWithCustomRecipient(
+ tokenIn,
+ amountToken,
+ minReceiveAmount,
+ referrerId,
+ msg.sender,
+ ONE_HUNDRED_PERCENT
+ ).mintAmount;
}
/**
@@ -224,31 +133,19 @@ contract DepositVault is ManageableVault, IDepositVault {
address recipient
)
external
- whenFnNotPaused(_DEPOSIT_INSTANT_WITH_CUSTOM_RECIPIENT_SELECTOR)
+ returns (
+ uint256 /* mintAmount */
+ )
{
- _validateUserAccess(msg.sender);
-
- if (recipient != msg.sender) {
- _validateUserAccess(recipient);
- }
-
- CalcAndValidateDepositResult memory result = _depositInstant(
- tokenIn,
- amountToken,
- minReceiveAmount,
- recipient
- );
-
- emit DepositInstantWithCustomRecipient(
- msg.sender,
- tokenIn,
- recipient,
- result.tokenAmountInUsd,
- amountToken,
- result.feeTokenAmount,
- result.mintAmount,
- referrerId
- );
+ return
+ _depositInstantWithCustomRecipient(
+ tokenIn,
+ amountToken,
+ minReceiveAmount,
+ referrerId,
+ recipient,
+ ONE_HUNDRED_PERCENT
+ ).mintAmount;
}
/**
@@ -258,32 +155,16 @@ contract DepositVault is ManageableVault, IDepositVault {
address tokenIn,
uint256 amountToken,
bytes32 referrerId
- )
- external
- whenFnNotPaused(_DEPOSIT_REQUEST_SELECTOR)
- returns (
- uint256 /*requestId*/
- )
- {
- _validateUserAccess(msg.sender);
-
- (
- uint256 requestId,
- CalcAndValidateDepositResult memory calcResult
- ) = _depositRequest(tokenIn, amountToken, msg.sender);
-
- emit DepositRequest(
- requestId,
- msg.sender,
+ ) external returns (uint256 requestId) {
+ (requestId, ) = _depositRequestWithCustomRecipient(
tokenIn,
amountToken,
- calcResult.tokenAmountInUsd,
- calcResult.feeTokenAmount,
- calcResult.tokenOutRate,
- referrerId
+ referrerId,
+ msg.sender,
+ 0,
+ 0,
+ msg.sender
);
-
- return requestId;
}
/**
@@ -293,40 +174,27 @@ contract DepositVault is ManageableVault, IDepositVault {
address tokenIn,
uint256 amountToken,
bytes32 referrerId,
- address recipient
+ address recipientRequest,
+ uint256 instantShare,
+ uint256 minReceiveAmountInstantShare,
+ address recipientInstant
)
external
- whenFnNotPaused(_DEPOSIT_REQUEST_WITH_CUSTOM_RECIPIENT_SELECTOR)
returns (
- uint256 /*requestId*/
+ uint256, /*requestId*/
+ uint256 /* instantMintAmount */
)
{
- _validateUserAccess(msg.sender);
-
- if (recipient != msg.sender) {
- _validateUserAccess(recipient);
- }
-
- (
- uint256 requestId,
- CalcAndValidateDepositResult memory calcResult
- ) = _depositRequest(tokenIn, amountToken, recipient);
-
- bytes32 referrerIdCopy = referrerId;
-
- emit DepositRequestWithCustomRecipient(
- requestId,
- msg.sender,
- tokenIn,
- recipient,
- amountToken,
- calcResult.tokenAmountInUsd,
- calcResult.feeTokenAmount,
- calcResult.tokenOutRate,
- referrerIdCopy
- );
-
- return requestId;
+ return
+ _depositRequestWithCustomRecipient(
+ tokenIn,
+ amountToken,
+ referrerId,
+ recipientRequest,
+ instantShare,
+ minReceiveAmountInstantShare,
+ recipientInstant
+ );
}
/**
@@ -334,18 +202,8 @@ contract DepositVault is ManageableVault, IDepositVault {
*/
function safeBulkApproveRequestAtSavedRate(uint256[] calldata requestIds)
external
- onlyVaultAdmin
{
- for (uint256 i = 0; i < requestIds.length; i++) {
- uint256 rate = mintRequests[requestIds[i]].tokenOutRate;
- bool success = _approveRequest(requestIds[i], rate, true, false);
-
- if (!success) {
- continue;
- }
-
- emit SafeApproveRequest(requestIds[i], rate);
- }
+ _safeBulkApproveRequest(requestIds, 0, true, false);
}
/**
@@ -353,48 +211,67 @@ contract DepositVault is ManageableVault, IDepositVault {
*/
function safeBulkApproveRequest(uint256[] calldata requestIds) external {
uint256 currentMTokenRate = _getMTokenRate();
- safeBulkApproveRequest(requestIds, currentMTokenRate);
+ _safeBulkApproveRequest(requestIds, currentMTokenRate, false, false);
}
/**
* @inheritdoc IDepositVault
*/
- function safeApproveRequest(uint256 requestId, uint256 newOutRate)
+ function safeBulkApproveRequestAvgRate(uint256[] calldata requestIds)
external
- onlyVaultAdmin
{
- _approveRequest(requestId, newOutRate, true, true);
+ uint256 currentMTokenRate = _getMTokenRate();
+ _safeBulkApproveRequest(requestIds, currentMTokenRate, false, true);
+ }
- emit SafeApproveRequest(requestId, newOutRate);
+ /**
+ * @inheritdoc IDepositVault
+ */
+ function safeBulkApproveRequest(
+ uint256[] calldata requestIds,
+ uint256 newOutRate
+ ) external {
+ _safeBulkApproveRequest(requestIds, newOutRate, false, false);
}
/**
* @inheritdoc IDepositVault
*/
- function approveRequest(uint256 requestId, uint256 newOutRate)
- external
- onlyVaultAdmin
- {
- _approveRequest(requestId, newOutRate, false, true);
+ function safeBulkApproveRequestAvgRate(
+ uint256[] calldata requestIds,
+ uint256 avgMTokenRate
+ ) external {
+ _safeBulkApproveRequest(requestIds, avgMTokenRate, false, true);
+ }
- emit ApproveRequest(requestId, newOutRate);
+ /**
+ * @inheritdoc IDepositVault
+ */
+ function approveRequest(
+ uint256 requestId,
+ uint256 newOutRate,
+ bool isAvgRate
+ ) external onlyContractAdmin {
+ _approveRequest(requestId, newOutRate, false, isAvgRate);
}
/**
* @inheritdoc IDepositVault
*/
- function rejectRequest(uint256 requestId) external onlyVaultAdmin {
+ function rejectRequest(uint256 requestId) external onlyContractAdmin {
Request memory request = mintRequests[requestId];
- require(request.sender != address(0), "DV: request not exist");
- require(
- request.status == RequestStatus.Pending,
- "DV: request not pending"
- );
+ _validateRequest(requestId, request.recipient, request.status);
+ _validateAndUpdateNextRequestIdToProcess(requestId, true);
mintRequests[requestId].status = RequestStatus.Canceled;
- emit RejectRequest(requestId, request.sender);
+ upcomingSupply -= _quoteMTokenFromRequest(
+ request,
+ request.tokenOutRate
+ );
+
+ emit RejectRequest(requestId);
}
/**
@@ -402,63 +279,98 @@ contract DepositVault is ManageableVault, IDepositVault {
*/
function setMinMTokenAmountForFirstDeposit(uint256 newValue)
external
- onlyVaultAdmin
+ onlyContractAdmin
{
minMTokenAmountForFirstDeposit = newValue;
- emit SetMinMTokenAmountForFirstDeposit(msg.sender, newValue);
+ emit SetMinMTokenAmountForFirstDeposit(newValue);
}
/**
* @inheritdoc IDepositVault
*/
- function setMaxSupplyCap(uint256 newValue) external onlyVaultAdmin {
- maxSupplyCap = newValue;
+ function setMaxAmountPerRequest(uint256 newValue)
+ external
+ onlyContractAdmin
+ {
+ maxAmountPerRequest = newValue;
- emit SetMaxSupplyCap(msg.sender, newValue);
+ emit SetMaxAmountPerRequest(newValue);
}
/**
- * @inheritdoc IDepositVault
+ * @notice calculates effective mToken supply including upcoming supply
+ * @return effective mToken supply
*/
- function safeBulkApproveRequest(
- uint256[] calldata requestIds,
- uint256 newOutRate
- ) public onlyVaultAdmin {
- for (uint256 i = 0; i < requestIds.length; i++) {
- bool success = _approveRequest(
- requestIds[i],
- newOutRate,
- true,
- false
- );
-
- if (!success) {
- continue;
- }
-
- emit SafeApproveRequest(requestIds[i], newOutRate);
- }
+ function getEffectiveMTokenSupply() external view returns (uint256) {
+ return _getEffectiveMTokenSupply();
}
/**
- * @inheritdoc ManageableVault
+ * @dev internal function to approve requests
+ * @param requestIds request ids
+ * @param newOutRate new out rate
+ * @param isRequestRate if true, newOutRate will be ignored and request rate will be used
+ * @param isAvgRate if true, newOutRate is avg rate
*/
- function vaultRole() public pure virtual override returns (bytes32) {
- return _DEFAULT_DEPOSIT_VAULT_ADMIN_ROLE;
+ function _safeBulkApproveRequest(
+ uint256[] calldata requestIds,
+ uint256 newOutRate,
+ bool isRequestRate,
+ bool isAvgRate
+ ) private onlyContractAdmin {
+ for (uint256 i = 0; i < requestIds.length; ++i) {
+ if (isRequestRate) {
+ newOutRate = mintRequests[requestIds[i]].tokenOutRate;
+ }
+ _approveRequest(requestIds[i], newOutRate, true, isAvgRate);
+ }
}
/**
- * @inheritdoc Greenlistable
+ * @dev internal deposit instant logic with custom recipient
+ * @param tokenIn tokenIn address
+ * @param amountToken amount of tokenIn (decimals 18)
+ * @param minReceiveAmount min amount of mToken to receive (decimals 18)
+ * @param referrerId referrer id
+ * @param recipient recipient address
+ * @param instantShareToValidate % amount of `amountToken` that will be deposited instantly
*/
- function greenlistTogglerRole()
- public
- view
- virtual
- override
- returns (bytes32)
+ function _depositInstantWithCustomRecipient(
+ address tokenIn,
+ uint256 amountToken,
+ uint256 minReceiveAmount,
+ bytes32 referrerId,
+ address recipient,
+ uint256 instantShareToValidate
+ )
+ private
+ validateUserAccess(recipient)
+ returns (CalcAndValidateDepositResult memory result)
{
- return vaultRole();
+ require(
+ instantShareToValidate <= maxInstantShare,
+ InstantShareTooHigh(instantShareToValidate, maxInstantShare)
+ );
+
+ result = _depositInstant(
+ tokenIn,
+ amountToken,
+ minReceiveAmount,
+ recipient
+ );
+
+ emit DepositInstant(
+ msg.sender,
+ tokenIn,
+ recipient,
+ amountToken,
+ result.feeTokenAmount,
+ result.mintAmount,
+ result.tokenOutRate,
+ result.tokenInRate,
+ referrerId
+ );
}
/**
@@ -480,10 +392,7 @@ contract DepositVault is ManageableVault, IDepositVault {
result = _calcAndValidateDeposit(user, tokenIn, amountToken, true);
- require(
- result.mintAmount >= minReceiveAmount,
- "DV: minReceiveAmount > actual"
- );
+ _requireSlippageNotExceeded(result.mintAmount, minReceiveAmount);
totalMinted[user] += result.mintAmount;
@@ -491,73 +400,155 @@ contract DepositVault is ManageableVault, IDepositVault {
_instantTransferTokensToTokensReceiver(
tokenIn,
- result.amountTokenWithoutFee,
+ amountToken,
result.tokenDecimals
);
- if (result.feeTokenAmount > 0)
- _tokenTransferFromUser(
- tokenIn,
- feeReceiver,
- result.feeTokenAmount,
- result.tokenDecimals
- );
-
mToken.mint(recipient, result.mintAmount);
_validateMaxSupplyCap(true);
}
/**
- * @dev internal deposit request logic
+ * @dev internal deposit request logic with custom recipient
* @param tokenIn tokenIn address
* @param amountToken amount of tokenIn (decimals 18)
- * @param recipient recipient address
- *
+ * @param referrerId referrer id
+ * @param recipientRequest recipient address for the request part
+ * @param instantShare % amount of `amountToken` that will be deposited instantly
+ * @param minReceiveAmountInstantShare min amount of mToken to receive for the instant share
+ * @param recipientInstant recipient address for the instant part
* @return requestId request id
- * @return calcResult calculated deposit result
*/
- function _depositRequest(
+ function _depositRequestWithCustomRecipient(
address tokenIn,
uint256 amountToken,
- address recipient
+ bytes32 referrerId,
+ address recipientRequest,
+ uint256 instantShare,
+ uint256 minReceiveAmountInstantShare,
+ address recipientInstant
)
private
+ validateUserAccess(recipientRequest)
returns (
- uint256 requestId,
- CalcAndValidateDepositResult memory calcResult
+ uint256, /* requestId */
+ uint256 /* instantMintAmount */
)
{
+ uint256 amountTokenInstant = _truncate(
+ (amountToken * instantShare) / ONE_HUNDRED_PERCENT,
+ _tokenDecimals(tokenIn)
+ );
+
+ CalcAndValidateDepositResult memory instantResult;
+ if (amountTokenInstant > 0) {
+ instantResult = _depositInstantWithCustomRecipient(
+ tokenIn,
+ amountTokenInstant,
+ minReceiveAmountInstantShare,
+ referrerId,
+ recipientInstant,
+ instantShare
+ );
+ }
+
+ uint256 amountTokenRequest = amountToken - amountTokenInstant;
+
+ return (
+ _depositRequest(
+ tokenIn,
+ amountTokenRequest,
+ recipientRequest,
+ referrerId,
+ amountTokenInstant,
+ instantResult.tokenAmountInUsd
+ ),
+ instantResult.mintAmount
+ );
+ }
+
+ /**
+ * @dev internal deposit request logic
+ * @param tokenIn tokenIn address
+ * @param amountToken amount of tokenIn (decimals 18)
+ * @param recipient recipient address
+ * @param referrerId referrer id
+ * @param depositedInstantAmount amount of tokenIn that was deposited instantly (decimals 18)
+ * @param depositedInstantUsdAmount amount of tokenIn that was deposited instantly in USD
+
+ * @return requestId request id
+ */
+ function _depositRequest(
+ address tokenIn,
+ uint256 amountToken,
+ address recipient,
+ bytes32 referrerId,
+ uint256 depositedInstantAmount,
+ uint256 depositedInstantUsdAmount
+ ) private returns (uint256 requestId) {
address user = msg.sender;
- requestId = currentRequestId.current();
- currentRequestId.increment();
+ requestId = currentRequestId++;
- calcResult = _calcAndValidateDeposit(user, tokenIn, amountToken, false);
+ CalcAndValidateDepositResult
+ memory calcResult = _calcAndValidateDeposit(
+ user,
+ tokenIn,
+ amountToken,
+ false
+ );
_requestTransferTokensToTokensReceiver(
tokenIn,
- calcResult.amountTokenWithoutFee,
+ calcResult.amountTokenWithoutFee + calcResult.feeTokenAmount,
calcResult.tokenDecimals
);
- if (calcResult.feeTokenAmount > 0)
- _tokenTransferFromUser(
- tokenIn,
- feeReceiver,
- calcResult.feeTokenAmount,
- calcResult.tokenDecimals
+ // prevents stack too deep error
+ {
+ Request memory request = Request({
+ recipient: recipient,
+ tokenIn: tokenIn,
+ status: RequestStatus.Pending,
+ depositedUsdAmount: calcResult.tokenAmountInUsd,
+ usdAmountWithoutFees: (calcResult.amountTokenWithoutFee *
+ calcResult.tokenInRate) / 10**18,
+ tokenOutRate: calcResult.tokenOutRate,
+ depositedInstantUsdAmount: depositedInstantUsdAmount,
+ approvedTokenOutRate: 0,
+ amountMToken: 0
+ });
+
+ mintRequests[requestId] = request;
+
+ uint256 estimatedMintAmount = _quoteMTokenFromRequest(
+ request,
+ request.tokenOutRate
+ );
+
+ require(
+ estimatedMintAmount <= maxAmountPerRequest,
+ MaxAmountPerRequestExceeded(estimatedMintAmount)
);
- mintRequests[requestId] = Request({
- sender: recipient,
- tokenIn: tokenIn,
- status: RequestStatus.Pending,
- depositedUsdAmount: calcResult.tokenAmountInUsd,
- usdAmountWithoutFees: (calcResult.amountTokenWithoutFee *
- calcResult.tokenInRate) / 10**18,
- tokenOutRate: calcResult.tokenOutRate
- });
+ upcomingSupply += estimatedMintAmount;
+ }
+
+ _validateMaxSupplyCap(true);
+
+ emit DepositRequest(
+ requestId,
+ msg.sender,
+ tokenIn,
+ recipient,
+ amountToken,
+ depositedInstantAmount,
+ calcResult.feeTokenAmount,
+ calcResult.tokenOutRate,
+ calcResult.tokenInRate,
+ referrerId
+ );
}
/**
@@ -566,45 +557,75 @@ contract DepositVault is ManageableVault, IDepositVault {
* Mints mTokens to user
* @param requestId request id
* @param newOutRate mToken rate
+ * @param isSafe if true:
+ * - safely validates max approve request id
+ * - safely validates if request id is sequential
+ * - safely validates max supply cap
+ * - requires variation tolerance
+ * @param isAvgRate if true, newOutRate is avg rate
*/
function _approveRequest(
uint256 requestId,
uint256 newOutRate,
bool isSafe,
- bool revertAboveSupplyCap
- )
- private
- returns (
- bool /* success */
- )
- {
+ bool isAvgRate
+ ) private {
Request memory request = mintRequests[requestId];
- require(request.sender != address(0), "DV: request not exist");
- require(
- request.status == RequestStatus.Pending,
- "DV: request not pending"
- );
+ _validateRequest(requestId, request.recipient, request.status);
+ _validateUserAccess(request.recipient, false);
- if (isSafe)
+ if (isSafe) {
_requireVariationTolerance(request.tokenOutRate, newOutRate);
+ }
- uint256 amountMToken = (request.usdAmountWithoutFees * (10**18)) /
- newOutRate;
+ if (isAvgRate) {
+ uint256 avgRate = _calculateHoldbackPartRateFromAvg(
+ request,
+ newOutRate
+ );
- if (!_validateMaxSupplyCap(amountMToken, revertAboveSupplyCap)) {
- return false;
+ if (request.depositedInstantUsdAmount > 0) {
+ require(avgRate > 0, InvalidAvgRate());
+ }
+
+ if (avgRate != 0) {
+ newOutRate = avgRate;
+ }
}
- mToken.mint(request.sender, amountMToken);
+ require(newOutRate > 0, InvalidNewMTokenRate());
- totalMinted[request.sender] += amountMToken;
+ uint256 amountMToken = _quoteMTokenFromRequest(request, newOutRate);
+ uint256 upcomingSupplyDecrease = _quoteMTokenFromRequest(
+ request,
+ request.tokenOutRate
+ );
+
+ if (
+ !_validateMaxSupplyCap(
+ upcomingSupplyDecrease,
+ amountMToken,
+ !isSafe
+ ) || !_validateAndUpdateNextRequestIdToProcess(requestId, !isSafe)
+ ) {
+ return;
+ }
+
+ upcomingSupply -= upcomingSupplyDecrease;
+
+ mToken.mint(request.recipient, amountMToken);
+
+ totalMinted[request.recipient] += amountMToken;
+
+ request.approvedTokenOutRate = newOutRate;
+ request.amountMToken = amountMToken;
request.status = RequestStatus.Processed;
- request.tokenOutRate = newOutRate;
+
mintRequests[requestId] = request;
- return true;
+ emit ApproveRequest(requestId, newOutRate, isSafe, isAvgRate);
}
/**
@@ -645,6 +666,26 @@ contract DepositVault is ManageableVault, IDepositVault {
);
}
+ /**
+ * @notice validates request
+ * if exist
+ * if status is expected
+ * @param requestId request id
+ * @param validateAddress address to check if not zero
+ * @param status request status
+ */
+ function _validateRequest(
+ uint256 requestId,
+ address validateAddress,
+ RequestStatus status
+ ) internal pure {
+ require(validateAddress != address(0), RequestNotExists(requestId));
+ require(
+ status == RequestStatus.Pending,
+ UnexpectedRequestStatus(requestId, status)
+ );
+ }
+
/**
* @dev validate deposit and calculate mint amount
* @param user user address
@@ -660,7 +701,9 @@ contract DepositVault is ManageableVault, IDepositVault {
uint256 amountToken,
bool isInstant
) internal returns (CalcAndValidateDepositResult memory result) {
- require(amountToken > 0, "DV: invalid amount");
+ require(amountToken > 0, InvalidAmount());
+
+ _validateInstantFee();
result.tokenDecimals = _tokenDecimals(tokenIn);
@@ -677,9 +720,10 @@ contract DepositVault is ManageableVault, IDepositVault {
_requireAndUpdateAllowance(tokenIn, amountToken);
result.feeTokenAmount = _truncate(
- _getFeeAmount(userCopy, tokenIn, amountToken, isInstant, 0),
+ _getFeeAmount(_getFee(userCopy, tokenIn, isInstant), amountToken),
result.tokenDecimals
);
+
result.amountTokenWithoutFee = amountToken - result.feeTokenAmount;
uint256 feeInUsd = (result.feeTokenAmount * result.tokenInRate) /
@@ -691,34 +735,22 @@ contract DepositVault is ManageableVault, IDepositVault {
result.mintAmount = mTokenAmount;
result.tokenOutRate = mTokenRate;
- if (!isFreeFromMinAmount[userCopy]) {
- _validateMinAmount(userCopy, result.mintAmount);
+ if (
+ !_validateMTokenAmount(userCopy, result.mintAmount) &&
+ totalMinted[userCopy] == 0
+ ) {
+ require(
+ result.mintAmount >= minMTokenAmountForFirstDeposit,
+ LessThanMinAmountFirstDeposit(
+ result.mintAmount,
+ minMTokenAmountForFirstDeposit
+ )
+ );
}
- require(result.mintAmount > 0, "DV: invalid mint amount");
- }
-
- /**
- * @dev validates that inputted USD amount >= minAmountToDepositInUsd()
- * and amount >= minAmount()
- * @param user user address
- * @param amountMTokenWithoutFee amount of mToken without fee (decimals 18)
- */
- function _validateMinAmount(address user, uint256 amountMTokenWithoutFee)
- internal
- view
- {
- require(amountMTokenWithoutFee >= minAmount, "DV: mToken amount < min");
-
- if (totalMinted[user] != 0) return;
-
- require(
- amountMTokenWithoutFee >= minMTokenAmountForFirstDeposit,
- "DV: mint amount < min"
- );
}
/**
- * @dev validates that mToken.totalSupply() <= maxSupplyCap
+ * @dev validates that mToken.totalSupply() <= mToken.maxSupplyCap()
*
* @param revertOnError if true, will revert if supply is exceeded
* if false, will return false if supply is exceeded without reverting
@@ -730,30 +762,34 @@ contract DepositVault is ManageableVault, IDepositVault {
view
returns (bool)
{
- return _validateMaxSupplyCap(0, revertOnError);
+ return _validateMaxSupplyCap(0, 0, revertOnError);
}
/**
- * @dev validates that mToken.totalSupply() <= maxSupplyCap
+ * @dev validates that mToken.totalSupply() <= mToken.maxSupplyCap()
*
+ * @param requestEstimatedMintAmount estimated amount of mToken to mint from request
* @param mintAmount amount of mToken to mint
* @param revertOnError if true, will revert if supply is exceeded
* if false, will return false if supply is exceeded without reverting
*
* @return true if supply is valid, false otherwise
*/
- function _validateMaxSupplyCap(uint256 mintAmount, bool revertOnError)
- internal
- view
- returns (bool)
- {
- bool isExceeded = mToken.totalSupply() + mintAmount > maxSupplyCap;
+ function _validateMaxSupplyCap(
+ uint256 requestEstimatedMintAmount,
+ uint256 mintAmount,
+ bool revertOnError
+ ) private view returns (bool) {
+ bool isExceeded = _getEffectiveMTokenSupply() +
+ mintAmount -
+ requestEstimatedMintAmount >
+ mToken.maxSupplyCap();
if (!revertOnError) {
return !isExceeded;
}
- require(!isExceeded, "DV: max supply cap exceeded");
+ require(!isExceeded, SupplyCapExceeded());
return true;
}
@@ -772,12 +808,7 @@ contract DepositVault is ManageableVault, IDepositVault {
virtual
returns (uint256 amountInUsd, uint256 rate)
{
- require(amount > 0, "DV: amount zero");
-
- TokenConfig storage tokenConfig = tokensConfig[tokenIn];
-
- rate = _getTokenRate(tokenConfig.dataFeed, tokenConfig.stable);
- require(rate > 0, "DV: rate zero");
+ rate = _getPTokenRate(tokenIn);
amountInUsd = (amount * rate) / (10**18);
}
@@ -801,11 +832,58 @@ contract DepositVault is ManageableVault, IDepositVault {
}
/**
- * @dev gets and validates mToken rate
- * @return mTokenRate mToken rate
+ * @dev calculates holdback part rate from avg rate
+ * @param request request
+ * @param avgMTokenRate avg mToken rate
+ * @return holdback part rate
+ */
+ function _calculateHoldbackPartRateFromAvg(
+ Request memory request,
+ uint256 avgMTokenRate
+ ) internal pure returns (uint256) {
+ if (
+ avgMTokenRate == 0 ||
+ request.tokenOutRate == 0 ||
+ request.depositedInstantUsdAmount == 0
+ ) {
+ return 0;
+ }
+
+ uint256 targetTotalMTokenValue = ((request.depositedUsdAmount +
+ request.depositedInstantUsdAmount) * (10**18)) / avgMTokenRate;
+
+ uint256 instantPartMTokenValue = (request.depositedInstantUsdAmount *
+ (10**18)) / request.tokenOutRate;
+
+ if (targetTotalMTokenValue <= instantPartMTokenValue) {
+ return 0;
+ }
+
+ uint256 holdbackPartValue = targetTotalMTokenValue -
+ instantPartMTokenValue;
+
+ return (request.depositedUsdAmount * (10**18)) / holdbackPartValue;
+ }
+
+ /**
+ * @dev calculates mToken amount to mint from request and provided mToken rate
+ * @param request request
+ * @param mTokenRate mToken rate
+ * @return mToken amount to mint
+ */
+ function _quoteMTokenFromRequest(Request memory request, uint256 mTokenRate)
+ private
+ view
+ returns (uint256)
+ {
+ return (request.usdAmountWithoutFees * (10**18)) / mTokenRate;
+ }
+
+ /**
+ * @dev calculates effective mToken supply including upcoming supply
+ * @return effective mToken supply
*/
- function _getMTokenRate() private view returns (uint256 mTokenRate) {
- mTokenRate = _getTokenRate(address(mTokenDataFeed), false);
- require(mTokenRate > 0, "DV: rate zero");
+ function _getEffectiveMTokenSupply() private view returns (uint256) {
+ return mToken.totalSupply() + upcomingSupply;
}
}
diff --git a/contracts/DepositVaultWithAave.sol b/contracts/DepositVaultWithAave.sol
index 834f59d4..ea51a9bd 100644
--- a/contracts/DepositVaultWithAave.sol
+++ b/contracts/DepositVaultWithAave.sol
@@ -1,11 +1,14 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import {IERC20Upgradeable as IERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import {SafeERC20Upgradeable as SafeERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
-import "./DepositVault.sol";
-import "./interfaces/aave/IAaveV3Pool.sol";
+import {DepositVault} from "./DepositVault.sol";
+import {IAaveV3Pool} from "./interfaces/aave/IAaveV3Pool.sol";
+import {DecimalsCorrectionLibrary} from "./libraries/DecimalsCorrectionLibrary.sol";
+import {Greenlistable} from "./access/Greenlistable.sol";
+import {ManageableVault} from "./abstract/ManageableVault.sol";
/**
* @title DepositVaultWithAave
@@ -42,22 +45,16 @@ contract DepositVaultWithAave is DepositVault {
/**
* @notice Emitted when an Aave V3 Pool is configured for a payment token
- * @param caller address of the caller
* @param token payment token address
* @param pool Aave V3 Pool address
*/
- event SetAavePool(
- address indexed caller,
- address indexed token,
- address indexed pool
- );
+ event SetAavePool(address indexed token, address indexed pool);
/**
* @notice Emitted when an Aave V3 Pool is removed for a payment token
- * @param caller address of the caller
* @param token payment token address
*/
- event RemoveAavePool(address indexed caller, address indexed token);
+ event RemoveAavePool(address indexed token);
/**
* @notice Emitted when `aaveDepositsEnabled` flag is updated
@@ -71,6 +68,35 @@ contract DepositVaultWithAave is DepositVault {
*/
event SetAutoInvestFallbackEnabled(bool indexed enabled);
+ /**
+ * @notice when token is not in pool
+ * @param aavePool Aave V3 Pool address
+ * @param token token address
+ */
+ error TokenNotInPool(address aavePool, address token);
+
+ /**
+ * @notice when pool is not set
+ * @param token token address
+ */
+ error PoolNotSet(address token);
+
+ /**
+ * @notice when auto-invest fails
+ * @param err error bytes
+ */
+ error AutoInvestFailed(bytes err);
+
+ /**
+ * @notice Passes role identifiers to the base DepositVault constructor
+ * @param _contractAdminRole contract admin role identifier
+ * @param _greenlistedRole greenlisted role identifier
+ * @custom:oz-upgrades-unsafe-allow constructor
+ */
+ constructor(bytes32 _contractAdminRole, bytes32 _greenlistedRole)
+ DepositVault(_contractAdminRole, _greenlistedRole)
+ {}
+
/**
* @notice Sets the Aave V3 Pool for a specific payment token
* @param _token payment token address
@@ -78,33 +104,33 @@ contract DepositVaultWithAave is DepositVault {
*/
function setAavePool(address _token, address _aavePool)
external
- onlyVaultAdmin
+ onlyContractAdmin
{
_validateAddress(_token, true);
_validateAddress(_aavePool, true);
require(
IAaveV3Pool(_aavePool).getReserveAToken(_token) != address(0),
- "DVA: token not in pool"
+ TokenNotInPool(_aavePool, _token)
);
aavePools[_token] = IAaveV3Pool(_aavePool);
- emit SetAavePool(msg.sender, _token, _aavePool);
+ emit SetAavePool(_token, _aavePool);
}
/**
* @notice Removes the Aave V3 Pool for a specific payment token
* @param _token payment token address
*/
- function removeAavePool(address _token) external onlyVaultAdmin {
- require(address(aavePools[_token]) != address(0), "DVA: pool not set");
+ function removeAavePool(address _token) external onlyContractAdmin {
+ require(address(aavePools[_token]) != address(0), PoolNotSet(_token));
delete aavePools[_token];
- emit RemoveAavePool(msg.sender, _token);
+ emit RemoveAavePool(_token);
}
/**
* @notice Updates `aaveDepositsEnabled` value
* @param enabled whether Aave auto-invest deposits are enabled
*/
- function setAaveDepositsEnabled(bool enabled) external onlyVaultAdmin {
+ function setAaveDepositsEnabled(bool enabled) external onlyContractAdmin {
aaveDepositsEnabled = enabled;
emit SetAaveDepositsEnabled(enabled);
}
@@ -115,7 +141,7 @@ contract DepositVaultWithAave is DepositVault {
*/
function setAutoInvestFallbackEnabled(bool enabled)
external
- onlyVaultAdmin
+ onlyContractAdmin
{
autoInvestFallbackEnabled = enabled;
emit SetAutoInvestFallbackEnabled(enabled);
@@ -128,7 +154,7 @@ contract DepositVaultWithAave is DepositVault {
address tokenIn,
uint256 amountToken,
uint256 tokensDecimals
- ) internal override {
+ ) internal virtual override {
IAaveV3Pool pool = aavePools[tokenIn];
if (!aaveDepositsEnabled || address(pool) == address(0)) {
return
@@ -149,7 +175,7 @@ contract DepositVaultWithAave is DepositVault {
address tokenIn,
uint256 amountToken,
uint256 tokensDecimals
- ) internal override {
+ ) internal virtual override {
IAaveV3Pool pool = aavePools[tokenIn];
if (!aaveDepositsEnabled || address(pool) == address(0)) {
return
@@ -189,12 +215,12 @@ contract DepositVaultWithAave is DepositVault {
try
pool.supply(tokenIn, transferredAmount, tokensReceiver, 0)
- {} catch {
+ {} catch (bytes memory error) {
if (autoInvestFallbackEnabled) {
IERC20(tokenIn).safeApprove(address(pool), 0);
IERC20(tokenIn).safeTransfer(tokensReceiver, transferredAmount);
} else {
- revert("DVA: auto-invest failed");
+ revert AutoInvestFailed(error);
}
}
}
diff --git a/contracts/DepositVaultWithMToken.sol b/contracts/DepositVaultWithMToken.sol
index fb60093e..c9ec852b 100644
--- a/contracts/DepositVaultWithMToken.sol
+++ b/contracts/DepositVaultWithMToken.sol
@@ -1,11 +1,14 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import {IERC20Upgradeable as IERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import {SafeERC20Upgradeable as SafeERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
-import "./DepositVault.sol";
-import "./interfaces/IDepositVault.sol";
+import {DepositVault} from "./DepositVault.sol";
+import {ManageableVault} from "./abstract/ManageableVault.sol";
+import {IDepositVault, DepositVaultInitParams} from "./interfaces/IDepositVault.sol";
+import {CommonVaultInitParams} from "./interfaces/IManageableVault.sol";
+import {DecimalsCorrectionLibrary} from "./libraries/DecimalsCorrectionLibrary.sol";
/**
* @title DepositVaultWithMToken
@@ -42,13 +45,9 @@ contract DepositVaultWithMToken is DepositVault {
/**
* @notice Emitted when the mToken DepositVault address is updated
- * @param caller address of the caller
* @param newVault new mToken DepositVault address
*/
- event SetMTokenDepositVault(
- address indexed caller,
- address indexed newVault
- );
+ event SetMTokenDepositVault(address indexed newVault);
/**
* @notice Emitted when `mTokenDepositsEnabled` flag is updated
@@ -62,42 +61,40 @@ contract DepositVaultWithMToken is DepositVault {
*/
event SetAutoInvestFallbackEnabled(bool indexed enabled);
+ /**
+ * @notice when zero mToken is received
+ * @param mTokenReceived mToken received
+ */
+ error ZeroMTokenReceived(uint256 mTokenReceived);
+
+ /**
+ * @notice when auto-invest fails
+ * @param err error bytes
+ */
+ error AutoInvestFailed(bytes err);
+
+ /**
+ * @notice Passes role identifiers to the base DepositVault constructor
+ * @param _contractAdminRole contract admin role identifier
+ * @param _greenlistedRole greenlisted role identifier
+ * @custom:oz-upgrades-unsafe-allow constructor
+ */
+ constructor(bytes32 _contractAdminRole, bytes32 _greenlistedRole)
+ DepositVault(_contractAdminRole, _greenlistedRole)
+ {}
+
/**
* @notice upgradeable pattern contract`s initializer
- * @param _ac address of MidasAccessControll contract
- * @param _mTokenInitParams init params for mToken
- * @param _receiversInitParams init params for receivers
- * @param _instantInitParams init params for instant operations
- * @param _sanctionsList address of sanctionsList contract
- * @param _variationTolerance percent of prices diviation 1% = 100
- * @param _minAmount basic min amount for operations in mToken
- * @param _minMTokenAmountForFirstDeposit min amount for first deposit in mToken
- * @param _maxSupplyCap max supply cap for mToken
+ * @param _commonVaultInitParams init params for common vault
+ * @param _depositVaultInitParams init params for deposit vault
* @param _mTokenDepositVault target mToken DepositVault address
*/
function initialize(
- address _ac,
- MTokenInitParams calldata _mTokenInitParams,
- ReceiversInitParams calldata _receiversInitParams,
- InstantInitParams calldata _instantInitParams,
- address _sanctionsList,
- uint256 _variationTolerance,
- uint256 _minAmount,
- uint256 _minMTokenAmountForFirstDeposit,
- uint256 _maxSupplyCap,
+ CommonVaultInitParams calldata _commonVaultInitParams,
+ DepositVaultInitParams calldata _depositVaultInitParams,
address _mTokenDepositVault
) external {
- initialize(
- _ac,
- _mTokenInitParams,
- _receiversInitParams,
- _instantInitParams,
- _sanctionsList,
- _variationTolerance,
- _minAmount,
- _minMTokenAmountForFirstDeposit,
- _maxSupplyCap
- );
+ initialize(_commonVaultInitParams, _depositVaultInitParams);
_validateAddress(_mTokenDepositVault, true);
mTokenDepositVault = IDepositVault(_mTokenDepositVault);
@@ -109,22 +106,22 @@ contract DepositVaultWithMToken is DepositVault {
*/
function setMTokenDepositVault(address _mTokenDepositVault)
external
- onlyVaultAdmin
+ onlyContractAdmin
{
require(
_mTokenDepositVault != address(mTokenDepositVault),
- "DVMT: already set"
+ SameAddressValue(_mTokenDepositVault)
);
_validateAddress(_mTokenDepositVault, true);
mTokenDepositVault = IDepositVault(_mTokenDepositVault);
- emit SetMTokenDepositVault(msg.sender, _mTokenDepositVault);
+ emit SetMTokenDepositVault(_mTokenDepositVault);
}
/**
* @notice Updates `mTokenDepositsEnabled` value
* @param enabled whether mToken auto-invest deposits are enabled
*/
- function setMTokenDepositsEnabled(bool enabled) external onlyVaultAdmin {
+ function setMTokenDepositsEnabled(bool enabled) external onlyContractAdmin {
mTokenDepositsEnabled = enabled;
emit SetMTokenDepositsEnabled(enabled);
}
@@ -135,7 +132,7 @@ contract DepositVaultWithMToken is DepositVault {
*/
function setAutoInvestFallbackEnabled(bool enabled)
external
- onlyVaultAdmin
+ onlyContractAdmin
{
autoInvestFallbackEnabled = enabled;
emit SetAutoInvestFallbackEnabled(enabled);
@@ -148,7 +145,7 @@ contract DepositVaultWithMToken is DepositVault {
address tokenIn,
uint256 amountToken,
uint256 tokensDecimals
- ) internal override {
+ ) internal virtual override {
if (!mTokenDepositsEnabled) {
return
super._instantTransferTokensToTokensReceiver(
@@ -168,7 +165,7 @@ contract DepositVaultWithMToken is DepositVault {
address tokenIn,
uint256 amountToken,
uint256 tokensDecimals
- ) internal override {
+ ) internal virtual override {
if (!mTokenDepositsEnabled) {
return
super._requestTransferTokensToTokensReceiver(
@@ -214,7 +211,6 @@ contract DepositVaultWithMToken is DepositVault {
);
IERC20 targetMToken = IERC20(address(mTokenDepositVault.mToken()));
- uint256 balanceBefore = targetMToken.balanceOf(address(this));
try
mTokenDepositVault.depositInstant(
@@ -223,17 +219,15 @@ contract DepositVaultWithMToken is DepositVault {
0,
bytes32(0)
)
- {
- uint256 mTokenReceived = targetMToken.balanceOf(address(this)) -
- balanceBefore;
- require(mTokenReceived > 0, "DVMT: zero mToken received");
+ returns (uint256 mTokenReceived) {
+ require(mTokenReceived > 0, ZeroMTokenReceived(mTokenReceived));
targetMToken.safeTransfer(tokensReceiver, mTokenReceived);
- } catch {
+ } catch (bytes memory err) {
if (autoInvestFallbackEnabled) {
IERC20(tokenIn).safeApprove(address(mTokenDepositVault), 0);
IERC20(tokenIn).safeTransfer(tokensReceiver, transferredAmount);
} else {
- revert("DVMT: auto-invest failed");
+ revert AutoInvestFailed(err);
}
}
}
diff --git a/contracts/DepositVaultWithMorpho.sol b/contracts/DepositVaultWithMorpho.sol
index d515da38..078ec1c5 100644
--- a/contracts/DepositVaultWithMorpho.sol
+++ b/contracts/DepositVaultWithMorpho.sol
@@ -1,10 +1,11 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import {IERC20Upgradeable as IERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import {SafeERC20Upgradeable as SafeERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
-import "./DepositVault.sol";
+import {DepositVault} from "./DepositVault.sol";
+import {DecimalsCorrectionLibrary} from "./libraries/DecimalsCorrectionLibrary.sol";
import {IMorphoVault} from "./interfaces/morpho/IMorphoVault.sol";
/**
@@ -42,22 +43,16 @@ contract DepositVaultWithMorpho is DepositVault {
/**
* @notice Emitted when a Morpho Vault is configured for a payment token
- * @param caller address of the caller
* @param token payment token address
* @param vault Morpho Vault address
*/
- event SetMorphoVault(
- address indexed caller,
- address indexed token,
- address indexed vault
- );
+ event SetMorphoVault(address indexed token, address indexed vault);
/**
* @notice Emitted when a Morpho Vault is removed for a payment token
- * @param caller address of the caller
* @param token payment token address
*/
- event RemoveMorphoVault(address indexed caller, address indexed token);
+ event RemoveMorphoVault(address indexed token);
/**
* @notice Emitted when `morphoDepositsEnabled` flag is updated
@@ -71,6 +66,41 @@ contract DepositVaultWithMorpho is DepositVault {
*/
event SetAutoInvestFallbackEnabled(bool indexed enabled);
+ /**
+ * @notice when asset mismatch
+ * @param morphoVault Morpho Vault address
+ * @param token token address
+ */
+ error AssetMismatch(address morphoVault, address token);
+
+ /**
+ * @notice when vault is not set
+ * @param token token address
+ */
+ error VaultNotSet(address token);
+
+ /**
+ * @notice when zero shares are received
+ * @param shares shares
+ */
+ error ZeroShares(uint256 shares);
+
+ /**
+ * @notice when auto-invest fails
+ * @param err error bytes
+ */
+ error AutoInvestFailed(bytes err);
+
+ /**
+ * @notice Passes role identifiers to the base DepositVault constructor
+ * @param _contractAdminRole contract admin role identifier
+ * @param _greenlistedRole greenlisted role identifier
+ * @custom:oz-upgrades-unsafe-allow constructor
+ */
+ constructor(bytes32 _contractAdminRole, bytes32 _greenlistedRole)
+ DepositVault(_contractAdminRole, _greenlistedRole)
+ {}
+
/**
* @notice Sets the Morpho Vault for a specific payment token
* @param _token payment token address
@@ -78,36 +108,36 @@ contract DepositVaultWithMorpho is DepositVault {
*/
function setMorphoVault(address _token, address _morphoVault)
external
- onlyVaultAdmin
+ onlyContractAdmin
{
_validateAddress(_token, true);
_validateAddress(_morphoVault, true);
require(
IMorphoVault(_morphoVault).asset() == _token,
- "DVM: asset mismatch"
+ AssetMismatch(_morphoVault, _token)
);
morphoVaults[_token] = IMorphoVault(_morphoVault);
- emit SetMorphoVault(msg.sender, _token, _morphoVault);
+ emit SetMorphoVault(_token, _morphoVault);
}
/**
* @notice Removes the Morpho Vault for a specific payment token
* @param _token payment token address
*/
- function removeMorphoVault(address _token) external onlyVaultAdmin {
+ function removeMorphoVault(address _token) external onlyContractAdmin {
require(
address(morphoVaults[_token]) != address(0),
- "DVM: vault not set"
+ VaultNotSet(_token)
);
delete morphoVaults[_token];
- emit RemoveMorphoVault(msg.sender, _token);
+ emit RemoveMorphoVault(_token);
}
/**
* @notice Updates `morphoDepositsEnabled` value
* @param enabled whether Morpho auto-invest deposits are enabled
*/
- function setMorphoDepositsEnabled(bool enabled) external onlyVaultAdmin {
+ function setMorphoDepositsEnabled(bool enabled) external onlyContractAdmin {
morphoDepositsEnabled = enabled;
emit SetMorphoDepositsEnabled(enabled);
}
@@ -118,7 +148,7 @@ contract DepositVaultWithMorpho is DepositVault {
*/
function setAutoInvestFallbackEnabled(bool enabled)
external
- onlyVaultAdmin
+ onlyContractAdmin
{
autoInvestFallbackEnabled = enabled;
emit SetAutoInvestFallbackEnabled(enabled);
@@ -131,7 +161,7 @@ contract DepositVaultWithMorpho is DepositVault {
address tokenIn,
uint256 amountToken,
uint256 tokensDecimals
- ) internal override {
+ ) internal virtual override {
IMorphoVault vault = morphoVaults[tokenIn];
if (!morphoDepositsEnabled || address(vault) == address(0)) {
return
@@ -152,7 +182,7 @@ contract DepositVaultWithMorpho is DepositVault {
address tokenIn,
uint256 amountToken,
uint256 tokensDecimals
- ) internal override {
+ ) internal virtual override {
IMorphoVault vault = morphoVaults[tokenIn];
if (!morphoDepositsEnabled || address(vault) == address(0)) {
return
@@ -196,13 +226,13 @@ contract DepositVaultWithMorpho is DepositVault {
try vault.deposit(transferredAmount, tokensReceiver) returns (
uint256 shares
) {
- require(shares > 0, "DVM: zero shares");
- } catch {
+ require(shares > 0, ZeroShares(shares));
+ } catch (bytes memory err) {
if (autoInvestFallbackEnabled) {
IERC20(tokenIn).safeApprove(address(vault), 0);
IERC20(tokenIn).safeTransfer(tokensReceiver, transferredAmount);
} else {
- revert("DVM: auto-invest failed");
+ revert AutoInvestFailed(err);
}
}
}
diff --git a/contracts/DepositVaultWithUSTB.sol b/contracts/DepositVaultWithUSTB.sol
index 9ebbae5f..5a841b85 100644
--- a/contracts/DepositVaultWithUSTB.sol
+++ b/contracts/DepositVaultWithUSTB.sol
@@ -1,9 +1,13 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
+import {SafeERC20Upgradeable as SafeERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
+import {IERC20Upgradeable as IERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import {ISuperstateToken} from "./interfaces/ustb/ISuperstateToken.sol";
-
-import "./DepositVault.sol";
+import {CommonVaultInitParams} from "./interfaces/IManageableVault.sol";
+import {DepositVault} from "./DepositVault.sol";
+import {DepositVaultInitParams} from "./interfaces/IDepositVault.sol";
+import {DecimalsCorrectionLibrary} from "./libraries/DecimalsCorrectionLibrary.sol";
/**
* @title DepositVaultWithUSTB
@@ -37,41 +41,40 @@ contract DepositVaultWithUSTB is DepositVault {
*/
event SetUstbDepositsEnabled(bool indexed enabled);
+ /**
+ * @notice when USTB token is not supported
+ * @param token token address
+ */
+ error UnsupportedUSTBToken(address token);
+
+ /**
+ * @notice when USTB fee is not zero
+ * @param fee fee
+ */
+ error USTBFeeNotZero(uint256 fee);
+
+ /**
+ * @notice Passes role identifiers to the base DepositVault constructor
+ * @param _contractAdminRole contract admin role identifier
+ * @param _greenlistedRole greenlisted role identifier
+ * @custom:oz-upgrades-unsafe-allow constructor
+ */
+ constructor(bytes32 _contractAdminRole, bytes32 _greenlistedRole)
+ DepositVault(_contractAdminRole, _greenlistedRole)
+ {}
+
/**
* @notice upgradeable pattern contract`s initializer
- * @param _ac address of MidasAccessControll contract
- * @param _mTokenInitParams init params for mToken
- * @param _receiversInitParams init params for receivers
- * @param _instantInitParams init params for instant operations
- * @param _sanctionsList address of sanctionsList contract
- * @param _variationTolerance percent of prices diviation 1% = 100
- * @param _minAmount basic min amount for operations in mToken
- * @param _minMTokenAmountForFirstDeposit min amount for first deposit in mToken
+ * @param _commonVaultInitParams init params for common vault
+ * @param _depositVaultInitParams init params for deposit vault
* @param _ustb USTB token address
*/
function initialize(
- address _ac,
- MTokenInitParams calldata _mTokenInitParams,
- ReceiversInitParams calldata _receiversInitParams,
- InstantInitParams calldata _instantInitParams,
- address _sanctionsList,
- uint256 _variationTolerance,
- uint256 _minAmount,
- uint256 _minMTokenAmountForFirstDeposit,
- uint256 _maxSupplyCap,
+ CommonVaultInitParams calldata _commonVaultInitParams,
+ DepositVaultInitParams calldata _depositVaultInitParams,
address _ustb
) external {
- initialize(
- _ac,
- _mTokenInitParams,
- _receiversInitParams,
- _instantInitParams,
- _sanctionsList,
- _variationTolerance,
- _minAmount,
- _minMTokenAmountForFirstDeposit,
- _maxSupplyCap
- );
+ initialize(_commonVaultInitParams, _depositVaultInitParams);
_validateAddress(_ustb, false);
@@ -82,7 +85,7 @@ contract DepositVaultWithUSTB is DepositVault {
* @notice Updates `ustbDepositsEnabled` value
* @param enabled whether USTB deposits are enabled
*/
- function setUstbDepositsEnabled(bool enabled) external onlyVaultAdmin {
+ function setUstbDepositsEnabled(bool enabled) external onlyContractAdmin {
ustbDepositsEnabled = enabled;
emit SetUstbDepositsEnabled(enabled);
}
@@ -102,7 +105,7 @@ contract DepositVaultWithUSTB is DepositVault {
address tokenIn,
uint256 amountToken,
uint256 tokensDecimals
- ) internal override {
+ ) internal virtual override {
if (!ustbDepositsEnabled) {
return
super._instantTransferTokensToTokensReceiver(
@@ -117,10 +120,10 @@ contract DepositVaultWithUSTB is DepositVault {
require(
config.sweepDestination != address(0),
- "DVU: unsupported USTB token"
+ UnsupportedUSTBToken(tokenIn)
);
- require(config.fee == 0, "DVU: USTB fee is not 0");
+ require(config.fee == 0, USTBFeeNotZero(config.fee));
address ustbToken = ustb;
uint256 transferredAmount = _tokenTransferFromUser(
diff --git a/contracts/RedemptionVault.sol b/contracts/RedemptionVault.sol
index 8f5762a2..b72b3b1d 100644
--- a/contracts/RedemptionVault.sol
+++ b/contracts/RedemptionVault.sol
@@ -1,17 +1,15 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import {IERC20Upgradeable as IERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
-import {IERC20MetadataUpgradeable as IERC20Metadata} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol";
+import {SafeERC20Upgradeable as SafeERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
+import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
-import {Counters} from "@openzeppelin/contracts/utils/Counters.sol";
-
-import "./interfaces/IRedemptionVault.sol";
-import "./interfaces/IDataFeed.sol";
-
-import "./abstract/ManageableVault.sol";
-
-import "./access/Greenlistable.sol";
+import {DecimalsCorrectionLibrary} from "./libraries/DecimalsCorrectionLibrary.sol";
+import {IRedemptionVault, LiquidityProviderLoanRequest, Request, RedemptionVaultInitParams} from "./interfaces/IRedemptionVault.sol";
+import {CommonVaultInitParams, RequestStatus} from "./interfaces/IManageableVault.sol";
+import {ManageableVault} from "./abstract/ManageableVault.sol";
+import {RedemptionSwapperHelpersLibrary} from "./libraries/RedemptionSwapperHelpersLibrary.sol";
/**
* @title RedemptionVault
@@ -20,143 +18,108 @@ import "./access/Greenlistable.sol";
*/
contract RedemptionVault is ManageableVault, IRedemptionVault {
using DecimalsCorrectionLibrary for uint256;
- using Counters for Counters.Counter;
+ using SafeERC20 for IERC20;
/**
* @notice return data of _calcAndValidateRedeem
* packed into a struct to avoid stack too deep errors
*/
struct CalcAndValidateRedeemResult {
- /// @notice fee amount in mToken
+ /// @notice fee amount in paymentToken
uint256 feeAmount;
- /// @notice amount of mToken without fee
- uint256 amountMTokenWithoutFee;
+ /// @notice amount of paymentToken without fee
+ uint256 amountTokenOutWithoutFee;
+ /// @notice amount of paymentToken with fee
+ uint256 amountTokenOut;
+ /// @notice payment token rate
+ uint256 tokenOutRate;
+ /// @notice mToken rate
+ uint256 mTokenRate;
+ /// @notice tokenOut decimals
+ uint256 tokenOutDecimals;
}
/**
- * @dev default role that grants admin rights to the contract
+ * @notice mapping, requestId to request data
*/
- bytes32 private constant _DEFAULT_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("REDEMPTION_VAULT_ADMIN_ROLE");
+ mapping(uint256 => Request) public redeemRequests;
/**
- * @dev selector for redeem instant
+ * @notice mapping, loanRequestId to loan request data
*/
- bytes4 private constant _REDEEM_INSTANT_SELECTOR =
- bytes4(keccak256("redeemInstant(address,uint256,uint256)"));
+ mapping(uint256 => LiquidityProviderLoanRequest) public loanRequests;
/**
- * @dev selector for redeem instant with custom recipient
+ * @notice address is designated for standard redemptions, allowing tokens to be pulled from this address
*/
- bytes4 private constant _REDEEM_INSTANT_WITH_CUSTOM_RECIPIENT_SELECTOR =
- bytes4(keccak256("redeemInstant(address,uint256,uint256,address)"));
+ address public requestRedeemer;
/**
- * @dev selector for redeem request
+ * @notice address of loan liquidity provider
*/
- bytes4 private constant _REDEEM_REQUEST_SELECTOR =
- bytes4(keccak256("redeemRequest(address,uint256)"));
+ address public loanLp;
/**
- * @dev selector for redeem request with custom recipient
+ * @notice address from which payment tokens will be pulled during loan repayment
*/
- bytes4 private constant _REDEEM_REQUEST_WITH_CUSTOM_RECIPIENT_SELECTOR =
- bytes4(keccak256("redeemRequest(address,uint256,address)"));
+ address public loanRepaymentAddress;
/**
- * @notice min amount for fiat requests
+ * @notice loan APR value in basis points (100 = 1%)
*/
- uint256 public minFiatRedeemAmount;
+ uint256 public loanApr;
/**
- * @notice fee percent for fiat requests
+ * @notice flag to determine if the loan LP liquidity should be used first
*/
- uint256 public fiatAdditionalFee;
+ bool public preferLoanLiquidity;
/**
- * @notice static fee in mToken for fiat requests
+ * @notice last loan request id
*/
- uint256 public fiatFlatFee;
+ uint256 public currentLoanRequestId;
/**
- * @notice mapping, requestId to request data
+ * @notice address of loan RedemptionVault-compatible vault
*/
- mapping(uint256 => Request) public redeemRequests;
+ IRedemptionVault public loanSwapperVault;
/**
- * @notice address is designated for standard redemptions, allowing tokens to be pulled from this address
+ * @dev leaving a storage gap for futures updates
*/
- address public requestRedeemer;
+ uint256[50] private __gap;
/**
- * @dev leaving a storage gap for futures updates
+ * @notice Passes role identifiers to the base ManageableVault constructor
+ * @param _contractAdminRole contract admin role identifier
+ * @param _greenlistedRole greenlisted role identifier
+ * @custom:oz-upgrades-unsafe-allow constructor
*/
- uint256[50] private __gap;
+ constructor(bytes32 _contractAdminRole, bytes32 _greenlistedRole)
+ ManageableVault(_contractAdminRole, _greenlistedRole)
+ {}
/**
* @notice upgradeable pattern contract`s initializer
- * @param _ac address of MidasAccessControll contract
- * @param _mTokenInitParams init params for mToken
- * @param _receiversInitParams init params for receivers
- * @param _instantInitParams init params for instant operations
- * @param _sanctionsList address of sanctionsList contract
- * @param _variationTolerance percent of prices diviation 1% = 100
- * @param _minAmount basic min amount for operations
- * @param _fiatRedemptionInitParams params fiatAdditionalFee, fiatFlatFee, minFiatRedeemAmount
- * @param _requestRedeemer address is designated for standard redemptions, allowing tokens to be pulled from this address
+ * @param _commonVaultInitParams init params for common vault
+ * @param _redemptionVaultInitParams init params for redemption vault
*/
function initialize(
- address _ac,
- MTokenInitParams calldata _mTokenInitParams,
- ReceiversInitParams calldata _receiversInitParams,
- InstantInitParams calldata _instantInitParams,
- address _sanctionsList,
- uint256 _variationTolerance,
- uint256 _minAmount,
- FiatRedeptionInitParams calldata _fiatRedemptionInitParams,
- address _requestRedeemer
- ) external initializer {
- __RedemptionVault_init(
- _ac,
- _mTokenInitParams,
- _receiversInitParams,
- _instantInitParams,
- _sanctionsList,
- _variationTolerance,
- _minAmount,
- _fiatRedemptionInitParams,
- _requestRedeemer
- );
- }
+ CommonVaultInitParams calldata _commonVaultInitParams,
+ RedemptionVaultInitParams calldata _redemptionVaultInitParams
+ ) public initializer {
+ __ManageableVault_init(_commonVaultInitParams);
- // solhint-disable func-name-mixedcase
- function __RedemptionVault_init(
- address _ac,
- MTokenInitParams calldata _mTokenInitParams,
- ReceiversInitParams calldata _receiversInitParams,
- InstantInitParams calldata _instantInitParams,
- address _sanctionsList,
- uint256 _variationTolerance,
- uint256 _minAmount,
- FiatRedeptionInitParams calldata _fiatRedemptionInitParams,
- address _requestRedeemer
- ) internal onlyInitializing {
- __ManageableVault_init(
- _ac,
- _mTokenInitParams,
- _receiversInitParams,
- _instantInitParams,
- _sanctionsList,
- _variationTolerance,
- _minAmount
- );
- _validateFee(_fiatRedemptionInitParams.fiatAdditionalFee, false);
- _validateAddress(_requestRedeemer, false);
+ _validateAddress(_redemptionVaultInitParams.requestRedeemer, false);
- minFiatRedeemAmount = _fiatRedemptionInitParams.minFiatRedeemAmount;
- fiatAdditionalFee = _fiatRedemptionInitParams.fiatAdditionalFee;
- fiatFlatFee = _fiatRedemptionInitParams.fiatFlatFee;
- requestRedeemer = _requestRedeemer;
+ requestRedeemer = _redemptionVaultInitParams.requestRedeemer;
+
+ loanLp = _redemptionVaultInitParams.loanLp;
+ loanRepaymentAddress = _redemptionVaultInitParams.loanRepaymentAddress;
+ loanSwapperVault = IRedemptionVault(
+ _redemptionVaultInitParams.loanSwapperVault
+ );
+ loanApr = _redemptionVaultInitParams.loanApr;
}
/**
@@ -166,26 +129,15 @@ contract RedemptionVault is ManageableVault, IRedemptionVault {
address tokenOut,
uint256 amountMTokenIn,
uint256 minReceiveAmount
- ) external whenFnNotPaused(_REDEEM_INSTANT_SELECTOR) {
- _validateUserAccess(msg.sender);
-
- (
- CalcAndValidateRedeemResult memory calcResult,
- uint256 amountTokenOutWithoutFee
- ) = _redeemInstant(
+ ) external returns (uint256) {
+ return
+ _redeemInstantWithCustomRecipient(
tokenOut,
amountMTokenIn,
minReceiveAmount,
- msg.sender
+ msg.sender,
+ ONE_HUNDRED_PERCENT
);
-
- emit RedeemInstant(
- msg.sender,
- tokenOut,
- amountMTokenIn,
- calcResult.feeAmount,
- amountTokenOutWithoutFee
- );
}
/**
@@ -196,31 +148,15 @@ contract RedemptionVault is ManageableVault, IRedemptionVault {
uint256 amountMTokenIn,
uint256 minReceiveAmount,
address recipient
- ) external whenFnNotPaused(_REDEEM_INSTANT_WITH_CUSTOM_RECIPIENT_SELECTOR) {
- _validateUserAccess(msg.sender);
-
- if (recipient != msg.sender) {
- _validateUserAccess(recipient);
- }
-
- (
- CalcAndValidateRedeemResult memory calcResult,
- uint256 amountTokenOutWithoutFee
- ) = _redeemInstant(
+ ) external returns (uint256) {
+ return
+ _redeemInstantWithCustomRecipient(
tokenOut,
amountMTokenIn,
minReceiveAmount,
- recipient
+ recipient,
+ ONE_HUNDRED_PERCENT
);
-
- emit RedeemInstantWithCustomRecipient(
- msg.sender,
- tokenOut,
- recipient,
- amountMTokenIn,
- calcResult.feeAmount,
- amountTokenOutWithoutFee
- );
}
/**
@@ -228,27 +164,16 @@ contract RedemptionVault is ManageableVault, IRedemptionVault {
*/
function redeemRequest(address tokenOut, uint256 amountMTokenIn)
external
- whenFnNotPaused(_REDEEM_REQUEST_SELECTOR)
- returns (
- uint256 /*requestId*/
- )
+ returns (uint256 requestId)
{
- _validateUserAccess(msg.sender);
-
- (
- uint256 requestId,
- CalcAndValidateRedeemResult memory calcResult
- ) = _redeemRequest(tokenOut, amountMTokenIn, false, msg.sender);
-
- emit RedeemRequest(
- requestId,
- msg.sender,
+ (requestId, ) = _redeemRequestWithCustomRecipient(
tokenOut,
amountMTokenIn,
- calcResult.feeAmount
+ msg.sender,
+ 0,
+ 0,
+ msg.sender
);
-
- return requestId;
}
/**
@@ -257,68 +182,26 @@ contract RedemptionVault is ManageableVault, IRedemptionVault {
function redeemRequest(
address tokenOut,
uint256 amountMTokenIn,
- address recipient
+ address recipientRequest,
+ uint256 instantShare,
+ uint256 minReceiveAmountInstantShare,
+ address recipientInstant
)
external
- whenFnNotPaused(_REDEEM_REQUEST_WITH_CUSTOM_RECIPIENT_SELECTOR)
- returns (
- uint256 /*requestId*/
- )
- {
- _validateUserAccess(msg.sender);
-
- if (recipient != msg.sender) {
- _validateUserAccess(recipient);
- }
-
- (
- uint256 requestId,
- CalcAndValidateRedeemResult memory calcResult
- ) = _redeemRequest(tokenOut, amountMTokenIn, false, recipient);
-
- emit RedeemRequestWithCustomRecipient(
- requestId,
- msg.sender,
- tokenOut,
- recipient,
- amountMTokenIn,
- calcResult.feeAmount
- );
-
- return requestId;
- }
-
- /**
- * @inheritdoc IRedemptionVault
- */
- function redeemFiatRequest(uint256 amountMTokenIn)
- external
- whenFnNotPaused(this.redeemFiatRequest.selector)
returns (
- uint256 /*requestId*/
+ uint256, /*requestId*/
+ uint256 /* instantReceivedAmount */
)
{
- _validateUserAccess(msg.sender);
-
- (
- uint256 requestId,
- CalcAndValidateRedeemResult memory calcResult
- ) = _redeemRequest(
- MANUAL_FULLFILMENT_TOKEN,
+ return
+ _redeemRequestWithCustomRecipient(
+ tokenOut,
amountMTokenIn,
- true,
- msg.sender
+ recipientRequest,
+ instantShare,
+ minReceiveAmountInstantShare,
+ recipientInstant
);
-
- emit RedeemRequest(
- requestId,
- msg.sender,
- MANUAL_FULLFILMENT_TOKEN,
- amountMTokenIn,
- calcResult.feeAmount
- );
-
- return requestId;
}
/**
@@ -326,410 +209,894 @@ contract RedemptionVault is ManageableVault, IRedemptionVault {
*/
function safeBulkApproveRequestAtSavedRate(uint256[] calldata requestIds)
external
- onlyVaultAdmin
{
- for (uint256 i = 0; i < requestIds.length; i++) {
- uint256 rate = redeemRequests[requestIds[i]].mTokenRate;
- bool success = _approveRequest(requestIds[i], rate, true, true);
-
- if (!success) {
- continue;
- }
-
- emit SafeApproveRequest(requestIds[i], rate);
- }
+ _safeBulkApproveRequest(requestIds, 0, true, false);
}
/**
* @inheritdoc IRedemptionVault
*/
function safeBulkApproveRequest(uint256[] calldata requestIds) external {
- uint256 currentMTokenRate = _getMTokenRate();
- safeBulkApproveRequest(requestIds, currentMTokenRate);
+ _safeBulkApproveRequest(requestIds, _getMTokenRate(), false, false);
}
/**
* @inheritdoc IRedemptionVault
*/
- function approveRequest(uint256 requestId, uint256 newMTokenRate)
+ function safeBulkApproveRequestAvgRate(uint256[] calldata requestIds)
external
- onlyVaultAdmin
{
- _approveRequest(requestId, newMTokenRate, false, false);
+ _safeBulkApproveRequest(requestIds, _getMTokenRate(), false, true);
+ }
- emit ApproveRequest(requestId, newMTokenRate);
+ /**
+ * @inheritdoc IRedemptionVault
+ */
+ function safeBulkApproveRequest(
+ uint256[] calldata requestIds,
+ uint256 newOutRate
+ ) external {
+ _safeBulkApproveRequest(requestIds, newOutRate, false, false);
}
/**
* @inheritdoc IRedemptionVault
*/
- function safeApproveRequest(uint256 requestId, uint256 newMTokenRate)
- external
- onlyVaultAdmin
- {
- _approveRequest(requestId, newMTokenRate, true, false);
+ function safeBulkApproveRequestAvgRate(
+ uint256[] calldata requestIds,
+ uint256 avgMTokenRate
+ ) external {
+ _safeBulkApproveRequest(requestIds, avgMTokenRate, false, true);
+ }
- emit SafeApproveRequest(requestId, newMTokenRate);
+ /**
+ * @inheritdoc IRedemptionVault
+ */
+ function approveRequest(
+ uint256 requestId,
+ uint256 newMTokenRate,
+ bool isAvgRate
+ ) external onlyContractAdmin {
+ _approveRequest(requestId, newMTokenRate, false, isAvgRate);
}
/**
* @inheritdoc IRedemptionVault
*/
- function rejectRequest(uint256 requestId) external onlyVaultAdmin {
+ function rejectRequest(uint256 requestId) external onlyContractAdmin {
Request memory request = redeemRequests[requestId];
- _validateRequest(request.sender, request.status);
+ _validateRequest(requestId, request.recipient, request.status);
+ _validateAndUpdateNextRequestIdToProcess(requestId, true);
redeemRequests[requestId].status = RequestStatus.Canceled;
- emit RejectRequest(requestId, request.sender);
+ emit RejectRequest(requestId);
}
/**
* @inheritdoc IRedemptionVault
*/
- function setMinFiatRedeemAmount(uint256 newValue) external onlyVaultAdmin {
- minFiatRedeemAmount = newValue;
+ function bulkRepayLpLoanRequest(uint256[] calldata requestIds)
+ external
+ onlyContractAdmin
+ {
+ uint256 _loanApr = loanApr;
+ for (uint256 i = 0; i < requestIds.length; ++i) {
+ LiquidityProviderLoanRequest memory request = loanRequests[
+ requestIds[i]
+ ];
+
+ _validateRequest(requestIds[i], request.tokenOut, request.status);
+
+ uint8 decimals = _tokenDecimals(request.tokenOut);
+ uint256 duration = block.timestamp - request.createdAt;
+ uint256 accruedInterest = _truncate(
+ (request.amountTokenOut * _loanApr * duration) /
+ (10_000 * 365 days),
+ decimals
+ );
+
+ uint256 amountFee;
+
+ if (accruedInterest > request.amountFee) {
+ amountFee = accruedInterest;
+ loanRequests[requestIds[i]].amountFee = amountFee;
+ } else {
+ amountFee = request.amountFee;
+ }
- emit SetMinFiatRedeemAmount(msg.sender, newValue);
+ _tokenTransferFromTo(
+ request.tokenOut,
+ loanRepaymentAddress,
+ loanLp,
+ request.amountTokenOut + amountFee,
+ decimals
+ );
+
+ loanRequests[requestIds[i]].status = RequestStatus.Processed;
+ emit RepayLpLoanRequest(requestIds[i], amountFee);
+ }
}
/**
* @inheritdoc IRedemptionVault
*/
- function setFiatFlatFee(uint256 feeInMToken) external onlyVaultAdmin {
- fiatFlatFee = feeInMToken;
+ function cancelLpLoanRequest(uint256 requestId) external onlyContractAdmin {
+ LiquidityProviderLoanRequest memory request = loanRequests[requestId];
+
+ _validateRequest(requestId, request.tokenOut, request.status);
- emit SetFiatFlatFee(msg.sender, feeInMToken);
+ loanRequests[requestId].status = RequestStatus.Canceled;
+ emit CancelLpLoanRequest(requestId);
}
/**
* @inheritdoc IRedemptionVault
*/
- function setFiatAdditionalFee(uint256 newFee) external onlyVaultAdmin {
- _validateFee(newFee, false);
+ function setRequestRedeemer(address redeemer) external onlyContractAdmin {
+ _validateAddress(redeemer, false);
- fiatAdditionalFee = newFee;
+ requestRedeemer = redeemer;
- emit SetFiatAdditionalFee(msg.sender, newFee);
+ emit SetRequestRedeemer(redeemer);
}
/**
* @inheritdoc IRedemptionVault
*/
- function setRequestRedeemer(address redeemer) external onlyVaultAdmin {
- _validateAddress(redeemer, false);
+ function setLoanLp(address newLoanLp) external onlyContractAdmin {
+ loanLp = newLoanLp;
- requestRedeemer = redeemer;
-
- emit SetRequestRedeemer(msg.sender, redeemer);
+ emit SetLoanLp(newLoanLp);
}
/**
* @inheritdoc IRedemptionVault
*/
- function safeBulkApproveRequest(
- uint256[] calldata requestIds,
- uint256 newOutRate
- ) public onlyVaultAdmin {
- for (uint256 i = 0; i < requestIds.length; i++) {
- bool success = _approveRequest(
- requestIds[i],
- newOutRate,
- true,
- true
- );
+ function setLoanRepaymentAddress(address newLoanRepaymentAddress)
+ external
+ onlyContractAdmin
+ {
+ loanRepaymentAddress = newLoanRepaymentAddress;
- if (!success) {
- continue;
- }
+ emit SetLoanRepaymentAddress(newLoanRepaymentAddress);
+ }
- emit SafeApproveRequest(requestIds[i], newOutRate);
- }
+ /**
+ * @inheritdoc IRedemptionVault
+ */
+ function setLoanSwapperVault(address newLoanSwapperVault)
+ external
+ onlyContractAdmin
+ {
+ loanSwapperVault = IRedemptionVault(newLoanSwapperVault);
+
+ emit SetLoanSwapperVault(newLoanSwapperVault);
}
/**
- * @inheritdoc ManageableVault
+ * @inheritdoc IRedemptionVault
*/
- function vaultRole() public pure virtual override returns (bytes32) {
- return _DEFAULT_REDEMPTION_VAULT_ADMIN_ROLE;
+ function setLoanApr(uint256 newLoanApr) external onlyContractAdmin {
+ loanApr = newLoanApr;
+
+ emit SetLoanApr(newLoanApr);
}
/**
- * @inheritdoc Greenlistable
+ * @inheritdoc IRedemptionVault
*/
- function greenlistTogglerRole()
- public
- view
- virtual
- override
- returns (bytes32)
+ function setPreferLoanLiquidity(bool newLoanLpFirst)
+ external
+ onlyContractAdmin
{
- return vaultRole();
+ preferLoanLiquidity = newLoanLpFirst;
+
+ emit SetPreferLoanLiquidity(newLoanLpFirst);
+ }
+
+ /**
+ * @dev internal function to approve requests
+ * @param requestIds request ids
+ * @param newOutRate new out rate
+ * @param isRequestRate if true, newOutRate will be ignored and request rate will be used
+ * @param isAvgRate if true, newOutRate is avg rate
+ */
+ function _safeBulkApproveRequest(
+ uint256[] calldata requestIds,
+ uint256 newOutRate,
+ bool isRequestRate,
+ bool isAvgRate
+ ) private onlyContractAdmin {
+ for (uint256 i = 0; i < requestIds.length; ++i) {
+ if (isRequestRate) {
+ newOutRate = redeemRequests[requestIds[i]].mTokenRate;
+ }
+
+ _approveRequest(requestIds[i], newOutRate, true, isAvgRate);
+ }
}
/**
* @dev validates approve
* burns amount from contract
- * transfer tokenOut to user if not fiat
+ * transfer tokenOut to user
* sets flag Processed
* @param requestId request id
* @param newMTokenRate new mToken rate
- * @param isSafe new mToken rate
- * @param safeValidateLiquidity if true, checks if there is enough liquidity
- * and if its not sufficient, function wont fail
- *
- * @return success true if success, false only in case if
- * safeValidateLiquidity == true and there is not enough liquidity
+ * @param isSafe if true:
+ * - safely validates max approve request id
+ * - safely validates if request id is sequential
+ * - safely validates if there is enough liquidity
+ * - requires variation tolerance
+ * @param isAvgRate if true, calculates holdback part rate from avg rate
*/
function _approveRequest(
uint256 requestId,
uint256 newMTokenRate,
bool isSafe,
- bool safeValidateLiquidity
- )
- internal
- returns (
- bool /* success */
- )
- {
+ bool isAvgRate
+ ) private {
Request memory request = redeemRequests[requestId];
- _validateRequest(request.sender, request.status);
+ _validateRequest(requestId, request.recipient, request.status);
+
+ _validateUserAccess(request.recipient, false);
if (isSafe) {
_requireVariationTolerance(request.mTokenRate, newMTokenRate);
}
- bool isFiat = request.tokenOut == MANUAL_FULLFILMENT_TOKEN;
-
- uint256 tokenDecimals = isFiat ? 18 : _tokenDecimals(request.tokenOut);
+ if (isAvgRate) {
+ uint256 avgRate = _calculateHoldbackPartRateFromAvg(
+ request,
+ newMTokenRate
+ );
- uint256 amountTokenOutWithoutFee = _truncate(
- (request.amountMToken * newMTokenRate) / request.tokenOutRate,
- tokenDecimals
- );
+ if (request.amountMTokenInstant > 0) {
+ require(avgRate > 0, InvalidAvgRate());
+ }
- if (!isFiat) {
- if (
- safeValidateLiquidity &&
- !_validateLiquidity(
- request.tokenOut,
- amountTokenOutWithoutFee,
- tokenDecimals
- )
- ) {
- return false;
+ if (avgRate != 0) {
+ newMTokenRate = avgRate;
}
+ }
- _tokenTransferFromTo(
- request.tokenOut,
- requestRedeemer,
- request.sender,
- amountTokenOutWithoutFee,
- tokenDecimals
- );
+ require(newMTokenRate > 0, InvalidNewMTokenRate());
+
+ CalcAndValidateRedeemResult memory calcResult = _calcAndValidateRedeem(
+ request.recipient,
+ request.tokenOut,
+ request.amountMToken,
+ newMTokenRate,
+ request.tokenOutRate,
+ true,
+ request.feePercent,
+ false
+ );
+
+ if (
+ (isSafe &&
+ IERC20(request.tokenOut).balanceOf(requestRedeemer) <
+ (calcResult.amountTokenOutWithoutFee + calcResult.feeAmount)
+ .convertFromBase18(calcResult.tokenOutDecimals)) ||
+ !_validateAndUpdateNextRequestIdToProcess(requestId, !isSafe)
+ ) {
+ return;
}
- _requireAndUpdateAllowance(request.tokenOut, amountTokenOutWithoutFee);
+ _tokenTransferFromTo(
+ request.tokenOut,
+ requestRedeemer,
+ request.recipient,
+ calcResult.amountTokenOutWithoutFee,
+ calcResult.tokenOutDecimals
+ );
+
+ _requireAndUpdateAllowance(request.tokenOut, calcResult.amountTokenOut);
- mToken.burn(address(this), request.amountMToken);
+ mToken.burn(requestRedeemer, request.amountMToken);
+ request.amountTokenOut = calcResult.amountTokenOutWithoutFee;
+ request.approvedMTokenRate = newMTokenRate;
request.status = RequestStatus.Processed;
- request.mTokenRate = newMTokenRate;
+
redeemRequests[requestId] = request;
- return true;
+ emit ApproveRequest(requestId, newMTokenRate, isSafe, isAvgRate);
}
/**
* @notice validates request
* if exist
- * if not processed
- * @param sender sender address
- * @param status request status
+ * if status is expected
+ * @param requestId request id
+ * @param validateAddress address to check if not zero
+ * @param status actual request status
*/
- function _validateRequest(address sender, RequestStatus status)
- internal
- pure
- {
- require(sender != address(0), "RV: request not exist");
- require(status == RequestStatus.Pending, "RV: request not pending");
+ function _validateRequest(
+ uint256 requestId,
+ address validateAddress,
+ RequestStatus status
+ ) private pure {
+ require(validateAddress != address(0), RequestNotExists(requestId));
+ require(
+ status == RequestStatus.Pending,
+ UnexpectedRequestStatus(requestId, status)
+ );
}
/**
- * @dev internal redeem instant logic
+ * @dev internal redeem instant logic with custom recipient
* @param tokenOut tokenOut address
* @param amountMTokenIn amount of mToken (decimals 18)
* @param minReceiveAmount min amount of tokenOut to receive (decimals 18)
* @param recipient recipient address
- *
- * @return calcResult calculated redeem result
- * @return amountTokenOutWithoutFee amount of tokenOut without fee
+ * @param instantShareToValidate % amount of instant share to validate
*/
- function _redeemInstant(
+ function _redeemInstantWithCustomRecipient(
address tokenOut,
uint256 amountMTokenIn,
uint256 minReceiveAmount,
- address recipient
+ address recipient,
+ uint256 instantShareToValidate
+ ) private validateUserAccess(recipient) returns (uint256) {
+ require(
+ instantShareToValidate <= maxInstantShare,
+ InstantShareTooHigh(instantShareToValidate, maxInstantShare)
+ );
+
+ CalcAndValidateRedeemResult memory calcResult = _redeemInstant(
+ tokenOut,
+ amountMTokenIn,
+ minReceiveAmount,
+ recipient
+ );
+
+ // emitting earlier for easier loan matching on the indexer
+ emit RedeemInstant(
+ msg.sender,
+ tokenOut,
+ recipient,
+ amountMTokenIn,
+ calcResult.feeAmount,
+ calcResult.amountTokenOutWithoutFee,
+ calcResult.mTokenRate,
+ calcResult.tokenOutRate
+ );
+
+ _obtainLiquidityAndTransfer(tokenOut, recipient, calcResult);
+
+ return
+ calcResult.amountTokenOutWithoutFee.convertFromBase18(
+ calcResult.tokenOutDecimals
+ );
+ }
+
+ /**
+ * @dev internal redeem request logic with custom recipient
+ * @param tokenOut tokenOut address
+ * @param amountMTokenIn amount of mToken (decimals 18)
+ * @param recipientRequest recipient address for the request part
+ * @param instantShare % amount of `amountMTokenIn` that will be redeemed instantly
+ * @param minReceiveAmountInstantShare min amount of tokenOut to receive for the instant share
+ * @param recipientInstant recipient address for the instant part
+ * @return requestId request id
+ */
+ function _redeemRequestWithCustomRecipient(
+ address tokenOut,
+ uint256 amountMTokenIn,
+ address recipientRequest,
+ uint256 instantShare,
+ uint256 minReceiveAmountInstantShare,
+ address recipientInstant
)
- internal
- virtual
+ private
+ validateUserAccess(recipientRequest)
returns (
- CalcAndValidateRedeemResult memory calcResult,
- uint256 amountTokenOutWithoutFee
+ uint256, /* requestId */
+ uint256 instantReceivedAmount
)
{
+ uint256 amountMTokenInInstant = (amountMTokenIn * instantShare) /
+ ONE_HUNDRED_PERCENT;
+
+ if (amountMTokenInInstant > 0) {
+ instantReceivedAmount = _redeemInstantWithCustomRecipient(
+ tokenOut,
+ amountMTokenInInstant,
+ minReceiveAmountInstantShare,
+ recipientInstant,
+ instantShare
+ );
+ }
+
+ uint256 amountMTokenInRequest = amountMTokenIn - amountMTokenInInstant;
+
+ return (
+ _redeemRequest(
+ tokenOut,
+ amountMTokenInRequest,
+ recipientRequest,
+ amountMTokenInInstant
+ ),
+ instantReceivedAmount
+ );
+ }
+
+ /**
+ * @dev internal redeem instant logic
+ * @param tokenOut tokenOut address
+ * @param amountMTokenIn amount of mToken (decimals 18)
+ * @param minReceiveAmount min amount of tokenOut to receive (decimals 18)
+ *
+ * @return calcResult calculated redeem result
+ */
+ function _redeemInstant(
+ address tokenOut,
+ uint256 amountMTokenIn,
+ uint256 minReceiveAmount,
+ address /* recipient */
+ ) private returns (CalcAndValidateRedeemResult memory calcResult) {
address user = msg.sender;
+ _validateInstantFee();
+
calcResult = _calcAndValidateRedeem(
user,
tokenOut,
amountMTokenIn,
- true,
- false
+ 0,
+ 0,
+ false,
+ 0,
+ true
);
_requireAndUpdateLimit(amountMTokenIn);
- address tokenOutCopy = tokenOut;
- uint256 tokenDecimals = _tokenDecimals(tokenOutCopy);
-
- (uint256 amountMTokenInUsd, uint256 mTokenRate) = _convertMTokenToUsd(
- amountMTokenIn
- );
- (uint256 amountTokenOut, uint256 tokenOutRate) = _convertUsdToToken(
- amountMTokenInUsd,
- tokenOutCopy
+ _requireSlippageNotExceeded(
+ calcResult.amountTokenOutWithoutFee,
+ minReceiveAmount
);
- amountTokenOutWithoutFee = _truncate(
- (calcResult.amountMTokenWithoutFee * mTokenRate) / tokenOutRate,
- tokenDecimals
- );
+ _requireAndUpdateAllowance(tokenOut, calcResult.amountTokenOut);
- require(
- amountTokenOutWithoutFee >= minReceiveAmount,
- "RV: minReceiveAmount > actual"
- );
+ mToken.burn(user, amountMTokenIn);
+ }
- _requireAndUpdateAllowance(tokenOutCopy, amountTokenOut);
+ /**
+ * @dev Calculates how much of liquidity is needed to fulfill the redemption
+ * and gets missing amount from all the available sources - vault liquidity and loan LP liquidity
+ * if `preferLoanLiquidity` is true, it will first try to use loan LP liquidity,
+ */
+ function _obtainLiquidity(
+ address tokenOut,
+ CalcAndValidateRedeemResult memory calcResult
+ ) private returns (uint256 usedLpLiquidity, uint256 lpFeePortion) {
+ uint256 tokenOutBalanceBase18 = IERC20(tokenOut)
+ .balanceOf(address(this))
+ .convertToBase18(_tokenDecimals(tokenOut));
- mToken.burn(user, calcResult.amountMTokenWithoutFee);
- if (calcResult.feeAmount > 0)
- _tokenTransferFromUser(
- address(mToken),
- feeReceiver,
+ uint256 totalAmount = calcResult.amountTokenOutWithoutFee +
+ calcResult.feeAmount;
+
+ if (preferLoanLiquidity) {
+ (usedLpLiquidity, lpFeePortion) = _tryObtainLoanLpLiquidity(
+ tokenOut,
+ totalAmount,
+ totalAmount,
+ calcResult.tokenOutRate,
calcResult.feeAmount,
- 18
+ calcResult.tokenOutDecimals
+ );
+
+ uint256 newBalance = tokenOutBalanceBase18 + usedLpLiquidity;
+
+ if (newBalance < totalAmount) {
+ _tryObtainVaultLiquidity(
+ tokenOut,
+ totalAmount - newBalance,
+ calcResult.tokenOutRate,
+ newBalance,
+ calcResult.tokenOutDecimals
+ );
+ }
+ } else if (tokenOutBalanceBase18 < totalAmount) {
+ uint256 obtainedVaultLiquidity = _tryObtainVaultLiquidity(
+ tokenOut,
+ totalAmount - tokenOutBalanceBase18,
+ calcResult.tokenOutRate,
+ tokenOutBalanceBase18,
+ calcResult.tokenOutDecimals
);
- _tokenTransferToUser(
- tokenOutCopy,
+ uint256 newBalance = tokenOutBalanceBase18 + obtainedVaultLiquidity;
+
+ if (newBalance < totalAmount) {
+ (usedLpLiquidity, lpFeePortion) = _tryObtainLoanLpLiquidity(
+ tokenOut,
+ totalAmount - newBalance,
+ totalAmount,
+ calcResult.tokenOutRate,
+ calcResult.feeAmount,
+ calcResult.tokenOutDecimals
+ );
+ }
+ }
+ }
+
+ /**
+ * @dev Obtains liquidity from different sources and transfers it to the recipient
+ * as well as fee distribution
+ * @param tokenOut tokenOut address
+ * @param recipient recipient address
+ * @param calcResult calculated redeem result
+ */
+ function _obtainLiquidityAndTransfer(
+ address tokenOut,
+ address recipient,
+ CalcAndValidateRedeemResult memory calcResult
+ ) private {
+ uint256 usedLpLiquidity;
+ uint256 lpFeePortion;
+
+ (usedLpLiquidity, lpFeePortion) = _obtainLiquidity(
+ tokenOut,
+ calcResult
+ );
+
+ // transfer from vault liquidity to user
+ _tokenTransferFromTo(
+ tokenOut,
+ address(this),
recipient,
- amountTokenOutWithoutFee,
- tokenDecimals
+ calcResult.amountTokenOutWithoutFee,
+ calcResult.tokenOutDecimals
+ );
+
+ if (usedLpLiquidity == 0) {
+ return;
+ }
+
+ uint256 loanRequestId = currentLoanRequestId++;
+
+ loanRequests[loanRequestId] = LiquidityProviderLoanRequest({
+ tokenOut: tokenOut,
+ amountTokenOut: usedLpLiquidity,
+ amountFee: lpFeePortion,
+ createdAt: block.timestamp,
+ status: RequestStatus.Pending
+ });
+
+ emit CreateLiquidityProviderLoanRequest(
+ loanRequestId,
+ tokenOut,
+ usedLpLiquidity,
+ lpFeePortion,
+ calcResult.mTokenRate,
+ calcResult.tokenOutRate
);
}
/**
- * @notice internal redeem request logic
+ * @dev wraps _obtainVaultLiquidityExternal with try/catch
* @param tokenOut tokenOut address
- * @param amountMTokenIn amount of mToken (decimals 18)
- *
- * @return requestId request id
- * @return calcResult calc result
+ * @param missingAmountBase18 amount of tokenOut needed in base 18
+ * @param tokenOutRate tokenOut rate
+ * @param currentTokenOutBalanceBase18 current balance of tokenOut in the vault in base 18
+ * @param tokenOutDecimals decimals of tokenOut
*/
- function _redeemRequest(
+ function _tryObtainVaultLiquidity(
address tokenOut,
- uint256 amountMTokenIn,
- bool isFiat,
- address recipient
+ uint256 missingAmountBase18,
+ uint256 tokenOutRate,
+ uint256 currentTokenOutBalanceBase18,
+ uint256 tokenOutDecimals
+ ) private returns (uint256 obtainedLiquidityBase18) {
+ try
+ this._obtainVaultLiquidityExternal(
+ tokenOut,
+ missingAmountBase18,
+ tokenOutRate,
+ currentTokenOutBalanceBase18,
+ tokenOutDecimals
+ )
+ returns (uint256 _obtainedLiquidityBase18) {
+ obtainedLiquidityBase18 = _obtainedLiquidityBase18;
+ } catch {
+ // do nothing
+ }
+ }
+
+ /**
+ * @dev wraps _obtainLoanLpLiquidityExternal with try/catch
+ * @param tokenOut tokenOut address
+ * @param missingAmountBase18 amount of tokenOut needed in base 18
+ * @param totalAmount total amount of tokenOut needed in base 18
+ * @param tokenOutRate tokenOut rate
+ * @param totalFee total fee of tokenOut
+ * @param tokenOutDecimals decimals of tokenOut
+ */
+ function _tryObtainLoanLpLiquidity(
+ address tokenOut,
+ uint256 missingAmountBase18,
+ uint256 totalAmount,
+ uint256 tokenOutRate,
+ uint256 totalFee,
+ uint256 tokenOutDecimals
+ ) private returns (uint256 obtainedLiquidityBase18, uint256 lpFeePortion) {
+ try
+ this._obtainLoanLpLiquidityExternal(
+ tokenOut,
+ missingAmountBase18,
+ totalAmount,
+ tokenOutRate,
+ totalFee,
+ tokenOutDecimals
+ )
+ returns (uint256 _obtainedLiquidityBase18, uint256 _lpFeePortion) {
+ (obtainedLiquidityBase18, lpFeePortion) = (
+ _obtainedLiquidityBase18,
+ _lpFeePortion
+ );
+ } catch {
+ // do nothing
+ }
+ }
+
+ /**
+ * @dev Check if contract has enough tokenOut balance for redeem,
+ * if not, obtains liquidity trough the custom strategies.
+ * In default implementation it does nothing.
+ * @return obtainedLiquidityBase18 amount of tokenOut obtained
+ */
+ function _obtainVaultLiquidity(
+ address, /* tokenOut */
+ uint256, /* missingAmountBase18 */
+ uint256, /* tokenOutRate */
+ uint256, /* currentTokenOutBalanceBase18 */
+ uint256 /* tokenOutDecimals */
)
internal
+ virtual
returns (
- uint256 requestId,
- CalcAndValidateRedeemResult memory calcResult
+ uint256 /* obtainedLiquidityBase18 */
)
{
- if (!isFiat) {
- require(
- tokenOut != MANUAL_FULLFILMENT_TOKEN,
- "RV: tokenOut == fiat"
+ return 0;
+ }
+
+ /**
+ * @notice This function can only be called by the contract itself (self-call restriction)
+ * @dev only calls _obtainVaultLiquidity internally and external because its used with try/catch
+ * @param tokenOut tokenOut address
+ * @param missingAmountBase18 amount of tokenOut needed in base 18
+ * @param tokenOutRate tokenOut rate
+ * @param currentTokenOutBalanceBase18 current balance of tokenOut in the vault in base 18
+ * @param tokenOutDecimals decimals of tokenOut
+ * @return obtainedLiquidityBase18 amount of tokenOut obtained
+ */
+ // solhint-disable-next-line private-vars-leading-underscore
+ function _obtainVaultLiquidityExternal(
+ address tokenOut,
+ uint256 missingAmountBase18,
+ uint256 tokenOutRate,
+ uint256 currentTokenOutBalanceBase18,
+ uint256 tokenOutDecimals
+ )
+ external
+ returns (
+ uint256 /* obtainedLiquidityBase18 */
+ )
+ {
+ _requireSelfCall();
+ return
+ _obtainVaultLiquidity(
+ tokenOut,
+ missingAmountBase18,
+ tokenOutRate,
+ currentTokenOutBalanceBase18,
+ tokenOutDecimals
);
+ }
+
+ /**
+ * @notice This function can only be called by the contract itself (self-call restriction)
+ * @dev Check if contract has enough tokenOut balance for redeem;
+ * if not, redeem the missing amount via loan LP liquidity
+ * @param tokenOut tokenOut address
+ * @param missingAmountBase18 amount of tokenOut needed in base 18
+ * @param totalAmount total amount of tokenOut needed in base 18
+ * @param tokenOutRate tokenOut rate
+ * @param totalFee total fee of tokenOut
+ * @param tokenOutDecimals decimals of tokenOut
+ */
+ // solhint-disable-next-line private-vars-leading-underscore
+ function _obtainLoanLpLiquidityExternal(
+ address tokenOut,
+ uint256 missingAmountBase18,
+ uint256 totalAmount,
+ uint256 tokenOutRate,
+ uint256 totalFee,
+ uint256 tokenOutDecimals
+ )
+ external
+ returns (
+ uint256, /* amountReceivedBase18 */
+ uint256 /* feePortionBase18 */
+ )
+ {
+ _requireSelfCall();
+
+ address _loanLp = loanLp;
+ IRedemptionVault _loanSwapperVault = loanSwapperVault;
+
+ if (missingAmountBase18 == 0) {
+ return (0, 0);
}
- address user = msg.sender;
+ // loan lp is not configured
+ if (_loanLp == address(0) || address(_loanSwapperVault) == address(0)) {
+ return (0, 0);
+ }
- calcResult = _calcAndValidateRedeem(
- user,
- tokenOut,
- amountMTokenIn,
- false,
- isFiat
- );
+ if (!_loanSwapperVault.waivedFeeRestriction(address(this))) {
+ return (0, 0);
+ }
+
+ uint256 mTokenARate;
+ IERC20 mTokenA;
+ uint256 grossTokenOutAmount;
+
+ // prevent stack too deep errors
+ {
+ uint256 mTokenABalance;
+
+ (
+ mTokenARate,
+ mTokenA,
+ mTokenABalance
+ ) = RedemptionSwapperHelpersLibrary.getSwapperDetails(
+ _loanSwapperVault,
+ _loanLp
+ );
+
+ grossTokenOutAmount = Math.mulDiv(
+ mTokenABalance,
+ mTokenARate,
+ tokenOutRate,
+ Math.Rounding.Up
+ );
+ }
+
+ if (grossTokenOutAmount > missingAmountBase18) {
+ grossTokenOutAmount = missingAmountBase18;
+ }
- address tokenOutCopy = tokenOut;
+ if (grossTokenOutAmount == 0) {
+ return (0, 0);
+ }
- // assigning the default value which is gonna be used
- // only for fiat redemptions
- uint256 tokenOutRate = 1e18;
+ uint256 lpFeePortion = _truncate(
+ (totalFee * grossTokenOutAmount) / totalAmount,
+ tokenOutDecimals
+ );
- if (!isFiat) {
- TokenConfig storage config = tokensConfig[tokenOutCopy];
- tokenOutRate = _getTokenRate(config.dataFeed, config.stable);
+ if (grossTokenOutAmount == lpFeePortion) {
+ return (0, lpFeePortion);
}
- uint256 mTokenRate = mTokenDataFeed.getDataInBase18();
+ address _tokenOut = tokenOut;
+ uint256 _tokenOutDecimals = tokenOutDecimals;
+
+ // Ceil so the inner vault's floored output is still >= net token out amount.
+ // Requires address(this) to have waivedFeeRestriction on the inner vault.
+ uint256 mTokenAAmount = Math.mulDiv(
+ grossTokenOutAmount - lpFeePortion,
+ tokenOutRate,
+ mTokenARate,
+ Math.Rounding.Up
+ );
+
+ return (
+ RedemptionSwapperHelpersLibrary.redeemInstantSwapper(
+ _loanSwapperVault,
+ mTokenA,
+ _loanLp,
+ _tokenOut,
+ mTokenAAmount,
+ _tokenOutDecimals
+ ),
+ lpFeePortion
+ );
+ }
+
+ /**
+ * @notice internal redeem request logic
+ * @param tokenOut tokenOut address
+ * @param amountMTokenIn amount of mToken (decimals 18)
+ * @param recipient recipient address
+ * @param amountMTokenInstant amount of mToken that was redeemed instantly
+ *
+ * @return requestId request id
+ */
+ function _redeemRequest(
+ address tokenOut,
+ uint256 amountMTokenIn,
+ address recipient,
+ uint256 amountMTokenInstant
+ ) private returns (uint256 requestId) {
+ _requireTokenExists(tokenOut);
+
+ address user = msg.sender;
+
+ _validateMTokenAmount(user, amountMTokenIn);
+
+ _validateInstantFee();
+
+ (, uint256 mTokenRate, uint256 tokenOutRate) = _convertMTokenToTokenOut(
+ amountMTokenIn,
+ 0,
+ tokenOut,
+ 0
+ );
_tokenTransferFromUser(
address(mToken),
- address(this),
- calcResult.amountMTokenWithoutFee,
+ address(requestRedeemer),
+ amountMTokenIn,
18 // mToken always have 18 decimals
);
- if (calcResult.feeAmount > 0)
- _tokenTransferFromUser(
- address(mToken),
- feeReceiver,
- calcResult.feeAmount,
- 18
- );
- requestId = currentRequestId.current();
- currentRequestId.increment();
+ requestId = currentRequestId++;
+
+ uint256 feePercent = _getFee(user, tokenOut, false);
redeemRequests[requestId] = Request({
- sender: recipient,
- tokenOut: tokenOutCopy,
+ recipient: recipient,
+ tokenOut: tokenOut,
status: RequestStatus.Pending,
- amountMToken: calcResult.amountMTokenWithoutFee,
+ amountMToken: amountMTokenIn,
mTokenRate: mTokenRate,
- tokenOutRate: tokenOutRate
+ tokenOutRate: tokenOutRate,
+ feePercent: feePercent,
+ amountMTokenInstant: amountMTokenInstant,
+ approvedMTokenRate: 0,
+ amountTokenOut: 0
});
- return (requestId, calcResult);
+ emit RedeemRequest(
+ requestId,
+ msg.sender,
+ tokenOut,
+ recipient,
+ amountMTokenIn,
+ amountMTokenInstant,
+ feePercent,
+ mTokenRate,
+ tokenOutRate
+ );
}
/**
* @dev calculates tokenOut amount from USD amount
* @param amountUsd amount of USD (decimals 18)
* @param tokenOut tokenOut address
- *
+ * @param overrideTokenRate override token rate if not zero
+
* @return amountToken converted USD to tokenOut
* @return tokenRate conversion rate
*/
- function _convertUsdToToken(uint256 amountUsd, address tokenOut)
- internal
- view
- returns (uint256 amountToken, uint256 tokenRate)
- {
- require(amountUsd > 0, "RV: amount zero");
-
- TokenConfig storage tokenConfig = tokensConfig[tokenOut];
-
- tokenRate = _getTokenRate(tokenConfig.dataFeed, tokenConfig.stable);
- require(tokenRate > 0, "RV: rate zero");
+ function _convertUsdToToken(
+ uint256 amountUsd,
+ address tokenOut,
+ uint256 overrideTokenRate
+ ) internal view returns (uint256 amountToken, uint256 tokenRate) {
+ tokenRate = overrideTokenRate > 0
+ ? overrideTokenRate
+ : _getPTokenRate(tokenOut);
amountToken = (amountUsd * (10**18)) / tokenRate;
}
@@ -737,18 +1104,18 @@ contract RedemptionVault is ManageableVault, IRedemptionVault {
/**
* @dev calculates USD amount from mToken amount
* @param amountMToken amount of mToken (decimals 18)
+ * @param overrideTokenRate override mToken rate if not zero
*
* @return amountUsd converted amount to USD
* @return mTokenRate conversion rate
*/
- function _convertMTokenToUsd(uint256 amountMToken)
- internal
- view
- returns (uint256 amountUsd, uint256 mTokenRate)
- {
- require(amountMToken > 0, "RV: amount zero");
-
- mTokenRate = _getMTokenRate();
+ function _convertMTokenToUsd(
+ uint256 amountMToken,
+ uint256 overrideTokenRate
+ ) internal view returns (uint256 amountUsd, uint256 mTokenRate) {
+ mTokenRate = overrideTokenRate > 0
+ ? overrideTokenRate
+ : _getMTokenRate();
amountUsd = (amountMToken * mTokenRate) / (10**18);
}
@@ -758,8 +1125,11 @@ contract RedemptionVault is ManageableVault, IRedemptionVault {
* @param user user address
* @param tokenOut tokenOut address
* @param amountMTokenIn mToken amount (decimals 18)
+ * @param overrideMTokenRate override mToken rate if not zero
+ * @param overrideTokenOutRate override token rate if not zero
+ * @param shouldOverrideFeePercent should override fee percent if true
+ * @param overrideFeePercent override fee percent if shouldOverrideFeePercent is true
* @param isInstant is instant operation
- * @param isFiat is fiat operation
*
* @return result calc result
*/
@@ -767,68 +1137,125 @@ contract RedemptionVault is ManageableVault, IRedemptionVault {
address user,
address tokenOut,
uint256 amountMTokenIn,
- bool isInstant,
- bool isFiat
- ) internal view returns (CalcAndValidateRedeemResult memory result) {
- require(amountMTokenIn > 0, "RV: invalid amount");
-
- if (!isFreeFromMinAmount[user]) {
- uint256 minRedeemAmount = isFiat ? minFiatRedeemAmount : minAmount;
- require(minRedeemAmount <= amountMTokenIn, "RV: amount < min");
- }
+ uint256 overrideMTokenRate,
+ uint256 overrideTokenOutRate,
+ bool shouldOverrideFeePercent,
+ uint256 overrideFeePercent,
+ bool isInstant
+ )
+ internal
+ view
+ virtual
+ returns (CalcAndValidateRedeemResult memory result)
+ {
+ _requireTokenExists(tokenOut);
+
+ _validateMTokenAmount(user, amountMTokenIn);
+
+ (
+ uint256 amountTokenOut,
+ uint256 mTokenRate,
+ uint256 tokenOutRate
+ ) = _convertMTokenToTokenOut(
+ amountMTokenIn,
+ overrideMTokenRate,
+ tokenOut,
+ overrideTokenOutRate
+ );
+
+ result.tokenOutDecimals = _tokenDecimals(tokenOut);
+ result.tokenOutRate = tokenOutRate;
+ result.mTokenRate = mTokenRate;
result.feeAmount = _getFeeAmount(
- user,
- tokenOut,
- amountMTokenIn,
- isInstant,
- isFiat ? fiatAdditionalFee : 0
+ shouldOverrideFeePercent
+ ? overrideFeePercent
+ : _getFee(user, tokenOut, isInstant),
+ amountTokenOut
);
- if (isFiat) {
- require(
- tokenOut == MANUAL_FULLFILMENT_TOKEN,
- "RV: tokenOut != fiat"
- );
- if (!waivedFeeRestriction[user]) result.feeAmount += fiatFlatFee;
- } else {
- _requireTokenExists(tokenOut);
- }
+ amountTokenOut = _truncate(amountTokenOut, result.tokenOutDecimals);
+ result.feeAmount = _truncate(result.feeAmount, result.tokenOutDecimals);
+
+ require(
+ amountTokenOut > result.feeAmount,
+ FeeExceedsAmount(result.feeAmount, amountTokenOut)
+ );
- require(amountMTokenIn > result.feeAmount, "RV: amountMTokenIn < fee");
+ result.amountTokenOut = amountTokenOut;
- result.amountMTokenWithoutFee = amountMTokenIn - result.feeAmount;
+ result.amountTokenOutWithoutFee = amountTokenOut - result.feeAmount;
}
- /*
- * @dev validates that liquidity of provided token on `requestRedeemer` is enough
- * @param token token address
- * @param requiredLiquidity minimum required liquidity of `requestRedeemer`
- * @param tokenDecimals `token` decimals
+ /**
+ * @dev converts mToken to tokenOut amount
+ * @param amountMTokenIn amount of mToken
+ * @param overrideMTokenRate override mToken rate if not zero
+ * @param tokenOut tokenOut address
+ * @param overrideTokenOutRate override token rate if not zero
*
- * @return false if not enough liquidity, otherwise true
+ * @return amountTokenOut amount of tokenOut
+ * @return mTokenRate conversion rate
+ * @return tokenOutRate conversion rate
*/
- function _validateLiquidity(
- address token,
- uint256 requiredLiquidity,
- uint256 tokenDecimals
+ function _convertMTokenToTokenOut(
+ uint256 amountMTokenIn,
+ uint256 overrideMTokenRate,
+ address tokenOut,
+ uint256 overrideTokenOutRate
)
- internal
+ private
view
returns (
- bool /* success */
+ uint256,
+ uint256,
+ uint256
)
{
- uint256 balance = IERC20(token).balanceOf(requestRedeemer);
- return balance >= requiredLiquidity.convertFromBase18(tokenDecimals);
+ (uint256 amountMTokenInUsd, uint256 mTokenRate) = _convertMTokenToUsd(
+ amountMTokenIn,
+ overrideMTokenRate
+ );
+ (uint256 amountTokenOut, uint256 tokenOutRate) = _convertUsdToToken(
+ amountMTokenInUsd,
+ tokenOut,
+ overrideTokenOutRate
+ );
+ return (amountTokenOut, mTokenRate, tokenOutRate);
+ }
+
+ /**
+ * @dev reverts if the caller is not the contract itself
+ */
+ function _requireSelfCall() private view {
+ require(msg.sender == address(this), NotSelfCall());
}
/**
- * @dev gets and validates mToken rate
- * @return mTokenRate mToken rate
+ * @dev calculates holdback part rate from avg rate
+ * @param request request
+ * @param avgMTokenRate avg mToken rate
+ * @return holdback part rate
*/
- function _getMTokenRate() private view returns (uint256 mTokenRate) {
- mTokenRate = _getTokenRate(address(mTokenDataFeed), false);
- require(mTokenRate > 0, "RV: rate zero");
+ function _calculateHoldbackPartRateFromAvg(
+ Request memory request,
+ uint256 avgMTokenRate
+ ) internal pure returns (uint256) {
+ if (request.amountMTokenInstant == 0) {
+ return 0;
+ }
+
+ uint256 targetTotalValue = ((request.amountMToken +
+ request.amountMTokenInstant) * avgMTokenRate) / (10**18);
+ uint256 instantPartValue = ((request.amountMTokenInstant *
+ request.mTokenRate) / (10**18));
+
+ if (targetTotalValue <= instantPartValue) {
+ return 0;
+ }
+
+ uint256 holdbackPartValue = targetTotalValue - instantPartValue;
+
+ return (holdbackPartValue * (10**18)) / request.amountMToken;
}
}
diff --git a/contracts/RedemptionVaultWithAave.sol b/contracts/RedemptionVaultWithAave.sol
index 2ac74b85..bb56ed88 100644
--- a/contracts/RedemptionVaultWithAave.sol
+++ b/contracts/RedemptionVaultWithAave.sol
@@ -1,12 +1,12 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import {IERC20Upgradeable as IERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
-import "./RedemptionVault.sol";
+import {RedemptionVault} from "./RedemptionVault.sol";
-import "./interfaces/aave/IAaveV3Pool.sol";
-import "./libraries/DecimalsCorrectionLibrary.sol";
+import {IAaveV3Pool} from "./interfaces/aave/IAaveV3Pool.sol";
+import {DecimalsCorrectionLibrary} from "./libraries/DecimalsCorrectionLibrary.sol";
/**
* @title RedemptionVaultWithAave
@@ -30,22 +30,49 @@ contract RedemptionVaultWithAave is RedemptionVault {
/**
* @notice Emitted when an Aave V3 Pool is configured for a payment token
- * @param caller address of the caller
* @param token payment token address
* @param pool Aave V3 Pool address
*/
- event SetAavePool(
- address indexed caller,
- address indexed token,
- address indexed pool
- );
+ event SetAavePool(address indexed token, address indexed pool);
/**
* @notice Emitted when an Aave V3 Pool is removed for a payment token
- * @param caller address of the caller
* @param token payment token address
*/
- event RemoveAavePool(address indexed caller, address indexed token);
+ event RemoveAavePool(address indexed token);
+
+ /**
+ * @notice when token is not in aave pool
+ * @param aavePool Aave V3 Pool address
+ * @param token token address
+ */
+ error TokenNotInPool(address aavePool, address token);
+
+ /**
+ * @notice when pool is not set
+ * @param token token address
+ */
+ error PoolNotSet(address token);
+
+ /**
+ * @notice when insufficient withdrawn amount
+ * @param withdrawnAmount withdrawn amount
+ * @param toWithdraw amount to withdraw
+ */
+ error InsufficientWithdrawnAmount(
+ uint256 withdrawnAmount,
+ uint256 toWithdraw
+ );
+
+ /**
+ * @notice Passes role identifiers to the base RedemptionVault constructor
+ * @param _contractAdminRole contract admin role identifier
+ * @param _greenlistedRole greenlisted role identifier
+ * @custom:oz-upgrades-unsafe-allow constructor
+ */
+ constructor(bytes32 _contractAdminRole, bytes32 _greenlistedRole)
+ RedemptionVault(_contractAdminRole, _greenlistedRole)
+ {}
/**
* @notice Sets the Aave V3 Pool for a specific payment token
@@ -54,148 +81,90 @@ contract RedemptionVaultWithAave is RedemptionVault {
*/
function setAavePool(address _token, address _aavePool)
external
- onlyVaultAdmin
+ onlyContractAdmin
{
_validateAddress(_token, true);
_validateAddress(_aavePool, true);
require(
IAaveV3Pool(_aavePool).getReserveAToken(_token) != address(0),
- "RVA: token not in pool"
+ TokenNotInPool(_aavePool, _token)
);
aavePools[_token] = IAaveV3Pool(_aavePool);
- emit SetAavePool(msg.sender, _token, _aavePool);
+ emit SetAavePool(_token, _aavePool);
}
/**
* @notice Removes the Aave V3 Pool for a specific payment token
* @param _token payment token address
*/
- function removeAavePool(address _token) external onlyVaultAdmin {
- require(address(aavePools[_token]) != address(0), "RVA: pool not set");
+ function removeAavePool(address _token) external onlyContractAdmin {
+ require(address(aavePools[_token]) != address(0), PoolNotSet(_token));
delete aavePools[_token];
- emit RemoveAavePool(msg.sender, _token);
+ emit RemoveAavePool(_token);
}
/**
- * @dev Redeem mToken to the selected payment token if daily limit and allowance are not exceeded.
- * If the contract doesn't have enough payment token, the Aave V3 withdrawal flow will be
- * triggered to withdraw the missing amount from the Aave Pool.
- * Burns mToken from the user.
- * Transfers fee in mToken to feeReceiver.
- * Transfers tokenOut to user.
- * @param tokenOut token out address
- * @param amountMTokenIn amount of mToken to redeem
- * @param minReceiveAmount minimum expected amount of tokenOut to receive (decimals 18)
- * @param recipient address that will receive the tokenOut
+ * @notice Check if contract has enough tokenOut balance for redeem;
+ * if not, withdraw the missing amount from the Aave V3 Pool
+ * @dev The Aave Pool burns the vault's aTokens and transfers the underlying
+ * asset directly to this contract. No approval is needed because the Pool
+ * burns aTokens from msg.sender (this contract) internally.
+ * @param tokenOut tokenOut address
+ * @param missingAmountBase18 amount of tokenOut needed in base 18
+ * @param tokenOutDecimals decimals of tokenOut
*/
- function _redeemInstant(
+ function _obtainVaultLiquidity(
address tokenOut,
- uint256 amountMTokenIn,
- uint256 minReceiveAmount,
- address recipient
+ uint256 missingAmountBase18,
+ uint256, /* tokenOutRate */
+ uint256, /* currentTokenOutBalanceBase18 */
+ uint256 tokenOutDecimals
)
internal
+ virtual
override
returns (
- CalcAndValidateRedeemResult memory calcResult,
- uint256 amountTokenOutWithoutFee
+ uint256 /* obtainedLiquidityBase18 */
)
{
- address user = msg.sender;
-
- calcResult = _calcAndValidateRedeem(
- user,
- tokenOut,
- amountMTokenIn,
- true,
- false
- );
-
- _requireAndUpdateLimit(amountMTokenIn);
-
- uint256 tokenDecimals = _tokenDecimals(tokenOut);
+ IAaveV3Pool pool = aavePools[tokenOut];
- uint256 amountMTokenInCopy = amountMTokenIn;
- address tokenOutCopy = tokenOut;
- uint256 minReceiveAmountCopy = minReceiveAmount;
+ // if we dont have a pool for the token, we can't withdraw, so do nothing
+ if (address(pool) == address(0)) {
+ return 0;
+ }
- (uint256 amountMTokenInUsd, uint256 mTokenRate) = _convertMTokenToUsd(
- amountMTokenInCopy
+ uint256 missingAmount = missingAmountBase18.convertFromBase18(
+ tokenOutDecimals
);
- (uint256 amountTokenOut, uint256 tokenOutRate) = _convertUsdToToken(
- amountMTokenInUsd,
- tokenOutCopy
- );
-
- _requireAndUpdateAllowance(tokenOutCopy, amountTokenOut);
-
- mToken.burn(user, calcResult.amountMTokenWithoutFee);
- if (calcResult.feeAmount > 0)
- _tokenTransferFromUser(
- address(mToken),
- feeReceiver,
- calcResult.feeAmount,
- 18
- );
- uint256 amountTokenOutWithoutFeeFrom18 = ((calcResult
- .amountMTokenWithoutFee * mTokenRate) / tokenOutRate)
- .convertFromBase18(tokenDecimals);
+ address aToken = pool.getReserveAToken(tokenOut);
- amountTokenOutWithoutFee = amountTokenOutWithoutFeeFrom18
- .convertToBase18(tokenDecimals);
+ // if we cant find the aToken, we can't withdraw, so do nothing
+ if (aToken == address(0)) {
+ return 0;
+ }
- require(
- amountTokenOutWithoutFee >= minReceiveAmountCopy,
- "RVA: minReceiveAmount > actual"
- );
+ uint256 aTokenBalance = IERC20(aToken).balanceOf(address(this));
- _checkAndRedeemAave(tokenOutCopy, amountTokenOutWithoutFeeFrom18);
+ uint256 toWithdraw = aTokenBalance >= missingAmount
+ ? missingAmount
+ : aTokenBalance;
- _tokenTransferToUser(
- tokenOutCopy,
- recipient,
- amountTokenOutWithoutFee,
- tokenDecimals
- );
- }
+ if (toWithdraw == 0) {
+ return 0;
+ }
- /**
- * @notice Check if contract has enough tokenOut balance for redeem;
- * if not, withdraw the missing amount from the Aave V3 Pool
- * @dev The Aave Pool burns the vault's aTokens and transfers the underlying
- * asset directly to this contract. No approval is needed because the Pool
- * burns aTokens from msg.sender (this contract) internally.
- * @param tokenOut tokenOut address
- * @param amountTokenOut amount of tokenOut needed
- */
- function _checkAndRedeemAave(address tokenOut, uint256 amountTokenOut)
- internal
- {
- uint256 contractBalanceTokenOut = IERC20(tokenOut).balanceOf(
+ uint256 withdrawnAmount = pool.withdraw(
+ tokenOut,
+ toWithdraw,
address(this)
);
- if (contractBalanceTokenOut >= amountTokenOut) return;
-
- IAaveV3Pool pool = aavePools[tokenOut];
- require(address(pool) != address(0), "RVA: no pool for token");
-
- uint256 missingAmount = amountTokenOut - contractBalanceTokenOut;
-
- address aToken = pool.getReserveAToken(tokenOut);
- require(aToken != address(0), "RVA: token not in Aave pool");
-
- uint256 aTokenBalance = IERC20(aToken).balanceOf(address(this));
require(
- aTokenBalance >= missingAmount,
- "RVA: insufficient aToken balance"
+ withdrawnAmount >= toWithdraw,
+ InsufficientWithdrawnAmount(withdrawnAmount, toWithdraw)
);
- uint256 withdrawnAmount = pool.withdraw(
- tokenOut,
- missingAmount,
- address(this)
- );
- require(withdrawnAmount >= missingAmount, "RVA: withdrawn < needed");
+ return withdrawnAmount.convertToBase18(tokenOutDecimals);
}
}
diff --git a/contracts/RedemptionVaultWithBUIDL.sol b/contracts/RedemptionVaultWithBUIDL.sol
deleted file mode 100644
index 488ce356..00000000
--- a/contracts/RedemptionVaultWithBUIDL.sol
+++ /dev/null
@@ -1,228 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import {IERC20Upgradeable as IERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
-import {SafeERC20Upgradeable as SafeERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
-
-import "./RedemptionVault.sol";
-
-import "./interfaces/buidl/IRedemption.sol";
-import "./libraries/DecimalsCorrectionLibrary.sol";
-
-/**
- * @title RedemptionVault
- * @notice Smart contract that handles mToken redemptions
- * @author RedDuck Software
- */
-contract RedemptionVaultWIthBUIDL is RedemptionVault {
- using DecimalsCorrectionLibrary for uint256;
- using SafeERC20 for IERC20;
-
- /**
- * @notice minimum amount of BUIDL to redeem. Will redeem at least this amount of BUIDL.
- */
- uint256 public minBuidlToRedeem;
-
- uint256 public minBuidlBalance;
-
- IRedemption public buidlRedemption;
-
- uint256[50] private __gap;
-
- /**
- * @param minBuidlToRedeem new min amount of BUIDL to redeem
- * @param sender address who set new min amount of BUIDL to redeem
- */
- event SetMinBuidlToRedeem(uint256 minBuidlToRedeem, address sender);
-
- /**
- * @param minBuidlBalance new `minBuidlBalance` value
- * @param sender address who set new `minBuidlBalance`
- */
- event SetMinBuidlBalance(uint256 minBuidlBalance, address sender);
-
- /**
- * @notice upgradeable pattern contract`s initializer
- * @param _ac address of MidasAccessControll contract
- * @param _mTokenInitParams init params for mToken
- * @param _receiversInitParams init params for receivers
- * @param _instantInitParams init params for instant operations
- * @param _sanctionsList address of sanctionsList contract
- * @param _variationTolerance percent of prices diviation 1% = 100
- * @param _minAmount basic min amount for operations
- * @param _fiatRedemptionInitParams params fiatAdditionalFee, fiatFlatFee, minFiatRedeemAmount
- * @param _buidlRedemption BUIDL redemption contract address
- * @param _requestRedeemer address is designated for standard redemptions, allowing tokens to be pulled from this address
- */
- function initialize(
- address _ac,
- MTokenInitParams calldata _mTokenInitParams,
- ReceiversInitParams calldata _receiversInitParams,
- InstantInitParams calldata _instantInitParams,
- address _sanctionsList,
- uint256 _variationTolerance,
- uint256 _minAmount,
- FiatRedeptionInitParams calldata _fiatRedemptionInitParams,
- address _requestRedeemer,
- address _buidlRedemption,
- uint256 _minBuidlToRedeem,
- uint256 _minBuidlBalance
- ) external initializer {
- __RedemptionVault_init(
- _ac,
- _mTokenInitParams,
- _receiversInitParams,
- _instantInitParams,
- _sanctionsList,
- _variationTolerance,
- _minAmount,
- _fiatRedemptionInitParams,
- _requestRedeemer
- );
- _validateAddress(_buidlRedemption, false);
- buidlRedemption = IRedemption(_buidlRedemption);
- minBuidlToRedeem = _minBuidlToRedeem;
- minBuidlBalance = _minBuidlBalance;
- }
-
- /**
- * @notice set min amount of BUIDL to redeem.
- * @param _minBuidlToRedeem min amount of BUIDL to redeem
- */
- function setMinBuidlToRedeem(uint256 _minBuidlToRedeem)
- external
- onlyVaultAdmin
- {
- minBuidlToRedeem = _minBuidlToRedeem;
-
- emit SetMinBuidlToRedeem(_minBuidlToRedeem, msg.sender);
- }
-
- /**
- * @notice set new `minBuidlBalance` value.
- * @param _minBuidlBalance new `minBuidlBalance` value
- */
- function setMinBuidlBalance(uint256 _minBuidlBalance)
- external
- onlyVaultAdmin
- {
- minBuidlBalance = _minBuidlBalance;
-
- emit SetMinBuidlBalance(_minBuidlBalance, msg.sender);
- }
-
- /**
- * @dev redeem mToken to USDC if daily limit and allowance not exceeded
- * If contract don't have enough USDC, BUIDL redemption flow will be triggered
- * Burns mToken from the user.
- * Transfers fee in mToken to feeReceiver
- * Transfers tokenOut to user.
- * @param tokenOut token out address, always ignore
- * @param amountMTokenIn amount of mToken to redeem
- * @param minReceiveAmount minimum expected amount of tokenOut to receive (decimals 18)
- */
- function _redeemInstant(
- address tokenOut,
- uint256 amountMTokenIn,
- uint256 minReceiveAmount,
- address recipient
- )
- internal
- override
- returns (
- CalcAndValidateRedeemResult memory calcResult,
- uint256 amountTokenOutWithoutFee
- )
- {
- address user = msg.sender;
-
- require(buidlRedemption.liquidity() == tokenOut, "RVB: invalid token");
-
- calcResult = _calcAndValidateRedeem(
- user,
- tokenOut,
- amountMTokenIn,
- true,
- false
- );
-
- _requireAndUpdateLimit(amountMTokenIn);
-
- uint256 tokenDecimals = _tokenDecimals(tokenOut);
-
- uint256 amountMTokenInCopy = amountMTokenIn;
- address tokenOutCopy = tokenOut;
- uint256 minReceiveAmountCopy = minReceiveAmount;
-
- (uint256 amountMTokenInUsd, uint256 mTokenRate) = _convertMTokenToUsd(
- amountMTokenInCopy
- );
- (uint256 amountTokenOut, uint256 tokenOutRate) = _convertUsdToToken(
- amountMTokenInUsd,
- tokenOutCopy
- );
-
- _requireAndUpdateAllowance(tokenOutCopy, amountTokenOut);
-
- mToken.burn(user, calcResult.amountMTokenWithoutFee);
- if (calcResult.feeAmount > 0)
- _tokenTransferFromUser(
- address(mToken),
- feeReceiver,
- calcResult.feeAmount,
- 18
- );
-
- amountTokenOutWithoutFee =
- (calcResult.amountMTokenWithoutFee * mTokenRate) /
- tokenOutRate;
-
- require(
- amountTokenOutWithoutFee >= minReceiveAmountCopy,
- "RVB: minReceiveAmount > actual"
- );
-
- uint256 amountTokenOutWithoutFeeFrom18 = amountTokenOutWithoutFee
- .convertFromBase18(tokenDecimals);
-
- _checkAndRedeemBUIDL(tokenOutCopy, amountTokenOutWithoutFeeFrom18);
-
- _tokenTransferToUser(
- tokenOutCopy,
- recipient,
- amountTokenOutWithoutFeeFrom18.convertToBase18(tokenDecimals),
- tokenDecimals
- );
- }
-
- /**
- * @notice Check if contract have enough USDC balance for redeem
- * if don't have trigger BUIDL redemption flow
- * @param tokenOut tokenOut address
- * @param amountTokenOut amount of tokenOut
- */
- function _checkAndRedeemBUIDL(address tokenOut, uint256 amountTokenOut)
- internal
- {
- uint256 contractBalanceTokenOut = IERC20(tokenOut).balanceOf(
- address(this)
- );
- if (contractBalanceTokenOut >= amountTokenOut) return;
-
- uint256 buidlToRedeem = amountTokenOut - contractBalanceTokenOut;
- if (buidlToRedeem < minBuidlToRedeem) {
- buidlToRedeem = minBuidlToRedeem;
- }
- IERC20 buidl = IERC20(buidlRedemption.asset());
- uint256 buidlBalance = buidl.balanceOf(address(this));
- require(buidlBalance >= buidlToRedeem, "RVB: buidlToRedeem > balance");
- if (
- buidlBalance - buidlToRedeem <= minBuidlToRedeem ||
- buidlBalance - buidlToRedeem <= minBuidlBalance
- ) {
- buidlToRedeem = buidlBalance;
- }
- buidl.safeIncreaseAllowance(address(buidlRedemption), buidlToRedeem);
- buidlRedemption.redeem(buidlToRedeem);
- }
-}
diff --git a/contracts/RedemptionVaultWithMToken.sol b/contracts/RedemptionVaultWithMToken.sol
index 7fa9024d..edc1be12 100644
--- a/contracts/RedemptionVaultWithMToken.sol
+++ b/contracts/RedemptionVaultWithMToken.sol
@@ -1,43 +1,32 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import {IERC20Upgradeable as IERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import {SafeERC20Upgradeable as SafeERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
-import "./RedemptionVault.sol";
-import "./interfaces/IRedemptionVault.sol";
-import "./libraries/DecimalsCorrectionLibrary.sol";
+import {RedemptionVault} from "./RedemptionVault.sol";
+import {ManageableVault} from "./abstract/ManageableVault.sol";
+import {DecimalsCorrectionLibrary} from "./libraries/DecimalsCorrectionLibrary.sol";
+import {IRedemptionVault, RedemptionVaultInitParams} from "./interfaces/IRedemptionVault.sol";
+import {CommonVaultInitParams} from "./interfaces/IManageableVault.sol";
+import {RedemptionSwapperHelpersLibrary} from "./libraries/RedemptionSwapperHelpersLibrary.sol";
/**
* @title RedemptionVaultWithMToken
* @notice Smart contract that handles redemptions using mToken RedemptionVault withdrawals
- * @dev Storage layout is preserved for safe upgrades from RedemptionVaultWithSwapper
* @author RedDuck Software
*/
contract RedemptionVaultWithMToken is RedemptionVault {
using DecimalsCorrectionLibrary for uint256;
using SafeERC20 for IERC20;
- /**
- * @dev Storage gap preserved from RedemptionVaultWithSwapper layout
- */
- uint256[50] private ___gap;
-
/**
* @notice mToken RedemptionVault used for fallback redemptions
- * @custom:oz-renamed-from mTbillRedemptionVault
*/
IRedemptionVault public redemptionVault;
- /**
- * @dev Deprecated storage slot preserved for storage layout compatibility. Must not be removed
- * @custom:oz-renamed-from liquidityProvider
- */
- // solhint-disable-next-line var-name-mixedcase
- address public liquidityProvider_deprecated;
-
/**
* @dev leaving a storage gap for futures updates
*/
@@ -45,47 +34,32 @@ contract RedemptionVaultWithMToken is RedemptionVault {
/**
* @notice Emitted when the redemption vault address is updated
- * @param caller address of the caller
* @param newVault new redemption vault address
*/
- event SetRedemptionVault(address indexed caller, address indexed newVault);
+ event SetRedemptionVault(address indexed newVault);
+
+ /**
+ * @notice Passes role identifiers to the base RedemptionVault constructor
+ * @param _contractAdminRole contract admin role identifier
+ * @param _greenlistedRole greenlisted role identifier
+ * @custom:oz-upgrades-unsafe-allow constructor
+ */
+ constructor(bytes32 _contractAdminRole, bytes32 _greenlistedRole)
+ RedemptionVault(_contractAdminRole, _greenlistedRole)
+ {}
/**
* @notice upgradeable pattern contract`s initializer
- * @param _ac address of MidasAccessControll contract
- * @param _mTokenInitParams init params for mToken
- * @param _receiversInitParams init params for receivers
- * @param _instantInitParams init params for instant operations
- * @param _sanctionsList address of sanctionsList contract
- * @param _variationTolerance percent of prices diviation 1% = 100
- * @param _minAmount basic min amount for operations
- * @param _fiatRedemptionInitParams params fiatAdditionalFee, fiatFlatFee, minFiatRedeemAmount
- * @param _requestRedeemer address is designated for standard redemptions, allowing tokens to be pulled from this address
+ * @param _commonVaultInitParams init params for common vault
+ * @param _redemptionInitParams init params for redemption vault state values
* @param _redemptionVault address of the mTokenA RedemptionVault
*/
function initialize(
- address _ac,
- MTokenInitParams calldata _mTokenInitParams,
- ReceiversInitParams calldata _receiversInitParams,
- InstantInitParams calldata _instantInitParams,
- address _sanctionsList,
- uint256 _variationTolerance,
- uint256 _minAmount,
- FiatRedeptionInitParams calldata _fiatRedemptionInitParams,
- address _requestRedeemer,
+ CommonVaultInitParams calldata _commonVaultInitParams,
+ RedemptionVaultInitParams calldata _redemptionInitParams,
address _redemptionVault
- ) external initializer {
- __RedemptionVault_init(
- _ac,
- _mTokenInitParams,
- _receiversInitParams,
- _instantInitParams,
- _sanctionsList,
- _variationTolerance,
- _minAmount,
- _fiatRedemptionInitParams,
- _requestRedeemer
- );
+ ) external {
+ initialize(_commonVaultInitParams, _redemptionInitParams);
_validateAddress(_redemptionVault, true);
redemptionVault = IRedemptionVault(_redemptionVault);
}
@@ -96,142 +70,52 @@ contract RedemptionVaultWithMToken is RedemptionVault {
*/
function setRedemptionVault(address _redemptionVault)
external
- onlyVaultAdmin
+ onlyContractAdmin
{
require(
_redemptionVault != address(redemptionVault),
- "RVMT: already set"
+ SameAddressValue(_redemptionVault)
);
_validateAddress(_redemptionVault, true);
redemptionVault = IRedemptionVault(_redemptionVault);
- emit SetRedemptionVault(msg.sender, _redemptionVault);
+ emit SetRedemptionVault(_redemptionVault);
}
/**
- * @dev Redeem mToken to the selected payment token if daily limit and allowance are not exceeded.
- * If the contract doesn't have enough payment token, the mToken RedemptionVault flow
- * will be triggered to redeem the missing amount.
- * Burns mToken from the user.
- * Transfers fee in mToken to feeReceiver.
- * Transfers tokenOut to user.
- * @param tokenOut token out address
- * @param amountMTokenIn amount of mToken to redeem
- * @param minReceiveAmount minimum expected amount of tokenOut to receive (decimals 18)
- * @param recipient address that will receive the tokenOut
+ * @notice Check if contract has enough tokenOut balance for redeem;
+ * if not, redeem the missing amount via mToken RedemptionVault
+ * @dev The other vault burns this contract's mToken and transfers the
+ * underlying asset to this contract
+ * @param tokenOut tokenOut address
+ * @param missingAmountBase18 amount of tokenOut needed in base 18
+ * @param tokenOutRate tokenOut rate
*/
- function _redeemInstant(
+ function _obtainVaultLiquidity(
address tokenOut,
- uint256 amountMTokenIn,
- uint256 minReceiveAmount,
- address recipient
+ uint256 missingAmountBase18,
+ uint256 tokenOutRate,
+ uint256, /* currentTokenOutBalanceBase18 */
+ uint256 tokenOutDecimals
)
internal
+ virtual
override
returns (
- CalcAndValidateRedeemResult memory calcResult,
- uint256 amountTokenOutWithoutFee
+ uint256 /* obtainedLiquidityBase18 */
)
{
- address user = msg.sender;
-
- calcResult = _calcAndValidateRedeem(
- user,
- tokenOut,
- amountMTokenIn,
- true,
- false
- );
-
- _requireAndUpdateLimit(amountMTokenIn);
-
- uint256 tokenDecimals = _tokenDecimals(tokenOut);
-
- uint256 amountMTokenInCopy = amountMTokenIn;
- address tokenOutCopy = tokenOut;
- uint256 minReceiveAmountCopy = minReceiveAmount;
-
- (uint256 amountMTokenInUsd, uint256 mTokenRate) = _convertMTokenToUsd(
- amountMTokenInCopy
- );
- (uint256 amountTokenOut, uint256 tokenOutRate) = _convertUsdToToken(
- amountMTokenInUsd,
- tokenOutCopy
- );
-
- _requireAndUpdateAllowance(tokenOutCopy, amountTokenOut);
-
- mToken.burn(user, calcResult.amountMTokenWithoutFee);
- if (calcResult.feeAmount > 0)
- _tokenTransferFromUser(
- address(mToken),
- feeReceiver,
- calcResult.feeAmount,
- 18
- );
-
- uint256 amountTokenOutWithoutFeeFrom18 = ((calcResult
- .amountMTokenWithoutFee * mTokenRate) / tokenOutRate)
- .convertFromBase18(tokenDecimals);
-
- amountTokenOutWithoutFee = amountTokenOutWithoutFeeFrom18
- .convertToBase18(tokenDecimals);
-
- require(
- amountTokenOutWithoutFee >= minReceiveAmountCopy,
- "RVMT: minReceiveAmount > actual"
- );
-
- _checkAndRedeemMToken(
- tokenOutCopy,
- amountTokenOutWithoutFeeFrom18,
- tokenOutRate
- );
-
- _tokenTransferToUser(
- tokenOutCopy,
- recipient,
- amountTokenOutWithoutFee,
- tokenDecimals
- );
- }
-
- /**
- * @notice Check if contract has enough tokenOut balance for redeem;
- * if not, redeem the missing amount via mToken RedemptionVault
- * @dev The other vault burns this contract's mToken and transfers the
- * underlying asset to this contract
- * @param tokenOut tokenOut address
- * @param amountTokenOut amount of tokenOut needed (native decimals)
- * @param tokenOutRate tokenOut price rate (decimals 18)
- */
- function _checkAndRedeemMToken(
- address tokenOut,
- uint256 amountTokenOut,
- uint256 tokenOutRate
- ) internal {
- uint256 tokenDecimals = _tokenDecimals(tokenOut);
- uint256 amountTokenOutBase18 = amountTokenOut.convertToBase18(
- tokenDecimals
- );
- uint256 contractBalanceBase18 = IERC20(tokenOut)
- .balanceOf(address(this))
- .convertToBase18(tokenDecimals);
- if (contractBalanceBase18 >= amountTokenOutBase18) return;
-
- require(
- ManageableVault(address(redemptionVault)).waivedFeeRestriction(
+ IRedemptionVault _redemptionVault = redemptionVault;
+
+ (
+ uint256 mTokenARate,
+ IERC20 mTokenA,
+ uint256 mTokenABalance
+ ) = RedemptionSwapperHelpersLibrary.getSwapperDetails(
+ _redemptionVault,
address(this)
- ),
- "RVMT: fees not waived on target"
- );
-
- uint256 missingAmountBase18 = amountTokenOutBase18 -
- contractBalanceBase18;
- uint256 mTokenARate = redemptionVault
- .mTokenDataFeed()
- .getDataInBase18();
+ );
// Ceil so the inner vault's floored output is still >= missingAmountBase18.
uint256 mTokenAAmount = Math.mulDiv(
@@ -241,22 +125,26 @@ contract RedemptionVaultWithMToken is RedemptionVault {
Math.Rounding.Up
);
- address mTokenA = address(redemptionVault.mToken());
-
- require(
- IERC20(mTokenA).balanceOf(address(this)) >= mTokenAAmount,
- "RVMT: balance < needed"
- );
-
- IERC20(mTokenA).safeIncreaseAllowance(
- address(redemptionVault),
- mTokenAAmount
- );
-
- redemptionVault.redeemInstant(
- tokenOut,
- mTokenAAmount,
- missingAmountBase18
- );
+ mTokenAAmount = mTokenABalance >= mTokenAAmount
+ ? mTokenAAmount
+ : mTokenABalance;
+
+ if (!_redemptionVault.waivedFeeRestriction(address(this))) {
+ return 0;
+ }
+
+ if (mTokenAAmount == 0) {
+ return 0;
+ }
+
+ return
+ RedemptionSwapperHelpersLibrary.redeemInstantSwapper(
+ _redemptionVault,
+ mTokenA,
+ address(this),
+ tokenOut,
+ mTokenAAmount,
+ tokenOutDecimals
+ );
}
}
diff --git a/contracts/RedemptionVaultWithMorpho.sol b/contracts/RedemptionVaultWithMorpho.sol
index bb4db3d8..8abe33f5 100644
--- a/contracts/RedemptionVaultWithMorpho.sol
+++ b/contracts/RedemptionVaultWithMorpho.sol
@@ -1,12 +1,12 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import {IERC20Upgradeable as IERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
-import "./RedemptionVault.sol";
+import {RedemptionVault} from "./RedemptionVault.sol";
import {IMorphoVault} from "./interfaces/morpho/IMorphoVault.sol";
-import "./libraries/DecimalsCorrectionLibrary.sol";
+import {DecimalsCorrectionLibrary} from "./libraries/DecimalsCorrectionLibrary.sol";
/**
* @title RedemptionVaultWithMorpho
@@ -31,22 +31,39 @@ contract RedemptionVaultWithMorpho is RedemptionVault {
/**
* @notice Emitted when a Morpho Vault is configured for a payment token
- * @param caller address of the caller
* @param token payment token address
* @param vault Morpho Vault address
*/
- event SetMorphoVault(
- address indexed caller,
- address indexed token,
- address indexed vault
- );
+ event SetMorphoVault(address indexed token, address indexed vault);
/**
* @notice Emitted when a Morpho Vault is removed for a payment token
- * @param caller address of the caller
* @param token payment token address
*/
- event RemoveMorphoVault(address indexed caller, address indexed token);
+ event RemoveMorphoVault(address indexed token);
+
+ /**
+ * @notice when asset mismatch
+ * @param morphoVault Morpho Vault address
+ * @param token token address
+ */
+ error AssetMismatch(address morphoVault, address token);
+
+ /**
+ * @notice when vault is not set
+ * @param token token address
+ */
+ error VaultNotSet(address token);
+
+ /**
+ * @notice Passes role identifiers to the base RedemptionVault constructor
+ * @param _contractAdminRole contract admin role identifier
+ * @param _greenlistedRole greenlisted role identifier
+ * @custom:oz-upgrades-unsafe-allow constructor
+ */
+ constructor(bytes32 _contractAdminRole, bytes32 _greenlistedRole)
+ RedemptionVault(_contractAdminRole, _greenlistedRole)
+ {}
/**
* @notice Sets the Morpho Vault for a specific payment token
@@ -55,113 +72,29 @@ contract RedemptionVaultWithMorpho is RedemptionVault {
*/
function setMorphoVault(address _token, address _morphoVault)
external
- onlyVaultAdmin
+ onlyContractAdmin
{
_validateAddress(_token, true);
_validateAddress(_morphoVault, true);
require(
IMorphoVault(_morphoVault).asset() == _token,
- "RVM: asset mismatch"
+ AssetMismatch(_morphoVault, _token)
);
morphoVaults[_token] = IMorphoVault(_morphoVault);
- emit SetMorphoVault(msg.sender, _token, _morphoVault);
+ emit SetMorphoVault(_token, _morphoVault);
}
/**
* @notice Removes the Morpho Vault for a specific payment token
* @param _token payment token address
*/
- function removeMorphoVault(address _token) external onlyVaultAdmin {
+ function removeMorphoVault(address _token) external onlyContractAdmin {
require(
address(morphoVaults[_token]) != address(0),
- "RVM: vault not set"
+ VaultNotSet(_token)
);
delete morphoVaults[_token];
- emit RemoveMorphoVault(msg.sender, _token);
- }
-
- /**
- * @dev Redeem mToken to the selected payment token if daily limit and allowance are not exceeded.
- * If the contract doesn't have enough payment token, the Morpho Vault withdrawal flow will be
- * triggered to withdraw the missing amount from the Morpho Vault.
- * Burns mToken from the user.
- * Transfers fee in mToken to feeReceiver.
- * Transfers tokenOut to user.
- * @param tokenOut token out address
- * @param amountMTokenIn amount of mToken to redeem
- * @param minReceiveAmount minimum expected amount of tokenOut to receive (decimals 18)
- * @param recipient address that will receive the tokenOut
- */
- function _redeemInstant(
- address tokenOut,
- uint256 amountMTokenIn,
- uint256 minReceiveAmount,
- address recipient
- )
- internal
- override
- returns (
- CalcAndValidateRedeemResult memory calcResult,
- uint256 amountTokenOutWithoutFee
- )
- {
- address user = msg.sender;
-
- calcResult = _calcAndValidateRedeem(
- user,
- tokenOut,
- amountMTokenIn,
- true,
- false
- );
-
- _requireAndUpdateLimit(amountMTokenIn);
-
- uint256 tokenDecimals = _tokenDecimals(tokenOut);
-
- uint256 amountMTokenInCopy = amountMTokenIn;
- address tokenOutCopy = tokenOut;
- uint256 minReceiveAmountCopy = minReceiveAmount;
-
- (uint256 amountMTokenInUsd, uint256 mTokenRate) = _convertMTokenToUsd(
- amountMTokenInCopy
- );
- (uint256 amountTokenOut, uint256 tokenOutRate) = _convertUsdToToken(
- amountMTokenInUsd,
- tokenOutCopy
- );
-
- _requireAndUpdateAllowance(tokenOutCopy, amountTokenOut);
-
- mToken.burn(user, calcResult.amountMTokenWithoutFee);
- if (calcResult.feeAmount > 0)
- _tokenTransferFromUser(
- address(mToken),
- feeReceiver,
- calcResult.feeAmount,
- 18
- );
-
- uint256 amountTokenOutWithoutFeeFrom18 = ((calcResult
- .amountMTokenWithoutFee * mTokenRate) / tokenOutRate)
- .convertFromBase18(tokenDecimals);
-
- amountTokenOutWithoutFee = amountTokenOutWithoutFeeFrom18
- .convertToBase18(tokenDecimals);
-
- require(
- amountTokenOutWithoutFee >= minReceiveAmountCopy,
- "RVM: minReceiveAmount > actual"
- );
-
- _checkAndRedeemMorpho(tokenOutCopy, amountTokenOutWithoutFeeFrom18);
-
- _tokenTransferToUser(
- tokenOutCopy,
- recipient,
- amountTokenOutWithoutFee,
- tokenDecimals
- );
+ emit RemoveMorphoVault(_token);
}
/**
@@ -171,27 +104,46 @@ contract RedemptionVaultWithMorpho is RedemptionVault {
* asset directly to this contract. No approval is needed because the vault
* burns shares from msg.sender (this contract) when msg.sender == owner.
* @param tokenOut tokenOut address
- * @param amountTokenOut amount of tokenOut needed
+ * @param missingAmountBase18 amount of tokenOut needed in base 18
+ * @param tokenOutDecimals decimals of tokenOut
*/
- function _checkAndRedeemMorpho(address tokenOut, uint256 amountTokenOut)
+ function _obtainVaultLiquidity(
+ address tokenOut,
+ uint256 missingAmountBase18,
+ uint256, /* tokenOutRate */
+ uint256, /* currentTokenOutBalanceBase18 */
+ uint256 tokenOutDecimals
+ )
internal
+ virtual
+ override
+ returns (
+ uint256 /* obtainedLiquidityBase18 */
+ )
{
- uint256 contractBalanceTokenOut = IERC20(tokenOut).balanceOf(
- address(this)
- );
- if (contractBalanceTokenOut >= amountTokenOut) return;
-
IMorphoVault vault = morphoVaults[tokenOut];
- require(address(vault) != address(0), "RVM: no vault for token");
- uint256 missingAmount = amountTokenOut - contractBalanceTokenOut;
+ if (address(vault) == address(0)) {
+ return 0;
+ }
- uint256 sharesNeeded = vault.previewWithdraw(missingAmount);
- require(
- vault.balanceOf(address(this)) >= sharesNeeded,
- "RVM: insufficient shares"
+ uint256 missingAmount = missingAmountBase18.convertFromBase18(
+ tokenOutDecimals
);
- vault.withdraw(missingAmount, address(this), address(this));
+ uint256 sharesNeeded = vault.previewWithdraw(missingAmount);
+ uint256 vaultSharesBalance = vault.balanceOf(address(this));
+ uint256 toRedeemShares = vaultSharesBalance >= sharesNeeded
+ ? sharesNeeded
+ : vaultSharesBalance;
+
+ if (toRedeemShares == 0) {
+ return 0;
+ }
+
+ return
+ vault
+ .redeem(toRedeemShares, address(this), address(this))
+ .convertToBase18(tokenOutDecimals);
}
}
diff --git a/contracts/RedemptionVaultWithSwapper.sol b/contracts/RedemptionVaultWithSwapper.sol
deleted file mode 100644
index b6de1bc8..00000000
--- a/contracts/RedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,265 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import {IERC20Upgradeable as IERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
-import {SafeERC20Upgradeable as SafeERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
-import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
-
-import "./RedemptionVault.sol";
-import "./interfaces/IRedemptionVault.sol";
-import "./interfaces/IRedemptionVaultWithSwapper.sol";
-import "./libraries/DecimalsCorrectionLibrary.sol";
-
-/**
- * @title RedemptionVaultWithSwapper
- * @notice Smart contract that handles mToken redemption.
- * In case of insufficient liquidity it uses a RV from a different
- * Midas product to fulfill instant redemption.
- * @dev mToken1 - is a main mToken of this vault
- * mToken2 - is a token of a second vault that is triggered when
- * current vault don`t have enough liquidity
- * @author RedDuck Software
- */
-contract RedemptionVaultWithSwapper is
- IRedemptionVaultWithSwapper,
- RedemptionVault
-{
- using DecimalsCorrectionLibrary for uint256;
- using SafeERC20 for IERC20;
-
- /**
- * @dev added second gap here to match the storage layout
- * from the previous contracts inheritance tree
- */
- uint256[50] private ___gap;
-
- /**
- * @notice mToken1 redemption vault
- * @dev The naming was not altered to maintain
- * compatibility with the currently deployed contracts.
- */
- IRedemptionVault public mTbillRedemptionVault;
-
- address public liquidityProvider;
-
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @notice upgradeable pattern contract`s initializer
- * @param _ac address of MidasAccessControll contract
- * @param _mTokenInitParams init params for mToken1
- * @param _receiversInitParams init params for receivers
- * @param _instantInitParams init params for instant operations
- * @param _sanctionsList address of sanctionsList contract
- * @param _variationTolerance percent of prices diviation 1% = 100
- * @param _minAmount basic min amount for operations
- * @param _fiatRedemptionInitParams params fiatAdditionalFee, fiatFlatFee, minFiatRedeemAmount
- * @param _requestRedeemer address is designated for standard redemptions, allowing tokens to be pulled from this address
- * @param _mTbillRedemptionVault mToken2 redemptionVault address
- * @param _liquidityProvider liquidity provider for pull mToken2
- */
- function initialize(
- address _ac,
- MTokenInitParams calldata _mTokenInitParams,
- ReceiversInitParams calldata _receiversInitParams,
- InstantInitParams calldata _instantInitParams,
- address _sanctionsList,
- uint256 _variationTolerance,
- uint256 _minAmount,
- FiatRedeptionInitParams calldata _fiatRedemptionInitParams,
- address _requestRedeemer,
- address _mTbillRedemptionVault,
- address _liquidityProvider
- ) external initializer {
- __RedemptionVault_init(
- _ac,
- _mTokenInitParams,
- _receiversInitParams,
- _instantInitParams,
- _sanctionsList,
- _variationTolerance,
- _minAmount,
- _fiatRedemptionInitParams,
- _requestRedeemer
- );
- _validateAddress(_mTbillRedemptionVault, true);
- _validateAddress(_liquidityProvider, false);
-
- mTbillRedemptionVault = IRedemptionVault(_mTbillRedemptionVault);
- liquidityProvider = _liquidityProvider;
- }
-
- /**
- * @dev redeem mToken1 to tokenOut if daily limit and allowance not exceeded
- * If contract don't have enough tokenOut, mToken1 will swap to mToken2 and redeem on mToken2 vault
- * Burns mToken1 from the user, if swap need mToken1 just tranfers to contract.
- * Transfers fee in mToken1 to feeReceiver
- * Transfers tokenOut to user.
- * @param tokenOut token out address
- * @param amountMTokenIn amount of mToken1 to redeem
- * @param minReceiveAmount minimum expected amount of tokenOut to receive (decimals 18)
- */
- function _redeemInstant(
- address tokenOut,
- uint256 amountMTokenIn,
- uint256 minReceiveAmount,
- address recipient
- )
- internal
- override
- returns (
- CalcAndValidateRedeemResult memory calcResult,
- uint256 amountTokenOutWithoutFee
- )
- {
- address user = msg.sender;
-
- calcResult = _calcAndValidateRedeem(
- user,
- tokenOut,
- amountMTokenIn,
- true,
- false
- );
-
- uint256 tokenDecimals = _tokenDecimals(tokenOut);
-
- uint256 amountMTokenInCopy = amountMTokenIn;
- address tokenOutCopy = tokenOut;
- uint256 minReceiveAmountCopy = minReceiveAmount;
-
- (uint256 amountMTokenInUsd, uint256 mTokenRate) = _convertMTokenToUsd(
- amountMTokenInCopy
- );
- (uint256 amountTokenOut, uint256 tokenOutRate) = _convertUsdToToken(
- amountMTokenInUsd,
- tokenOutCopy
- );
-
- amountTokenOutWithoutFee = _truncate(
- (calcResult.amountMTokenWithoutFee * mTokenRate) / tokenOutRate,
- tokenDecimals
- );
-
- require(
- amountTokenOutWithoutFee >= minReceiveAmountCopy,
- "RVS: minReceiveAmount > actual"
- );
-
- if (calcResult.feeAmount > 0)
- _tokenTransferFromUser(
- address(mToken),
- feeReceiver,
- calcResult.feeAmount,
- 18
- );
-
- uint256 contractTokenOutBalance = IERC20(tokenOutCopy).balanceOf(
- address(this)
- );
-
- _requireAndUpdateLimit(amountMTokenInCopy);
- _requireAndUpdateAllowance(tokenOutCopy, amountTokenOut);
-
- if (
- contractTokenOutBalance >=
- amountTokenOutWithoutFee.convertFromBase18(tokenDecimals)
- ) {
- mToken.burn(user, calcResult.amountMTokenWithoutFee);
- } else {
- uint256 mTbillAmount = _swapMToken1ToMToken2(
- calcResult.amountMTokenWithoutFee
- );
-
- IERC20(mTbillRedemptionVault.mToken()).safeIncreaseAllowance(
- address(mTbillRedemptionVault),
- mTbillAmount
- );
-
- mTbillRedemptionVault.redeemInstant(
- tokenOutCopy,
- mTbillAmount,
- minReceiveAmountCopy
- );
-
- uint256 contractTokenOutBalanceAfterRedeem = IERC20(tokenOutCopy)
- .balanceOf(address(this));
- amountTokenOutWithoutFee = (contractTokenOutBalanceAfterRedeem -
- contractTokenOutBalance).convertToBase18(tokenDecimals);
- }
-
- _tokenTransferToUser(
- tokenOutCopy,
- recipient,
- amountTokenOutWithoutFee,
- tokenDecimals
- );
- }
-
- /**
- * @inheritdoc IRedemptionVaultWithSwapper
- */
- function setLiquidityProvider(address provider) external onlyVaultAdmin {
- require(liquidityProvider != provider, "MRVS: already provider");
- _validateAddress(provider, false);
-
- liquidityProvider = provider;
-
- emit SetLiquidityProvider(msg.sender, provider);
- }
-
- /**
- * @inheritdoc IRedemptionVaultWithSwapper
- */
- function setSwapperVault(address newVault) external onlyVaultAdmin {
- require(
- newVault != address(mTbillRedemptionVault),
- "MRVS: already provider"
- );
- _validateAddress(newVault, true);
-
- mTbillRedemptionVault = IRedemptionVault(newVault);
-
- emit SetSwapperVault(msg.sender, newVault);
- }
-
- /**
- * @notice Transfers mToken1 to liquidity provider
- * Transfers mToken2 from liquidity provider to contract
- * Returns amount on mToken2 using exchange rates
- * @param mToken1Amount mToken1 token amount (decimals 18)
- */
- function _swapMToken1ToMToken2(uint256 mToken1Amount)
- internal
- returns (uint256 mTokenAmount)
- {
- _tokenTransferFromUser(
- address(mToken),
- liquidityProvider,
- mToken1Amount,
- 18
- );
-
- uint256 mTbillRate = mTbillRedemptionVault
- .mTokenDataFeed()
- .getDataInBase18();
- uint256 mTokenRate = mTokenDataFeed.getDataInBase18();
- mTokenAmount = Math.mulDiv(
- mToken1Amount,
- mTokenRate,
- mTbillRate,
- Math.Rounding.Up
- );
-
- _tokenTransferFromTo(
- address(mTbillRedemptionVault.mToken()),
- liquidityProvider,
- address(this),
- mTokenAmount,
- 18
- );
- }
-}
diff --git a/contracts/RedemptionVaultWithUSTB.sol b/contracts/RedemptionVaultWithUSTB.sol
index 16905126..34c3de13 100644
--- a/contracts/RedemptionVaultWithUSTB.sol
+++ b/contracts/RedemptionVaultWithUSTB.sol
@@ -1,13 +1,15 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import {IERC20Upgradeable as IERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import {SafeERC20Upgradeable as SafeERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
-import "./RedemptionVault.sol";
+import {RedemptionVault} from "./RedemptionVault.sol";
+import {RedemptionVaultInitParams} from "./interfaces/IRedemptionVault.sol";
+import {CommonVaultInitParams} from "./interfaces/IManageableVault.sol";
-import "./interfaces/ustb/IUSTBRedemption.sol";
-import "./libraries/DecimalsCorrectionLibrary.sol";
+import {IUSTBRedemption} from "./interfaces/ustb/IUSTBRedemption.sol";
+import {DecimalsCorrectionLibrary} from "./libraries/DecimalsCorrectionLibrary.sol";
/**
* @title RedemptionVaultWithUSTB
@@ -18,16 +20,6 @@ contract RedemptionVaultWithUSTB is RedemptionVault {
using DecimalsCorrectionLibrary for uint256;
using SafeERC20 for IERC20;
- /**
- * @custom:oz-upgrades-renamed-from minBuidlToRedeem
- */
- uint256 private __deprecatedStorageSlot1;
-
- /**
- * @custom:oz-upgrades-renamed-from minBuidlBalance
- */
- uint256 private __deprecatedStorageSlot2;
-
/**
* @notice USTB redemption contract address
* @dev Used to handle USTB redemptions when vault has insufficient USDC
@@ -39,159 +31,92 @@ contract RedemptionVaultWithUSTB is RedemptionVault {
*/
uint256[50] private __gap;
+ /**
+ * @notice Passes role identifiers to the base RedemptionVault constructor
+ * @param _contractAdminRole contract admin role identifier
+ * @param _greenlistedRole greenlisted role identifier
+ * @custom:oz-upgrades-unsafe-allow constructor
+ */
+ constructor(bytes32 _contractAdminRole, bytes32 _greenlistedRole)
+ RedemptionVault(_contractAdminRole, _greenlistedRole)
+ {}
+
/**
* @notice upgradeable pattern contract`s initializer
- * @param _ac address of MidasAccessControll contract
- * @param _mTokenInitParams init params for mToken
- * @param _receiversInitParams init params for receivers
- * @param _instantInitParams init params for instant operations
- * @param _sanctionsList address of sanctionsList contract
- * @param _variationTolerance percent of prices diviation 1% = 100
- * @param _minAmount basic min amount for operations
- * @param _fiatRedemptionInitParams params fiatAdditionalFee, fiatFlatFee, minFiatRedeemAmount
- * @param _requestRedeemer address is designated for standard redemptions, allowing tokens to be pulled from this address
+ * @param _commonVaultInitParams init params for common vault
+ * @param _redemptionInitParams init params for redemption vault state values
* @param _ustbRedemption USTB redemption contract address
*/
function initialize(
- address _ac,
- MTokenInitParams calldata _mTokenInitParams,
- ReceiversInitParams calldata _receiversInitParams,
- InstantInitParams calldata _instantInitParams,
- address _sanctionsList,
- uint256 _variationTolerance,
- uint256 _minAmount,
- FiatRedeptionInitParams calldata _fiatRedemptionInitParams,
- address _requestRedeemer,
+ CommonVaultInitParams calldata _commonVaultInitParams,
+ RedemptionVaultInitParams calldata _redemptionInitParams,
address _ustbRedemption
- ) external initializer {
- __RedemptionVault_init(
- _ac,
- _mTokenInitParams,
- _receiversInitParams,
- _instantInitParams,
- _sanctionsList,
- _variationTolerance,
- _minAmount,
- _fiatRedemptionInitParams,
- _requestRedeemer
- );
+ ) external {
+ initialize(_commonVaultInitParams, _redemptionInitParams);
_validateAddress(_ustbRedemption, false);
ustbRedemption = IUSTBRedemption(_ustbRedemption);
}
/**
- * @dev Redeem mToken to the selected payment token if daily limit and allowance are not exceeded.
- * If USDC is the payment token and the contract doesn't have enough USDC, the USTB redemption flow will be triggered for the missing amount.
- * Burns mToken from the user.
- * Transfers fee in mToken to feeReceiver.
- * Transfers tokenOut to user.
- * @param tokenOut token out address
- * @param amountMTokenIn amount of mToken to redeem
- * @param minReceiveAmount minimum expected amount of tokenOut to receive (decimals 18)
+ * @notice Check if contract has enough USDC balance for redeem
+ * if not, trigger USTB redemption flow to redeem exactly the missing amount
+ * @param tokenOut tokenOut address
+ * @param missingAmountBase18 amount of tokenOut needed in base 18
+ * @param currentTokenOutBalanceBase18 current balance of tokenOut in the vault in base 18
+ * @param tokenOutDecimals decimals of tokenOut
*/
- function _redeemInstant(
+ function _obtainVaultLiquidity(
address tokenOut,
- uint256 amountMTokenIn,
- uint256 minReceiveAmount,
- address recipient
+ uint256 missingAmountBase18,
+ uint256, /* tokenOutRate */
+ uint256 currentTokenOutBalanceBase18,
+ uint256 tokenOutDecimals
)
internal
+ virtual
override
returns (
- CalcAndValidateRedeemResult memory calcResult,
- uint256 amountTokenOutWithoutFee
+ uint256 /* obtainedLiquidityBase18 */
)
{
- address user = msg.sender;
-
- calcResult = _calcAndValidateRedeem(
- user,
- tokenOut,
- amountMTokenIn,
- true,
- false
- );
-
- _requireAndUpdateLimit(amountMTokenIn);
+ // If tokenOut is not USDC, do nothing
+ if (tokenOut != ustbRedemption.USDC()) {
+ return 0;
+ }
- uint256 tokenDecimals = _tokenDecimals(tokenOut);
-
- uint256 amountMTokenInCopy = amountMTokenIn;
- address tokenOutCopy = tokenOut;
- uint256 minReceiveAmountCopy = minReceiveAmount;
-
- (uint256 amountMTokenInUsd, uint256 mTokenRate) = _convertMTokenToUsd(
- amountMTokenInCopy
- );
- (uint256 amountTokenOut, uint256 tokenOutRate) = _convertUsdToToken(
- amountMTokenInUsd,
- tokenOutCopy
+ uint256 missingAmount = missingAmountBase18.convertFromBase18(
+ tokenOutDecimals
);
- _requireAndUpdateAllowance(tokenOutCopy, amountTokenOut);
+ IUSTBRedemption _ustbRedemption = ustbRedemption;
- mToken.burn(user, calcResult.amountMTokenWithoutFee);
- if (calcResult.feeAmount > 0)
- _tokenTransferFromUser(
- address(mToken),
- feeReceiver,
- calcResult.feeAmount,
- 18
- );
+ uint256 fee = _ustbRedemption.calculateFee(missingAmount);
- uint256 amountTokenOutWithoutFeeFrom18 = ((calcResult
- .amountMTokenWithoutFee * mTokenRate) / tokenOutRate)
- .convertFromBase18(tokenDecimals);
+ // If fee is not zero, do nothing
+ if (fee != 0) {
+ return 0;
+ }
- amountTokenOutWithoutFee = amountTokenOutWithoutFeeFrom18
- .convertToBase18(tokenDecimals);
-
- require(
- amountTokenOutWithoutFee >= minReceiveAmountCopy,
- "RVU: minReceiveAmount > actual"
- );
-
- _checkAndRedeemUSTB(tokenOutCopy, amountTokenOutWithoutFeeFrom18);
-
- _tokenTransferToUser(
- tokenOutCopy,
- recipient,
- amountTokenOutWithoutFee,
- tokenDecimals
- );
- }
-
- /**
- * @notice Check if contract has enough USDC balance for redeem
- * if not, trigger USTB redemption flow to redeem exactly the missing amount
- * @param tokenOut tokenOut address
- * @param amountTokenOut amount of tokenOut needed
- */
- function _checkAndRedeemUSTB(address tokenOut, uint256 amountTokenOut)
- internal
- {
- uint256 contractBalanceTokenOut = IERC20(tokenOut).balanceOf(
- address(this)
- );
- if (contractBalanceTokenOut >= amountTokenOut) return;
-
- require(tokenOut == ustbRedemption.USDC(), "RVU: invalid token");
-
- uint256 missingAmount = amountTokenOut - contractBalanceTokenOut;
-
- uint256 fee = ustbRedemption.calculateFee(missingAmount);
- require(fee == 0, "RVU: ustb fee not zero");
-
- (uint256 ustbToRedeem, ) = ustbRedemption.calculateUstbIn(
+ (uint256 ustbToRedeem, ) = _ustbRedemption.calculateUstbIn(
missingAmount
);
- IERC20 ustb = IERC20(ustbRedemption.SUPERSTATE_TOKEN());
+ IERC20 ustb = IERC20(_ustbRedemption.SUPERSTATE_TOKEN());
uint256 ustbBalance = ustb.balanceOf(address(this));
- require(ustbBalance >= ustbToRedeem, "RVU: insufficient USTB balance");
+ ustbToRedeem = ustbBalance >= ustbToRedeem ? ustbToRedeem : ustbBalance;
+
+ // if nothing to redeem, do nothing
+ if (ustbToRedeem == 0) {
+ return 0;
+ }
+
+ ustb.safeIncreaseAllowance(address(_ustbRedemption), ustbToRedeem);
+ _ustbRedemption.redeem(ustbToRedeem);
- ustb.safeIncreaseAllowance(address(ustbRedemption), ustbToRedeem);
- ustbRedemption.redeem(ustbToRedeem);
+ return
+ IERC20(tokenOut).balanceOf(address(this)).convertToBase18(
+ tokenOutDecimals
+ ) - currentTokenOutBalanceBase18;
}
}
diff --git a/contracts/abstract/ManageableVault.sol b/contracts/abstract/ManageableVault.sol
index 0c59123f..d7959a81 100644
--- a/contracts/abstract/ManageableVault.sol
+++ b/contracts/abstract/ManageableVault.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import {IERC20Upgradeable as IERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import {IERC20MetadataUpgradeable as IERC20Metadata} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol";
@@ -9,16 +9,21 @@ import {EnumerableSetUpgradeable as EnumerableSet} from "@openzeppelin/contracts
import {Counters} from "@openzeppelin/contracts/utils/Counters.sol";
-import "../interfaces/IManageableVault.sol";
-import "../interfaces/IMToken.sol";
-import "../interfaces/IDataFeed.sol";
+import {IManageableVault, TokenConfig, CommonVaultInitParams} from "../interfaces/IManageableVault.sol";
+import {IMToken} from "../interfaces/IMToken.sol";
+import {IDataFeed} from "../interfaces/IDataFeed.sol";
-import "../access/Greenlistable.sol";
-import "../access/Blacklistable.sol";
-import "../abstract/WithSanctionsList.sol";
+import {Greenlistable} from "../access/Greenlistable.sol";
+import {Blacklistable} from "../access/Blacklistable.sol";
+import {WithSanctionsList} from "../abstract/WithSanctionsList.sol";
-import "../libraries/DecimalsCorrectionLibrary.sol";
-import "../access/Pausable.sol";
+import {DecimalsCorrectionLibrary} from "../libraries/DecimalsCorrectionLibrary.sol";
+import {IMidasAccessControlManaged} from "../interfaces/IMidasAccessControlManaged.sol";
+import {PauseGuardsLibrary} from "../libraries/PauseGuardsLibrary.sol";
+import {WithMidasAccessControl} from "../access/WithMidasAccessControl.sol";
+
+import {RateLimitLibrary} from "../libraries/RateLimitLibrary.sol";
+import {MidasInitializable} from "./MidasInitializable.sol";
/**
* @title ManageableVault
@@ -26,96 +31,101 @@ import "../access/Pausable.sol";
* @notice Contract with base Vault methods
*/
abstract contract ManageableVault is
- Pausable,
IManageableVault,
Blacklistable,
Greenlistable,
WithSanctionsList
{
using EnumerableSet for EnumerableSet.AddressSet;
+ using EnumerableSet for EnumerableSet.UintSet;
using DecimalsCorrectionLibrary for uint256;
using SafeERC20 for IERC20;
- using Counters for Counters.Counter;
+ using RateLimitLibrary for RateLimitLibrary.WindowRateLimits;
/**
- * @notice address that represents off-chain USD bank transfer
+ * @notice stable coin static rate 1:1 USD in 18 decimals
*/
- address public constant MANUAL_FULLFILMENT_TOKEN = address(0x0);
+ uint256 public constant STABLECOIN_RATE = 10**18;
/**
- * @notice stable coin static rate 1:1 USD in 18 decimals
+ * @notice 100 percent with base 100
+ * @dev for example, 10% will be (10 * 100)%
*/
- uint256 public constant STABLECOIN_RATE = 10**18;
+ uint256 public constant ONE_HUNDRED_PERCENT = 100 * 100;
/**
- * @notice last request id
+ * @dev role that grants admin rights to the contract
+ * @custom:oz-upgrades-unsafe-allow state-variable-immutable
*/
- Counters.Counter public currentRequestId;
+ // solhint-disable-next-line var-name-mixedcase
+ bytes32 private immutable _CONTRACT_ADMIN_ROLE;
/**
- * @notice 100 percent with base 100
- * @dev for example, 10% will be (10 * 100)%
+ * @dev role that grants greenlisted status to the contract
+ * @custom:oz-upgrades-unsafe-allow state-variable-immutable
*/
- uint256 public constant ONE_HUNDRED_PERCENT = 100 * 100;
+ // solhint-disable-next-line var-name-mixedcase
+ bytes32 private immutable _GREENLISTED_ROLE;
- uint256 public constant MAX_UINT = type(uint256).max;
+ /**
+ * @notice mapping, token address to token config
+ */
+ mapping(address => TokenConfig) public tokensConfig;
/**
- * @notice mToken token
+ * @notice mapping, user address => is free frmo min amounts
*/
- IMToken public mToken;
+ mapping(address => bool) public isFreeFromMinAmount;
/**
- * @notice mToken data feed contract
+ * @notice address restriction with zero fees
*/
- IDataFeed public mTokenDataFeed;
+ mapping(address => bool) public override waivedFeeRestriction;
/**
- * @notice address to which tokens and mTokens will be sent
+ * @dev tokens that can be used as USD representation
*/
- address public tokensReceiver;
+ EnumerableSet.AddressSet internal _paymentTokens;
/**
- * @dev fee for initial operations 1% = 100
+ * @notice instant rate limits state
*/
- uint256 public instantFee;
+ RateLimitLibrary.WindowRateLimits private _instantRateLimits;
/**
- * @dev daily limit for initial operations
- * if user exceed this limit he will need
- * to create requests
+ * @notice last request id
*/
- uint256 public instantDailyLimit;
+ uint256 public currentRequestId;
/**
- * @dev mapping days (number from 1970) to limit amount
+ * @notice next expected request id to process
*/
- mapping(uint256 => uint256) public dailyLimits;
+ uint256 public nextExpectedRequestIdToProcess;
/**
- * @notice address to which fees will be sent
+ * @notice max requestId that can be approved
*/
- address public feeReceiver;
+ uint256 public maxApproveRequestId;
/**
- * @notice variation tolerance of tokenOut rates for "safe" requests approve
+ * @notice mToken token
*/
- uint256 public variationTolerance;
+ IMToken public mToken;
/**
- * @notice address restriction with zero fees
+ * @notice mToken data feed contract
*/
- mapping(address => bool) public waivedFeeRestriction;
+ IDataFeed public mTokenDataFeed;
/**
- * @dev tokens that can be used as USD representation
+ * @notice address to which tokens and mTokens will be sent
*/
- EnumerableSet.AddressSet internal _paymentTokens;
+ address public tokensReceiver;
/**
- * @notice mapping, token address to token config
+ * @notice variation tolerance of tokenOut rates for "safe" requests approve
*/
- mapping(address => TokenConfig) public tokensConfig;
+ uint256 public variationTolerance;
/**
* @notice basic min operations amount
@@ -123,9 +133,29 @@ abstract contract ManageableVault is
uint256 public minAmount;
/**
- * @notice mapping, user address => is free frmo min amounts
+ * @dev fee for initial operations 1% = 100
*/
- mapping(address => bool) public isFreeFromMinAmount;
+ uint256 public instantFee;
+
+ /**
+ * @notice minimum instant fee
+ */
+ uint256 public minInstantFee;
+
+ /**
+ * @notice maximum instant fee
+ */
+ uint256 public maxInstantFee;
+
+ /**
+ * @notice maximum instant share value in basis points (100 = 1%)
+ */
+ uint256 public maxInstantShare;
+
+ /**
+ * @notice enforce sequential request processing flag
+ */
+ bool public sequentialRequestProcessing;
/**
* @dev leaving a storage gap for futures updates
@@ -133,67 +163,64 @@ abstract contract ManageableVault is
uint256[50] private __gap;
/**
- * @dev checks that msg.sender do have a vaultRole() role
+ * @dev validate msg.sender and recipient access, validates if function is not paused
+ * @param recipient recipient address
*/
- modifier onlyVaultAdmin() {
- _onlyRole(vaultRole(), msg.sender);
+ modifier validateUserAccess(address recipient) {
+ _validateUserAccess(msg.sender, recipient);
_;
}
/**
- * @dev upgradeable pattern contract`s initializer
- * @param _ac address of MidasAccessControll contract
- * @param _mTokenInitParams init params for mToken
- * @param _receiversInitParams init params for receivers
- * @param _instantInitParams init params for instant operations
- * @param _sanctionsList address of sanctionsList contract
- * @param _variationTolerance percent of prices diviation 1% = 100
- * @param _minAmount basic min amount for operations
+ * @notice constructor
+ * @param _contractAdminRole contract admin role
+ * @param _greenlistedRole greenlisted role
+ * @custom:oz-upgrades-unsafe-allow constructor
*/
- // solhint-disable func-name-mixedcase
- function __ManageableVault_init(
- address _ac,
- MTokenInitParams calldata _mTokenInitParams,
- ReceiversInitParams calldata _receiversInitParams,
- InstantInitParams calldata _instantInitParams,
- address _sanctionsList,
- uint256 _variationTolerance,
- uint256 _minAmount
- ) internal onlyInitializing {
- _validateAddress(_mTokenInitParams.mToken, false);
- _validateAddress(_mTokenInitParams.mTokenDataFeed, false);
- _validateAddress(_receiversInitParams.tokensReceiver, true);
- _validateAddress(_receiversInitParams.feeReceiver, true);
- require(_instantInitParams.instantDailyLimit > 0, "zero limit");
- _validateFee(_variationTolerance, true);
- _validateFee(_instantInitParams.instantFee, false);
-
- mToken = IMToken(_mTokenInitParams.mToken);
- __Pausable_init(_ac);
- __Greenlistable_init_unchained();
- __Blacklistable_init_unchained();
- __WithSanctionsList_init_unchained(_sanctionsList);
-
- tokensReceiver = _receiversInitParams.tokensReceiver;
- feeReceiver = _receiversInitParams.feeReceiver;
- instantFee = _instantInitParams.instantFee;
- instantDailyLimit = _instantInitParams.instantDailyLimit;
- minAmount = _minAmount;
- variationTolerance = _variationTolerance;
- mTokenDataFeed = IDataFeed(_mTokenInitParams.mTokenDataFeed);
+ constructor(bytes32 _contractAdminRole, bytes32 _greenlistedRole)
+ MidasInitializable()
+ {
+ _CONTRACT_ADMIN_ROLE = _contractAdminRole;
+ _GREENLISTED_ROLE = _greenlistedRole;
}
/**
- * @inheritdoc IManageableVault
+ * @dev upgradeable pattern contract`s initializer
+ * @param _commonVaultInitParams init params for common vault
*/
- function withdrawToken(
- address token,
- uint256 amount,
- address withdrawTo
- ) external onlyVaultAdmin {
- IERC20(token).safeTransfer(withdrawTo, amount);
+ // solhint-disable func-name-mixedcase
+ function __ManageableVault_init(
+ CommonVaultInitParams calldata _commonVaultInitParams
+ ) internal onlyInitializing {
+ __WithMidasAccessControl_init(_commonVaultInitParams.ac);
+ __WithSanctionsList_init_unchained(
+ _commonVaultInitParams.sanctionsList
+ );
- emit WithdrawToken(msg.sender, token, withdrawTo, amount);
+ _validateAddress(_commonVaultInitParams.mToken, true);
+ _validateAddress(_commonVaultInitParams.mTokenDataFeed, true);
+ _validateAddress(_commonVaultInitParams.tokensReceiver, true);
+ _validateFee(_commonVaultInitParams.variationTolerance, true);
+ _validateFee(_commonVaultInitParams.instantFee, false);
+ _validateFee(_commonVaultInitParams.maxInstantShare, false);
+
+ mToken = IMToken(_commonVaultInitParams.mToken);
+
+ tokensReceiver = _commonVaultInitParams.tokensReceiver;
+ instantFee = _commonVaultInitParams.instantFee;
+ minAmount = _commonVaultInitParams.minAmount;
+ variationTolerance = _commonVaultInitParams.variationTolerance;
+ mTokenDataFeed = IDataFeed(_commonVaultInitParams.mTokenDataFeed);
+ sequentialRequestProcessing = _commonVaultInitParams
+ .sequentialRequestProcessing;
+
+ maxInstantShare = _commonVaultInitParams.maxInstantShare;
+ maxApproveRequestId = _commonVaultInitParams.maxApproveRequestId;
+
+ _setMinMaxInstantFee(
+ _commonVaultInitParams.minInstantFee,
+ _commonVaultInitParams.maxInstantFee
+ );
}
/**
@@ -205,8 +232,8 @@ abstract contract ManageableVault is
uint256 tokenFee,
uint256 allowance,
bool stable
- ) external onlyVaultAdmin {
- require(_paymentTokens.add(token), "MV: already added");
+ ) external onlyContractAdmin {
+ require(_paymentTokens.add(token), PaymentTokenAlreadyAdded(token));
_validateAddress(dataFeed, false);
_validateFee(tokenFee, false);
@@ -216,41 +243,30 @@ abstract contract ManageableVault is
allowance: allowance,
stable: stable
});
- emit AddPaymentToken(
- msg.sender,
- token,
- dataFeed,
- tokenFee,
- allowance,
- stable
- );
+ emit AddPaymentToken(token, dataFeed, tokenFee, allowance, stable);
}
/**
* @inheritdoc IManageableVault
* @dev reverts if token is not presented
*/
- function removePaymentToken(address token) external onlyVaultAdmin {
- require(_paymentTokens.remove(token), "MV: not exists");
+ function removePaymentToken(address token) external onlyContractAdmin {
+ require(_paymentTokens.remove(token), PaymentTokenNotExists(token));
delete tokensConfig[token];
- emit RemovePaymentToken(token, msg.sender);
+ emit RemovePaymentToken(token);
}
/**
* @inheritdoc IManageableVault
- * @dev reverts if new allowance zero
*/
function changeTokenAllowance(address token, uint256 allowance)
external
- onlyVaultAdmin
+ onlyContractAdmin
{
- if (token != MANUAL_FULLFILMENT_TOKEN) {
- _requireTokenExists(token);
- }
+ _requireTokenExists(token);
- require(allowance > 0, "MV: zero allowance");
tokensConfig[token].allowance = allowance;
- emit ChangeTokenAllowance(token, msg.sender, allowance);
+ emit ChangeTokenAllowance(token, allowance);
}
/**
@@ -259,98 +275,122 @@ abstract contract ManageableVault is
*/
function changeTokenFee(address token, uint256 fee)
external
- onlyVaultAdmin
+ onlyContractAdmin
{
_requireTokenExists(token);
_validateFee(fee, false);
tokensConfig[token].fee = fee;
- emit ChangeTokenFee(token, msg.sender, fee);
+ emit ChangeTokenFee(token, fee);
}
/**
* @inheritdoc IManageableVault
- * @dev reverts if new tolerance zero
+ * @dev reverts if new tolerance > 100%
*/
- function setVariationTolerance(uint256 tolerance) external onlyVaultAdmin {
- _validateFee(tolerance, true);
+ function setVariationTolerance(uint256 tolerance)
+ external
+ onlyContractAdmin
+ {
+ _validateFee(tolerance, false);
variationTolerance = tolerance;
- emit SetVariationTolerance(msg.sender, tolerance);
+ emit SetVariationTolerance(tolerance);
}
/**
* @inheritdoc IManageableVault
*/
- function setMinAmount(uint256 newAmount) external onlyVaultAdmin {
+ function setMinAmount(uint256 newAmount) external onlyContractAdmin {
minAmount = newAmount;
- emit SetMinAmount(msg.sender, newAmount);
+ emit SetMinAmount(newAmount);
}
/**
* @inheritdoc IManageableVault
- * @dev reverts if account is already added
*/
- function addWaivedFeeAccount(address account) external onlyVaultAdmin {
- require(!waivedFeeRestriction[account], "MV: already added");
- waivedFeeRestriction[account] = true;
- emit AddWaivedFeeAccount(account, msg.sender);
+ function setWaivedFeeAccount(address account, bool enable)
+ external
+ onlyContractAdmin
+ {
+ require(waivedFeeRestriction[account] != enable, SameBoolValue(enable));
+ waivedFeeRestriction[account] = enable;
+ emit SetWaivedFeeAccount(account, enable);
}
/**
* @inheritdoc IManageableVault
- * @dev reverts if account is already removed
+ * @dev reverts address zero or equal address(this)
*/
- function removeWaivedFeeAccount(address account) external onlyVaultAdmin {
- require(waivedFeeRestriction[account], "MV: not found");
- waivedFeeRestriction[account] = false;
- emit RemoveWaivedFeeAccount(account, msg.sender);
+ function setTokensReceiver(address receiver) external onlyContractAdmin {
+ _validateAddress(receiver, true);
+
+ tokensReceiver = receiver;
+
+ emit SetTokensReceiver(receiver);
}
/**
* @inheritdoc IManageableVault
- * @dev reverts address zero or equal address(this)
*/
- function setFeeReceiver(address receiver) external onlyVaultAdmin {
- _validateAddress(receiver, true);
-
- feeReceiver = receiver;
+ function setInstantFee(uint256 newInstantFee) external onlyContractAdmin {
+ _validateFee(newInstantFee, false);
- emit SetFeeReceiver(msg.sender, receiver);
+ instantFee = newInstantFee;
+ emit SetInstantFee(newInstantFee);
}
/**
* @inheritdoc IManageableVault
- * @dev reverts address zero or equal address(this)
*/
- function setTokensReceiver(address receiver) external onlyVaultAdmin {
- _validateAddress(receiver, true);
-
- tokensReceiver = receiver;
+ function setMinMaxInstantFee(
+ uint256 newMinInstantFee,
+ uint256 newMaxInstantFee
+ ) external onlyContractAdmin {
+ _setMinMaxInstantFee(newMinInstantFee, newMaxInstantFee);
+ }
- emit SetTokensReceiver(msg.sender, receiver);
+ /**
+ * @inheritdoc IManageableVault
+ */
+ function setMaxInstantShare(uint256 newMaxInstantShare)
+ external
+ onlyContractAdmin
+ {
+ _validateFee(newMaxInstantShare, false);
+ maxInstantShare = newMaxInstantShare;
+ emit SetMaxInstantShare(newMaxInstantShare);
}
/**
* @inheritdoc IManageableVault
*/
- function setInstantFee(uint256 newInstantFee) external onlyVaultAdmin {
- _validateFee(newInstantFee, false);
+ function setMaxApproveRequestId(uint256 newMaxApproveRequestId)
+ external
+ onlyContractAdmin
+ {
+ maxApproveRequestId = newMaxApproveRequestId;
+ emit SetMaxApproveRequestId(newMaxApproveRequestId);
+ }
- instantFee = newInstantFee;
- emit SetInstantFee(msg.sender, newInstantFee);
+ /**
+ * @inheritdoc IManageableVault
+ */
+ function setInstantLimitConfig(uint256 window, uint256 limit)
+ external
+ onlyContractAdmin
+ {
+ _instantRateLimits.setWindowLimit(window, limit);
}
/**
* @inheritdoc IManageableVault
*/
- function setInstantDailyLimit(uint256 newInstantDailyLimit)
+ function removeInstantLimitConfig(uint256 window)
external
- onlyVaultAdmin
+ onlyContractAdmin
{
- require(newInstantDailyLimit > 0, "MV: limit zero");
- instantDailyLimit = newInstantDailyLimit;
- emit SetInstantDailyLimit(msg.sender, newInstantDailyLimit);
+ _instantRateLimits.removeWindowLimit(window);
}
/**
@@ -358,15 +398,39 @@ abstract contract ManageableVault is
*/
function freeFromMinAmount(address user, bool enable)
external
- onlyVaultAdmin
+ onlyContractAdmin
{
- require(isFreeFromMinAmount[user] != enable, "DV: already free");
+ require(isFreeFromMinAmount[user] != enable, SameAddressValue(user));
isFreeFromMinAmount[user] = enable;
emit FreeFromMinAmount(user, enable);
}
+ /**
+ * @inheritdoc IManageableVault
+ */
+ function setSequentialRequestProcessing(bool enforce)
+ external
+ onlyContractAdmin
+ {
+ require(sequentialRequestProcessing != enforce, SameBoolValue(enforce));
+ sequentialRequestProcessing = enforce;
+ emit SetSequentialRequestProcessing(enforce);
+ }
+
+ /**
+ * @inheritdoc IManageableVault
+ */
+ function withdrawToken(address token, uint256 amount)
+ external
+ onlyContractAdmin
+ {
+ address withdrawTo = tokensReceiver;
+ IERC20(token).safeTransfer(withdrawTo, amount);
+ emit WithdrawToken(token, withdrawTo, amount);
+ }
+
/**
* @notice returns array of stablecoins supported by the vault
* can be called only from permissioned actor.
@@ -377,29 +441,44 @@ abstract contract ManageableVault is
}
/**
- * @notice AC role of vault administrator
- * @return role bytes32 role
+ * @notice returns array of instant rate limit statuses
+ * @return statuses array of instant rate limit statuses
*/
- function vaultRole() public view virtual returns (bytes32);
+ function getInstantLimitStatuses()
+ external
+ view
+ returns (
+ RateLimitLibrary.WindowRateLimitStatus[] memory /* statuses */
+ )
+ {
+ return _instantRateLimits.getWindowStatuses();
+ }
/**
- * @inheritdoc WithSanctionsList
+ * @inheritdoc Greenlistable
*/
- function sanctionsListAdminRole()
- public
- view
- virtual
- override
- returns (bytes32)
- {
- return vaultRole();
+ function greenlistedRole() public view virtual override returns (bytes32) {
+ return _GREENLISTED_ROLE;
}
/**
- * @inheritdoc Pausable
+ * @dev set minimum/maximum instant fee
+ * @param newMinInstantFee new minimum instant fee
+ * @param newMaxInstantFee new maximum instant fee
*/
- function pauseAdminRole() public view override returns (bytes32) {
- return vaultRole();
+ function _setMinMaxInstantFee(
+ uint256 newMinInstantFee,
+ uint256 newMaxInstantFee
+ ) private {
+ _validateFee(newMinInstantFee, false);
+ _validateFee(newMaxInstantFee, false);
+ require(
+ newMinInstantFee <= newMaxInstantFee,
+ InvalidMinMaxInstantFee(newMinInstantFee, newMaxInstantFee)
+ );
+ minInstantFee = newMinInstantFee;
+ maxInstantFee = newMaxInstantFee;
+ emit SetMinMaxInstantFee(newMinInstantFee, newMaxInstantFee);
}
/**
@@ -418,22 +497,17 @@ abstract contract ManageableVault is
uint256 amount,
uint256 tokenDecimals
) internal returns (uint256 transferAmount) {
- transferAmount = amount.convertFromBase18(tokenDecimals);
-
- require(
- amount == transferAmount.convertToBase18(tokenDecimals),
- "MV: invalid rounding"
- );
-
- IERC20(token).safeTransferFrom(msg.sender, to, transferAmount);
+ return
+ _tokenTransferFromTo(token, msg.sender, to, amount, tokenDecimals);
}
/**
- * @dev do safeTransferFrom on a given token
+ * @dev do safeTransfer or safeTransferFrom on a given token
* and converts `amount` from base18
* to amount with a correct precision.
* @param token address of token
- * @param from address
+ * @param from address. If its address(this) the safeTransfer will be used
+ * instead of safeTransferFrom
* @param to address
* @param amount amount of `token` to transfer from `user`
* @param tokenDecimals token decimals
@@ -444,41 +518,68 @@ abstract contract ManageableVault is
address to,
uint256 amount,
uint256 tokenDecimals
- ) internal {
- uint256 transferAmount = amount.convertFromBase18(tokenDecimals);
+ ) internal returns (uint256 transferAmount) {
+ if (amount == 0) return 0;
+ transferAmount = amount.convertFromBase18(tokenDecimals);
+ uint256 truncatedAmount = transferAmount.convertToBase18(tokenDecimals);
require(
- amount == transferAmount.convertToBase18(tokenDecimals),
- "MV: invalid rounding"
+ amount == truncatedAmount,
+ InvalidRounding(amount, truncatedAmount)
);
- IERC20(token).safeTransferFrom(from, to, transferAmount);
+ if (from == address(this)) {
+ IERC20(token).safeTransfer(to, transferAmount);
+ } else {
+ IERC20(token).safeTransferFrom(from, to, transferAmount);
+ }
}
/**
- * @dev do safeTransfer on a given token
- * and converts `amount` from base18
- * to amount with a correct precision. Sends tokens
- * from `contract` to `user`
- * @param token address of token
- * @param to address of user
- * @param amount amount of `token` to transfer from `user` (decimals 18)
- * @param tokenDecimals token decimals
+ * @dev check if operation exceed daily limit and update limit data
+ * @param amount operation amount (decimals 18)
*/
- function _tokenTransferToUser(
- address token,
- address to,
- uint256 amount,
- uint256 tokenDecimals
- ) internal {
- uint256 transferAmount = amount.convertFromBase18(tokenDecimals);
+ function _requireAndUpdateLimit(uint256 amount) internal {
+ _instantRateLimits.consumeLimit(amount);
+ }
+
+ /**
+ * @dev check if request id is sequential and update next expected request id to process
+ * @param requestId request id
+ * @param revertIfInvalid if true, reverts if request id is not sequential, otherwise returns false
+ * @return isValid true if request id is sequential or sequentialRequestProcessing is disabled
+ */
+ function _validateAndUpdateNextRequestIdToProcess(
+ uint256 requestId,
+ bool revertIfInvalid
+ ) internal returns (bool isValid) {
+ isValid = true;
+
+ if (!_validateMaxApproveRequestId(requestId, revertIfInvalid)) {
+ return false;
+ }
+
+ uint256 _nextExpectedRequestIdToProcess = nextExpectedRequestIdToProcess;
+
+ if (
+ sequentialRequestProcessing &&
+ requestId != _nextExpectedRequestIdToProcess
+ ) {
+ isValid = false;
+ }
+
+ if (!revertIfInvalid && !isValid) {
+ return false;
+ }
require(
- amount == transferAmount.convertToBase18(tokenDecimals),
- "MV: invalid rounding"
+ isValid,
+ InvalidRequestSequence(requestId, _nextExpectedRequestIdToProcess)
);
- IERC20(token).safeTransfer(to, transferAmount);
+ if (requestId >= _nextExpectedRequestIdToProcess) {
+ nextExpectedRequestIdToProcess = requestId + 1;
+ }
}
/**
@@ -495,20 +596,7 @@ abstract contract ManageableVault is
* @param token address of token
*/
function _requireTokenExists(address token) internal view virtual {
- require(_paymentTokens.contains(token), "MV: token not exists");
- }
-
- /**
- * @dev check if operation exceed daily limit and update limit data
- * @param amount operation amount (decimals 18)
- */
- function _requireAndUpdateLimit(uint256 amount) internal {
- uint256 currentDayNumber = block.timestamp / 1 days;
- uint256 nextLimitAmount = dailyLimits[currentDayNumber] + amount;
-
- require(nextLimitAmount <= instantDailyLimit, "MV: exceed limit");
-
- dailyLimits[currentDayNumber] = nextLimitAmount;
+ require(_paymentTokens.contains(token), UnknownPaymentToken(token));
}
/**
@@ -520,45 +608,63 @@ abstract contract ManageableVault is
internal
{
uint256 prevAllowance = tokensConfig[token].allowance;
- if (prevAllowance == MAX_UINT) return;
+ if (prevAllowance == type(uint256).max) return;
- require(prevAllowance >= amount, "MV: exceed allowance");
+ require(
+ prevAllowance >= amount,
+ AllowanceExceeded(prevAllowance, amount)
+ );
tokensConfig[token].allowance -= amount;
}
/**
- * @dev returns calculated fee amount depends on parameters
- * if additionalFee not zero, token fee replaced with additionalFee
+ * @dev returns calculated fee amount depends on the provided fee percent and amount
+ * @param feePercent fee percent
+ * @param amount amount of token (decimals 18)
+
+ * @return feeAmount calculated fee amount
+ */
+ function _getFeeAmount(uint256 feePercent, uint256 amount)
+ internal
+ pure
+ returns (uint256)
+ {
+ return (amount * feePercent) / ONE_HUNDRED_PERCENT;
+ }
+
+ /**
+ * @dev returns calculated fee percent depends on parameters
* @param sender sender address
* @param token token address
- * @param amount amount of token (decimals 18)
* @param isInstant is instant operation
- * @param additionalFee fee for fiat operations
- * @return fee amount of input token
+ *
+ * @return feePercent calculated fee percent
*/
- function _getFeeAmount(
+ function _getFee(
address sender,
address token,
- uint256 amount,
- bool isInstant,
- uint256 additionalFee
- ) internal view returns (uint256) {
+ bool isInstant
+ ) internal view returns (uint256 feePercent) {
if (waivedFeeRestriction[sender]) return 0;
- uint256 feePercent;
- if (additionalFee == 0) {
- TokenConfig storage tokenConfig = tokensConfig[token];
- feePercent = tokenConfig.fee;
- } else {
- feePercent = additionalFee;
- }
+ feePercent = tokensConfig[token].fee;
if (isInstant) feePercent += instantFee;
if (feePercent > ONE_HUNDRED_PERCENT) feePercent = ONE_HUNDRED_PERCENT;
+ }
- return (amount * feePercent) / ONE_HUNDRED_PERCENT;
+ /**
+ * @dev validates instant fee is within the range of min/max instant fee
+ */
+ function _validateInstantFee() internal view {
+ uint256 currentInstantFee = instantFee;
+ require(
+ currentInstantFee >= minInstantFee &&
+ currentInstantFee <= maxInstantFee,
+ InstantFeeOutOfBounds(currentInstantFee)
+ );
}
/**
@@ -578,17 +684,104 @@ abstract contract ManageableVault is
require(
priceDifPercent <= variationTolerance,
- "MV: exceed price diviation"
+ PriceVariationExceeded(priceDifPercent, variationTolerance)
);
}
- function _validateUserAccess(address user)
+ /**
+ * @dev validates that inputted mToken amount is >= minAmount()
+ * only if the `user` is not free from min amount
+ * @param user user address
+ * @param amountMToken amount of mToken
+ * @return isFreeFromMinAmount if the `user` is free from min amount
+ */
+ function _validateMTokenAmount(address user, uint256 amountMToken)
+ internal
+ view
+ returns (
+ bool /* isFreeFromMinAmount */
+ )
+ {
+ require(amountMToken > 0, InvalidAmount());
+
+ if (isFreeFromMinAmount[user]) {
+ return true;
+ }
+
+ require(
+ amountMToken >= minAmount,
+ AmountLessThanMin(amountMToken, minAmount)
+ );
+
+ return false;
+ }
+
+ /**
+ * @dev validate user access
+ * @param user user address
+ * @param validatePaused if true, validates if function is not paused
+ */
+ function _validateUserAccess(address user, bool validatePaused)
internal
view
onlyGreenlisted(user)
onlyNotBlacklisted(user)
onlyNotSanctioned(user)
- {}
+ {
+ require(user != address(0), InvalidAddress(user));
+ if (!validatePaused) return;
+ PauseGuardsLibrary.requireNotPaused(accessControl, msg.sig);
+ }
+
+ /**
+ * @dev validate user access and validates if function is not paused
+ * @param user user address
+ * @param recipient recipient address
+ */
+ function _validateUserAccess(address user, address recipient)
+ internal
+ view
+ {
+ _validateUserAccess(user, true);
+
+ if (recipient != user) {
+ _validateUserAccess(recipient, false);
+ }
+ }
+
+ /**
+ * @inheritdoc WithMidasAccessControl
+ */
+ function contractAdminRole()
+ public
+ view
+ virtual
+ override
+ returns (bytes32)
+ {
+ return _CONTRACT_ADMIN_ROLE;
+ }
+
+ /**
+ * @inheritdoc WithMidasAccessControl
+ */
+ function _validateFunctionAccessWithTimelock(
+ bytes32 role,
+ uint32 overrideDelay,
+ bool roleIsFunctionOperator,
+ address account,
+ bool validateFunctionRole
+ ) internal view override {
+ PauseGuardsLibrary.requireFnNotPaused(accessControl, msg.sig);
+
+ super._validateFunctionAccessWithTimelock(
+ role,
+ overrideDelay,
+ roleIsFunctionOperator,
+ account,
+ validateFunctionRole
+ );
+ }
/**
* @dev convert value to inputted decimals precision
@@ -610,8 +803,8 @@ abstract contract ManageableVault is
* @param checkMin if need to check minimum
*/
function _validateFee(uint256 fee, bool checkMin) internal pure {
- require(fee <= ONE_HUNDRED_PERCENT, "fee > 100%");
- if (checkMin) require(fee > 0, "fee == 0");
+ require(fee <= ONE_HUNDRED_PERCENT, InvalidFee(fee));
+ if (checkMin) require(fee > 0, InvalidFee(fee));
}
/**
@@ -620,8 +813,8 @@ abstract contract ManageableVault is
* @param selfCheck check if address not address(this)
*/
function _validateAddress(address addr, bool selfCheck) internal view {
- require(addr != address(0), "zero address");
- if (selfCheck) require(addr != address(this), "invalid address");
+ require(addr != address(0), InvalidAddress(addr));
+ if (selfCheck) require(addr != address(this), InvalidAddress(addr));
}
/**
@@ -642,4 +835,66 @@ abstract contract ManageableVault is
return rate;
}
+
+ /**
+ * @dev gets and validates mToken rate
+ * @return mTokenRate mToken rate
+ */
+ function _getMTokenRate() internal view returns (uint256 mTokenRate) {
+ mTokenRate = _getTokenRate(address(mTokenDataFeed), false);
+ _validateTokenRate(mTokenRate);
+ }
+
+ /**
+ * @dev gets and validates pToken rate
+ * @param token address of pToken
+ * @return tokenRate token rate
+ */
+ function _getPTokenRate(address token)
+ internal
+ view
+ returns (uint256 tokenRate)
+ {
+ TokenConfig storage tokenConfig = tokensConfig[token];
+ tokenRate = _getTokenRate(tokenConfig.dataFeed, tokenConfig.stable);
+ _validateTokenRate(tokenRate);
+ }
+
+ /**
+ * @dev validates that actual receive amount is greater than or equal to minimum receive amount
+ * @param actualReceiveAmount actual receive amount
+ * @param minReceiveAmount minimum receive amount
+ */
+ function _requireSlippageNotExceeded(
+ uint256 actualReceiveAmount,
+ uint256 minReceiveAmount
+ ) internal pure {
+ require(
+ actualReceiveAmount >= minReceiveAmount,
+ SlippageExceeded(minReceiveAmount, actualReceiveAmount)
+ );
+ }
+
+ /**
+ * @dev validates that request id is less than or equal to max approve request id
+ * @param requestId request id
+ */
+ function _validateMaxApproveRequestId(
+ uint256 requestId,
+ bool revertIfInvalid
+ ) internal view returns (bool isValid) {
+ isValid = requestId <= maxApproveRequestId;
+
+ if (revertIfInvalid) {
+ require(isValid, RequestIdTooHigh(requestId, maxApproveRequestId));
+ }
+ }
+
+ /**
+ * @dev validates token rate
+ * @param rate token rate
+ */
+ function _validateTokenRate(uint256 rate) private pure {
+ require(rate > 0, InvalidTokenRate(rate));
+ }
}
diff --git a/contracts/abstract/MidasInitializable.sol b/contracts/abstract/MidasInitializable.sol
index f7aa5bbe..f549598a 100644
--- a/contracts/abstract/MidasInitializable.sol
+++ b/contracts/abstract/MidasInitializable.sol
@@ -1,7 +1,8 @@
-// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
-import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
+import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
+import {StorageSlotUpgradeable as StorageSlot} from "@openzeppelin/contracts-upgradeable/utils/StorageSlotUpgradeable.sol";
/**
* @title MidasInitializable
@@ -11,8 +12,45 @@ import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"
* initialization of implementation contract
*/
abstract contract MidasInitializable is Initializable {
+ /**
+ * @notice error when the sender is not the proxy admin
+ */
+ error SenderNotProxyAdmin();
+
+ /**
+ * @notice error when the address is invalid
+ * @param addr address
+ */
+ error InvalidAddress(address addr);
+
+ /**
+ * @notice modifier to check if the sender is the proxy admin
+ */
+ modifier onlyProxyAdmin() {
+ _onlyProxyAdmin();
+ _;
+ }
+
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
+
+ /**
+ * @notice function to check if the sender is the proxy admin
+ */
+ function _onlyProxyAdmin() internal view virtual {
+ address admin = StorageSlot
+ .getAddressSlot(
+ 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103
+ )
+ .value;
+
+ // during proxy deployment and initialize calls admin would be zero
+ if (admin == address(0)) {
+ return;
+ }
+
+ require(msg.sender == admin, SenderNotProxyAdmin());
+ }
}
diff --git a/contracts/abstract/WithSanctionsList.sol b/contracts/abstract/WithSanctionsList.sol
index b65a62ee..ca13f24d 100644
--- a/contracts/abstract/WithSanctionsList.sol
+++ b/contracts/abstract/WithSanctionsList.sol
@@ -1,9 +1,8 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
-import "../interfaces/ISanctionsList.sol";
-import "../access/WithMidasAccessControl.sol";
-import "./MidasInitializable.sol";
+import {ISanctionsList} from "../interfaces/ISanctionsList.sol";
+import {WithMidasAccessControl} from "../access/WithMidasAccessControl.sol";
/**
* @title WithSanctionsList
@@ -23,13 +22,15 @@ abstract contract WithSanctionsList is WithMidasAccessControl {
uint256[50] private __gap;
/**
- * @param caller function caller (msg.sender)
* @param newSanctionsList new address of `sanctionsList`
*/
- event SetSanctionsList(
- address indexed caller,
- address indexed newSanctionsList
- );
+ event SetSanctionsList(address indexed newSanctionsList);
+
+ /**
+ * @notice when user is sanctioned on sanctions list contract
+ * @param user user address
+ */
+ error Sanctioned(address user);
/**
* @dev checks that a given `user` is not sanctioned
@@ -39,24 +40,12 @@ abstract contract WithSanctionsList is WithMidasAccessControl {
if (_sanctionsList != address(0)) {
require(
!ISanctionsList(_sanctionsList).isSanctioned(user),
- "WSL: sanctioned"
+ Sanctioned(user)
);
}
_;
}
- /**
- * @dev upgradeable pattern contract`s initializer
- */
- // solhint-disable func-name-mixedcase
- function __WithSanctionsList_init(
- address _accesControl,
- address _sanctionsList
- ) internal onlyInitializing {
- __WithMidasAccessControl_init(_accesControl);
- __WithSanctionsList_init_unchained(_sanctionsList);
- }
-
/**
* @dev upgradeable pattern contract`s initializer unchained
*/
@@ -70,21 +59,13 @@ abstract contract WithSanctionsList is WithMidasAccessControl {
/**
* @notice updates `sanctionsList` address.
- * can be called only from permissioned actor that have
- * `sanctionsListAdminRole()` role
* @param newSanctionsList new sanctions list address
*/
- function setSanctionsList(address newSanctionsList) external {
- _onlyRole(sanctionsListAdminRole(), msg.sender);
-
+ function setSanctionsList(address newSanctionsList)
+ external
+ onlyContractAdmin
+ {
sanctionsList = newSanctionsList;
- emit SetSanctionsList(msg.sender, newSanctionsList);
+ emit SetSanctionsList(newSanctionsList);
}
-
- /**
- * @notice AC role of sanctions list admin
- * @dev address that have this role can use `setSanctionsList`
- * @return role bytes32 role
- */
- function sanctionsListAdminRole() public view virtual returns (bytes32);
}
diff --git a/contracts/access/Blacklistable.sol b/contracts/access/Blacklistable.sol
index dd2801e9..0caaad69 100644
--- a/contracts/access/Blacklistable.sol
+++ b/contracts/access/Blacklistable.sol
@@ -1,7 +1,8 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
-import "./WithMidasAccessControl.sol";
+import {WithMidasAccessControl} from "./WithMidasAccessControl.sol";
+import {MidasAuthLibrary} from "../libraries/MidasAuthLibrary.sol";
/**
* @title Blacklistable
@@ -16,8 +17,7 @@ abstract contract Blacklistable is WithMidasAccessControl {
uint256[50] private __gap;
/**
- * @dev checks that a given `account` doesnt
- * have BLACKLISTED_ROLE
+ * @dev checks that a given `account` doesnt have blacklisted role
*/
modifier onlyNotBlacklisted(address account) {
_onlyNotBlacklisted(account);
@@ -25,31 +25,13 @@ abstract contract Blacklistable is WithMidasAccessControl {
}
/**
- * @dev upgradeable pattern contract`s initializer
- * @param _accessControl MidasAccessControl contract address
+ * @dev checks that a given `account` doesnt have blacklisted role
*/
- // solhint-disable func-name-mixedcase
- function __Blacklistable_init(address _accessControl)
- internal
- onlyInitializing
- {
- __WithMidasAccessControl_init(_accessControl);
- __Blacklistable_init_unchained();
+ function _onlyNotBlacklisted(address account) internal view {
+ MidasAuthLibrary.requireNotBlacklisted(
+ accessControl,
+ account,
+ MidasAuthLibrary.DEFAULT_BLACKLISTED_ROLE
+ );
}
-
- /**
- * @dev upgradeable pattern contract`s initializer unchained
- */
- // solhint-disable func-name-mixedcase
- function __Blacklistable_init_unchained() internal onlyInitializing {}
-
- /**
- * @dev checks that a given `account` doesnt
- * have BLACKLISTED_ROLE
- */
- function _onlyNotBlacklisted(address account)
- internal
- view
- onlyNotRole(BLACKLISTED_ROLE, account)
- {}
}
diff --git a/contracts/access/Greenlistable.sol b/contracts/access/Greenlistable.sol
index 8c314c52..99088b2c 100644
--- a/contracts/access/Greenlistable.sol
+++ b/contracts/access/Greenlistable.sol
@@ -1,7 +1,8 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
-import "./WithMidasAccessControl.sol";
+import {WithMidasAccessControl} from "./WithMidasAccessControl.sol";
+import {MidasAuthLibrary} from "../libraries/MidasAuthLibrary.sol";
/**
* @title Greenlistable
@@ -20,89 +21,39 @@ abstract contract Greenlistable is WithMidasAccessControl {
*/
uint256[50] private __gap;
- event SetGreenlistEnable(address indexed sender, bool enable);
-
/**
- * @dev checks that a given `account`
- * have `greenlistedRole()`
+ * @param enable enable
*/
- modifier onlyGreenlisted(address account) {
- if (greenlistEnabled) _onlyGreenlisted(account);
- _;
- }
+ event SetGreenlistEnable(bool enable);
/**
- * @dev checks that a given `account`
- * have `greenlistedRole()`
- * do the check even if greenlist check is off
+ * @dev checks that a given `account` has `greenlistedRole()`
*/
- modifier onlyAlwaysGreenlisted(address account) {
- _onlyGreenlisted(account);
+ modifier onlyGreenlisted(address account) {
+ if (greenlistEnabled) {
+ MidasAuthLibrary.requireGreenlisted(
+ accessControl,
+ account,
+ greenlistedRole()
+ );
+ }
_;
}
- /**
- * @dev upgradeable pattern contract`s initializer
- * @param _accessControl MidasAccessControl contract address
- */
- // solhint-disable func-name-mixedcase
- function __Greenlistable_init(address _accessControl)
- internal
- onlyInitializing
- {
- __WithMidasAccessControl_init(_accessControl);
- __Greenlistable_init_unchained();
- }
-
- /**
- * @dev upgradeable pattern contract`s initializer unchained
- */
- // solhint-disable func-name-mixedcase
- function __Greenlistable_init_unchained() internal onlyInitializing {}
-
/**
* @notice enable or disable greenlist.
* can be called only from permissioned actor.
* @param enable enable
*/
- function setGreenlistEnable(bool enable) external {
- _onlyGreenlistToggler(msg.sender);
- require(greenlistEnabled != enable, "GL: same enable status");
+ function setGreenlistEnable(bool enable) external onlyContractAdmin {
+ require(greenlistEnabled != enable, SameBoolValue(enable));
greenlistEnabled = enable;
- emit SetGreenlistEnable(msg.sender, enable);
+ emit SetGreenlistEnable(enable);
}
/**
* @notice AC role of a greenlist
* @return role bytes32 role
*/
- function greenlistedRole() public view virtual returns (bytes32) {
- return GREENLISTED_ROLE;
- }
-
- /**
- * @notice AC role of a greenlist toggler
- * @return role bytes32 role
- */
- function greenlistTogglerRole() public view virtual returns (bytes32);
-
- /**
- * @dev checks that a given `account`
- * have a `greenlistedRole()`
- */
- function _onlyGreenlisted(address account)
- private
- view
- onlyRole(greenlistedRole(), account)
- {}
-
- /**
- * @dev checks that a given `account`
- * have a `greenlistTogglerRole()`
- */
- function _onlyGreenlistToggler(address account)
- internal
- view
- onlyRole(greenlistTogglerRole(), account)
- {}
+ function greenlistedRole() public view virtual returns (bytes32);
}
diff --git a/contracts/access/MidasAccessControl.sol b/contracts/access/MidasAccessControl.sol
index 3c9e9e47..23b3e269 100644
--- a/contracts/access/MidasAccessControl.sol
+++ b/contracts/access/MidasAccessControl.sol
@@ -1,10 +1,14 @@
-// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
-import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
+import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
+import {IAccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/IAccessControlUpgradeable.sol";
-import "./MidasAccessControlRoles.sol";
-import "../abstract/MidasInitializable.sol";
+import {MidasInitializable} from "../abstract/MidasInitializable.sol";
+import {IMidasAccessControl} from "../interfaces/IMidasAccessControl.sol";
+import {MidasAuthLibrary} from "../libraries/MidasAuthLibrary.sol";
+import {IMidasAccessControlManaged} from "../interfaces/IMidasAccessControlManaged.sol";
+import {IMidasTimelockManager} from "../interfaces/IMidasTimelockManager.sol";
/**
* @title MidasAccessControl
@@ -12,56 +16,577 @@ import "../abstract/MidasInitializable.sol";
* @author RedDuck Software
*/
contract MidasAccessControl is
+ IMidasAccessControl,
+ IMidasAccessControlManaged,
AccessControlUpgradeable,
- MidasInitializable,
- MidasAccessControlRoles
+ MidasInitializable
{
+ /**
+ * @notice actor that can change green list statuses of addresses
+ */
+ bytes32 public constant GREENLIST_OPERATOR_ROLE =
+ keccak256("GREENLIST_OPERATOR_ROLE");
+
+ /**
+ * @notice actor that can change black list statuses of addresses
+ */
+ bytes32 public constant BLACKLIST_OPERATOR_ROLE =
+ keccak256("BLACKLIST_OPERATOR_ROLE");
+
+ /**
+ * @notice roles that are held by users
+ */
+ mapping(bytes32 => bool) public isUserFacingRole;
+
+ /**
+ * @dev Grant operators may call `setPermissionRoleMult` for the corresponding permission key.
+ */
+ mapping(bytes32 => mapping(address => bool)) private _grantOperatorRoles;
+
+ /**
+ * @dev Accounts allowed to call the scoped function on `targetContract`.
+ */
+ mapping(bytes32 => mapping(address => bool)) private _permissionRoles;
+
+ /**
+ * @dev timelock delay for each role
+ */
+ mapping(bytes32 => uint32) private _roleTimelockDelays;
+
+ /**
+ * @notice address of MidasAccessControlTimelockController contract
+ */
+ address public timelockManager;
+
+ /**
+ * @notice address of MidasAccessControlTimelockController contract
+ */
+ address public pauseManager;
+
+ /**
+ * @notice default delay for all of the roles
+ */
+ uint32 public defaultDelay;
+
+ /**
+ * @dev leaving a storage gap for futures updates
+ */
+ uint256[50] private __gap;
+
+ /**
+ * @dev validates that the msg.sender has the role
+ * @param role role to check access for
+ */
+ modifier onlyRoleWithTimelock(bytes32 role) {
+ _validateRoleAccess(role);
+ _;
+ }
+
+ /**
+ * @dev validates that the caller has the function role with timelock
+ * @param role base role to validate
+ * @param overrideDelay override delay for the invocation
+ */
+ modifier onlyRoleDelayOverride(bytes32 role, uint32 overrideDelay) {
+ _validateRoleAccess(role, overrideDelay);
+ _;
+ }
+
/**
* @notice upgradeable pattern contract`s initializer
+ * @param _defaultDelay default delay
+ * @param _userFacingRoles array of additional user facing roles
*/
- function initialize() external initializer {
+ function initialize(
+ uint32 _defaultDelay,
+ bytes32[] calldata _userFacingRoles
+ ) external {
+ _initializeV1();
+ initializeV2(_defaultDelay, _userFacingRoles);
+ }
+
+ /**
+ * @notice upgradeable pattern contract`s initializer
+ */
+ function _initializeV1() private initializer {
__AccessControl_init();
- _setupRoles(msg.sender);
+ _setupRoles(_msgSender());
+ }
+
+ /**
+ * @notice initializerV2. Initializes user facing roles
+ * @param _userFacingRoles array of additional user facing roles
+ */
+ function initializeV2(
+ uint32 _defaultDelay,
+ bytes32[] calldata _userFacingRoles
+ ) public reinitializer(2) onlyProxyAdmin {
+ _validateDelay(_defaultDelay);
+
+ defaultDelay = _defaultDelay;
+
+ isUserFacingRole[MidasAuthLibrary.DEFAULT_BLACKLISTED_ROLE] = true;
+ isUserFacingRole[MidasAuthLibrary.DEFAULT_GREENLISTED_ROLE] = true;
+
+ for (uint256 i = 0; i < _userFacingRoles.length; ++i) {
+ isUserFacingRole[_userFacingRoles[i]] = true;
+ }
+ }
+
+ /**
+ * @notice initializes timelock manager. Moved to a searate initializer
+ * as its 2-way dependency between the contracts.
+ * @dev can be called only by DEFAULT_ADMIN_ROLE
+ * @param _timelockManager address of the timelock manager
+ * @param _pauseManager address of the pause manager
+ */
+ function initializeRelationships(
+ address _timelockManager,
+ address _pauseManager
+ ) external {
+ _checkRole(DEFAULT_ADMIN_ROLE, _msgSender());
+
+ require(timelockManager == address(0), InvalidAddress(timelockManager));
+ require(pauseManager == address(0), InvalidAddress(pauseManager));
+
+ require(
+ _timelockManager != address(0),
+ InvalidAddress(_timelockManager)
+ );
+ require(_pauseManager != address(0), InvalidAddress(_pauseManager));
+
+ timelockManager = _timelockManager;
+ pauseManager = _pauseManager;
}
/**
- * @notice grant multiple roles to multiple users
- * in one transaction
- * @dev length`s of 2 arays should match
- * @param roles array of bytes32 roles
- * @param addresses array of user addresses
+ * @inheritdoc IMidasAccessControl
*/
- function grantRoleMult(bytes32[] memory roles, address[] memory addresses)
+ function setDefaultDelay(uint32 _defaultDelay)
external
+ onlyRoleDelayOverride(DEFAULT_ADMIN_ROLE, 2 days)
{
- require(roles.length == addresses.length, "MAC: mismatch arrays");
+ _validateDelay(_defaultDelay);
+ defaultDelay = _defaultDelay;
+ emit SetDefaultDelay(_defaultDelay);
+ }
+
+ /**
+ * @inheritdoc IMidasAccessControl
+ */
+ function setRoleDelayMult(SetRoleDelayParams[] calldata params)
+ external
+ onlyRoleWithTimelock(DEFAULT_ADMIN_ROLE)
+ {
+ require(params.length > 0, EmptyArray());
+
+ for (uint256 i = 0; i < params.length; ++i) {
+ _validateDelay(params[i].delay);
+ _roleTimelockDelays[params[i].role] = params[i].delay;
+ }
+
+ emit SetRoleDelays(params);
+ }
+
+ /**
+ * @inheritdoc IMidasAccessControl
+ */
+ function setUserFacingRoleMult(SetUserFacingRoleParams[] calldata params)
+ external
+ onlyRoleWithTimelock(DEFAULT_ADMIN_ROLE)
+ {
+ require(params.length > 0, EmptyArray());
+
+ for (uint256 i = 0; i < params.length; ++i) {
+ SetUserFacingRoleParams calldata param = params[i];
+
+ // if value already set, skip and do not emit event
+ if (isUserFacingRole[param.role] == param.enabled) {
+ continue;
+ }
+
+ isUserFacingRole[param.role] = param.enabled;
+ emit SetUserFacingRole(param.role, param.enabled);
+ }
+ }
+
+ /**
+ * @inheritdoc IMidasAccessControl
+ */
+ function setGrantOperatorRoleMult(
+ address targetContract,
+ SetGrantOperatorRoleParams[] calldata params
+ ) external {
+ require(params.length > 0, EmptyArray());
+
+ bytes32 masterRole = _getContractAdminRole(targetContract);
+ _validateRoleAccess(masterRole);
+
+ MidasAuthLibrary.requireNotUserFacingRole(this, masterRole);
+
+ for (uint256 i = 0; i < params.length; ++i) {
+ SetGrantOperatorRoleParams calldata param = params[i];
+
+ bytes32 operatorKey = grantOperatorRoleKey(
+ masterRole,
+ targetContract,
+ param.functionSelector
+ );
+
+ _validateAndUpdateDelay(operatorKey, param.delay);
+
+ // if value already set, skip and do not emit event
+ if (
+ _grantOperatorRoles[operatorKey][param.operator] ==
+ param.enabled
+ ) {
+ continue;
+ }
+
+ _grantOperatorRoles[operatorKey][param.operator] = param.enabled;
+ emit SetGrantOperatorRole(
+ masterRole,
+ targetContract,
+ param.operator,
+ param.functionSelector,
+ param.enabled
+ );
+ }
+ }
+
+ /**
+ * @inheritdoc IMidasAccessControl
+ */
+ function setPermissionRoleMult(
+ bytes32 masterRole,
+ address targetContract,
+ SetPermissionRoleParams[] calldata params
+ ) public {
+ require(params.length > 0, EmptyArray());
+
+ bytes32 operatorRoleKey = grantOperatorRoleKey(
+ masterRole,
+ targetContract,
+ params[0].functionSelector
+ );
+
+ bool isOperatorRoleUsed = _validateOperatorRoleAccess(
+ masterRole,
+ operatorRoleKey,
+ _msgSender()
+ );
+
+ for (uint256 i = 0; i < params.length; ++i) {
+ SetPermissionRoleParams calldata param = params[i];
+
+ // if master role is used, then selectors could be different
+ // if operator role is used, then selectors must be the same as different
+ // selectors require different operator role hence different delays
+ if (isOperatorRoleUsed && i > 0) {
+ require(
+ param.functionSelector == params[0].functionSelector,
+ FunctionSelectorMismatch(
+ param.functionSelector,
+ params[0].functionSelector
+ )
+ );
+ }
+
+ bytes32 functionRoleKey = permissionRoleKey(
+ masterRole,
+ targetContract,
+ param.functionSelector
+ );
+
+ _validateAndUpdateDelay(functionRoleKey, param.delay);
+
+ // if value already set, skip and do not emit event
+ if (
+ _permissionRoles[functionRoleKey][param.account] ==
+ param.enabled
+ ) {
+ continue;
+ }
+
+ _permissionRoles[functionRoleKey][param.account] = param.enabled;
+ emit SetPermissionRole(
+ masterRole,
+ targetContract,
+ param.account,
+ param.functionSelector,
+ param.enabled
+ );
+ }
+ }
+
+ /**
+ * @inheritdoc IMidasAccessControl
+ */
+ function setPermissionRoleMult(
+ address targetContract,
+ SetPermissionRoleParams[] calldata params
+ ) external {
+ bytes32 masterRole = _getContractAdminRole(targetContract);
+ setPermissionRoleMult(masterRole, targetContract, params);
+ }
+
+ /**
+ * @inheritdoc IMidasAccessControl
+ */
+ function grantRoleMult(GrantRoleMultParams[] calldata params) external {
+ require(params.length > 0, EmptyArray());
+
+ bytes32 adminRole = getRoleAdmin(params[0].role);
+ _validateRoleAccess(adminRole);
+
+ for (uint256 i = 0; i < params.length; ++i) {
+ GrantRoleMultParams calldata param = params[i];
+
+ require(
+ getRoleAdmin(param.role) == adminRole,
+ RoleAdminMismatch(param.role, adminRole)
+ );
+ _grantRole(param.role, param.account);
+ _validateAndUpdateDelay(param.role, param.delay);
+ }
+ }
- for (uint256 i = 0; i < roles.length; i++) {
- _checkRole(getRoleAdmin(roles[i]), msg.sender);
- _grantRole(roles[i], addresses[i]);
+ /**
+ * @inheritdoc IMidasAccessControl
+ */
+ function revokeRoleMult(RevokeRoleMultParams[] calldata params) external {
+ require(params.length > 0, EmptyArray());
+
+ bytes32 adminRole = getRoleAdmin(params[0].role);
+ address actualSender = _validateRoleAccess(adminRole);
+
+ for (uint256 i = 0; i < params.length; ++i) {
+ RevokeRoleMultParams calldata param = params[i];
+
+ require(
+ getRoleAdmin(param.role) == adminRole,
+ RoleAdminMismatch(param.role, adminRole)
+ );
+ _validateRevokeRole(param.role, param.account, actualSender);
+ _revokeRole(param.role, param.account);
}
}
/**
- * @notice revoke multiple roles from multiple users
- * in one transaction
- * @dev length`s of 2 arays should match
- * @param roles array of bytes32 roles
- * @param addresses array of user addresses
+ * @inheritdoc AccessControlUpgradeable
+ */
+ function grantRole(bytes32 role, address account)
+ public
+ override(AccessControlUpgradeable, IAccessControlUpgradeable)
+ onlyRoleWithTimelock(getRoleAdmin(role))
+ {
+ _grantRole(role, account);
+ }
+
+ /**
+ * @inheritdoc IMidasAccessControl
+ */
+ function grantRole(
+ bytes32 role,
+ address account,
+ uint32 delay
+ ) public {
+ grantRole(role, account);
+ _validateAndUpdateDelay(role, delay);
+ }
+
+ /**
+ * @inheritdoc AccessControlUpgradeable
+ */
+ function revokeRole(bytes32 role, address account)
+ public
+ override(AccessControlUpgradeable, IAccessControlUpgradeable)
+ {
+ address actualSender = _validateRoleAccess(getRoleAdmin(role));
+
+ _validateRevokeRole(role, account, actualSender);
+ _revokeRole(role, account);
+ }
+
+ /**
+ * @inheritdoc IMidasAccessControl
+ */
+ function setRoleAdmin(bytes32 role, bytes32 newAdminRole)
+ external
+ onlyRoleWithTimelock(getRoleAdmin(role))
+ {
+ _setRoleAdmin(role, newAdminRole);
+ }
+
+ // solhint-disable-next-line
+ /**
+ * @notice renouce role is forbidden
+ */
+ function renounceRole(bytes32, address)
+ public
+ pure
+ override(AccessControlUpgradeable, IAccessControlUpgradeable)
+ {
+ revert Forbidden();
+ }
+
+ /**
+ * @inheritdoc IMidasAccessControl
+ */
+ function isFunctionAccessGrantOperator(
+ bytes32 masterRole,
+ address targetContract,
+ bytes4 functionSelector,
+ address operator
+ ) external view returns (bool) {
+ bytes32 key = grantOperatorRoleKey(
+ masterRole,
+ targetContract,
+ functionSelector
+ );
+ return isFunctionAccessGrantOperator(key, operator);
+ }
+
+ /**
+ * @inheritdoc IMidasAccessControl
+ */
+ function isFunctionAccessGrantOperator(bytes32 key, address operator)
+ public
+ view
+ returns (bool)
+ {
+ return _grantOperatorRoles[key][operator];
+ }
+
+ /**
+ * @inheritdoc IMidasAccessControl
+ */
+ function hasFunctionPermission(
+ bytes32 masterRole,
+ address targetContract,
+ bytes4 functionSelector,
+ address account
+ ) external view returns (bool) {
+ bytes32 key = permissionRoleKey(
+ masterRole,
+ targetContract,
+ functionSelector
+ );
+ return _permissionRoles[key][account];
+ }
+
+ /**
+ * @inheritdoc IMidasAccessControl
*/
- function revokeRoleMult(bytes32[] memory roles, address[] memory addresses)
+ function hasFunctionPermission(bytes32 key, address account)
external
+ view
+ returns (bool)
{
- require(roles.length == addresses.length, "MAC: mismatch arrays");
- for (uint256 i = 0; i < roles.length; i++) {
- _checkRole(getRoleAdmin(roles[i]), msg.sender);
- _revokeRole(roles[i], addresses[i]);
+ return _permissionRoles[key][account];
+ }
+
+ /**
+ * @inheritdoc IMidasAccessControl
+ */
+ function permissionRoleKey(
+ bytes32 masterRole,
+ address targetContract,
+ bytes4 functionSelector
+ ) public pure returns (bytes32) {
+ return
+ _functionPermissionKey(
+ masterRole,
+ targetContract,
+ functionSelector,
+ ""
+ );
+ }
+
+ /**
+ * @inheritdoc IMidasAccessControl
+ */
+ function grantOperatorRoleKey(
+ bytes32 masterRole,
+ address targetContract,
+ bytes4 functionSelector
+ ) public pure returns (bytes32) {
+ return
+ _functionPermissionKey(
+ masterRole,
+ targetContract,
+ functionSelector,
+ "operator"
+ );
+ }
+
+ /**
+ * @inheritdoc IMidasAccessControl
+ */
+ function getRoleTimelockDelay(bytes32 role, uint32 overrideDelay)
+ public
+ view
+ returns (
+ uint32, /* delay */
+ bool /* isDefault */
+ )
+ {
+ uint32 delay = overrideDelay != MidasAuthLibrary.NULL_DELAY
+ ? overrideDelay
+ : _roleTimelockDelays[role];
+
+ uint32 actualDelay = delay == MidasAuthLibrary.NULL_DELAY
+ ? defaultDelay
+ : delay == MidasAuthLibrary.NO_DELAY
+ ? 0
+ : delay;
+
+ return (actualDelay, delay == 0);
+ }
+
+ /**
+ * @inheritdoc IMidasAccessControlManaged
+ */
+ function contractAdminRole() public view override returns (bytes32) {
+ return DEFAULT_ADMIN_ROLE;
+ }
+
+ /**
+ * @dev validates and sets the delay for a role during the role granting
+ * @param role role id
+ */
+ function _validateAndUpdateDelay(bytes32 role, uint32 delay) private {
+ if (delay == MidasAuthLibrary.NULL_DELAY) {
+ return;
}
+
+ _validateDelay(delay);
+
+ require(_roleTimelockDelays[role] == 0, DelayIsAlreadySet());
+
+ _roleTimelockDelays[role] = delay;
+ emit SetRoleDelay(role, delay);
}
- //solhint-disable disable-next-line
- function renounceRole(bytes32, address) public pure override {
- revert("MAC: Forbidden");
+ /**
+ * @dev calculates the base key for function permission mappings
+ * @param masterRole OZ role for the scope
+ */
+ function _functionPermissionKey(
+ bytes32 masterRole,
+ address targetContract,
+ bytes4 functionSelector,
+ bytes memory additionalData
+ ) private pure returns (bytes32) {
+ return
+ keccak256(
+ abi.encode(
+ masterRole,
+ targetContract,
+ functionSelector,
+ additionalData
+ )
+ );
}
/**
@@ -70,7 +595,172 @@ contract MidasAccessControl is
function _setupRoles(address admin) private {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
- _setRoleAdmin(BLACKLISTED_ROLE, BLACKLIST_OPERATOR_ROLE);
- _setRoleAdmin(GREENLISTED_ROLE, GREENLIST_OPERATOR_ROLE);
+ _setRoleAdmin(
+ MidasAuthLibrary.DEFAULT_BLACKLISTED_ROLE,
+ BLACKLIST_OPERATOR_ROLE
+ );
+ _setRoleAdmin(
+ MidasAuthLibrary.DEFAULT_GREENLISTED_ROLE,
+ GREENLIST_OPERATOR_ROLE
+ );
+ }
+
+ /**
+ * @dev validates that the delay is within the maximum delay
+ * @param delay delay to validate
+ */
+ function _validateDelay(uint32 delay) private view {
+ MidasAuthLibrary.validateTimelockDelay(delay);
+ }
+
+ /**
+ * @notice verifies that the role can be revoked
+ * @param role role to be revoked
+ * @param account account to be revoked
+ * @param actualSender account that actually verified for the function access
+ */
+ function _validateRevokeRole(
+ bytes32 role,
+ address account,
+ address actualSender
+ ) private {
+ if (role == DEFAULT_ADMIN_ROLE && account == actualSender) {
+ revert CannotRevokeFromSelf(role, actualSender);
+ }
+ }
+
+ /**
+ * @notice validates that the msg.sender with a role has access to the function
+ * @param role role to check access for
+ * @param overrideDelay override delay for the invocation
+ * @return actualAccount actual account that has access to the function
+ */
+ function _validateRoleAccess(bytes32 role, uint32 overrideDelay)
+ internal
+ view
+ returns (
+ address /* actualAccount */
+ )
+ {
+ return
+ MidasAuthLibrary.validateFunctionAccessWithTimelock(
+ this,
+ role,
+ overrideDelay,
+ false,
+ _msgSender(),
+ false
+ );
+ }
+
+ /**
+ * @notice validates that the msg.sender with a role has access to the function
+ * @param role role to check access for
+ * @return actualAccount actual account that has access to the function
+ */
+ function _validateRoleAccess(bytes32 role)
+ internal
+ view
+ returns (
+ address /* actualAccount */
+ )
+ {
+ return
+ MidasAuthLibrary.validateFunctionAccessWithTimelock(
+ this,
+ role,
+ MidasAuthLibrary.NULL_DELAY,
+ false,
+ _msgSender(),
+ false
+ );
+ }
+
+ /**
+ * @dev validates that the account with a master or operator role has access to the function
+ * selects a role with a shortest delay in case if has both roles
+ * @param masterRole master role
+ * @param operatorRole operator role
+ * @param account account to check access for
+ * @return isOperatorRole whether the account has the operator role
+ */
+ function _validateOperatorRoleAccess(
+ bytes32 masterRole,
+ bytes32 operatorRole,
+ address account
+ ) internal view returns (bool isOperatorRole) {
+ bytes32 role = _resolveOperatorRole(masterRole, operatorRole, account);
+ isOperatorRole = role == operatorRole;
+
+ MidasAuthLibrary.validateFunctionAccessWithTimelock(
+ this,
+ role,
+ MidasAuthLibrary.NULL_DELAY,
+ isOperatorRole,
+ account,
+ false
+ );
+ }
+
+ /**
+ * @dev validates that the account has either operator or master role and uses the role with a shortest delay
+ * @param masterRole master role
+ * @param operatorRole operator role
+ * @param account account to check access for
+ */
+ function _resolveOperatorRole(
+ bytes32 masterRole,
+ bytes32 operatorRole,
+ address account
+ ) internal view returns (bytes32) {
+ IMidasTimelockManager _timelockManager = IMidasTimelockManager(
+ timelockManager
+ );
+
+ // means that its a preflight call
+ if (account == address(_timelockManager)) {
+ account = MidasAuthLibrary.resolveProposer(msg.data);
+ } else if (account == _timelockManager.timelock()) {
+ account = _timelockManager.getOriginalProposer(
+ address(this),
+ msg.data
+ );
+ }
+
+ bool isOperator = isFunctionAccessGrantOperator(operatorRole, account);
+ bool hasMasterRole = hasRole(masterRole, account);
+
+ if (!isOperator && !hasMasterRole) {
+ return operatorRole;
+ }
+
+ if (!hasMasterRole) {
+ return operatorRole;
+ }
+
+ if (!isOperator) {
+ return masterRole;
+ }
+
+ return
+ MidasAuthLibrary.resolveAccessRole(
+ this,
+ masterRole,
+ operatorRole,
+ MidasAuthLibrary.NULL_DELAY
+ );
+ }
+
+ /**
+ * @notice gets the contract admin role for the target contract
+ * @param targetContract address of the target contract
+ * @return contractAdminRole contract admin role
+ */
+ function _getContractAdminRole(address targetContract)
+ private
+ view
+ returns (bytes32)
+ {
+ return IMidasAccessControlManaged(targetContract).contractAdminRole();
}
}
diff --git a/contracts/access/MidasAccessControlRoles.sol b/contracts/access/MidasAccessControlRoles.sol
deleted file mode 100644
index 295eb544..00000000
--- a/contracts/access/MidasAccessControlRoles.sol
+++ /dev/null
@@ -1,31 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.9;
-
-/**
- * @title MidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors
- * @author RedDuck Software
- */
-abstract contract MidasAccessControlRoles {
- /**
- * @notice actor that can change green list statuses of addresses
- */
- bytes32 public constant GREENLIST_OPERATOR_ROLE =
- keccak256("GREENLIST_OPERATOR_ROLE");
-
- /**
- * @notice actor that can change black list statuses of addresses
- */
- bytes32 public constant BLACKLIST_OPERATOR_ROLE =
- keccak256("BLACKLIST_OPERATOR_ROLE");
-
- /**
- * @notice actor that is greenlisted
- */
- bytes32 public constant GREENLISTED_ROLE = keccak256("GREENLISTED_ROLE");
-
- /**
- * @notice actor that is blacklisted
- */
- bytes32 public constant BLACKLISTED_ROLE = keccak256("BLACKLISTED_ROLE");
-}
diff --git a/contracts/access/MidasAccessControlTimelockController.sol b/contracts/access/MidasAccessControlTimelockController.sol
new file mode 100644
index 00000000..98fe0299
--- /dev/null
+++ b/contracts/access/MidasAccessControlTimelockController.sol
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
+
+import {TimelockControllerUpgradeable} from "@openzeppelin/contracts-upgradeable/governance/TimelockControllerUpgradeable.sol";
+import {MidasInitializable} from "../abstract/MidasInitializable.sol";
+
+/**
+ * @title MidasAccessControlTimelockController
+ * @notice TimelockController for Midas Protocol that is controlled by MidasTimelockManager
+ * @author RedDuck Software
+ */
+contract MidasAccessControlTimelockController is
+ TimelockControllerUpgradeable,
+ MidasInitializable
+{
+ /**
+ * @notice address of MidasTimelockManager contract
+ */
+ address public timelockManager;
+
+ /**
+ * @dev leaving a storage gap for futures updates
+ */
+ uint256[50] private __gap;
+
+ /**
+ * @notice upgradeable pattern contract`s initializer
+ * @param _timelockManager address of MidasTimelockManager contract
+ */
+ function initialize(address _timelockManager) external initializer {
+ address[] memory managerArray = new address[](1);
+ managerArray[0] = _timelockManager;
+
+ __TimelockController_init(0, managerArray, managerArray, address(0));
+
+ timelockManager = _timelockManager;
+ }
+}
diff --git a/contracts/access/MidasPauseManager.sol b/contracts/access/MidasPauseManager.sol
new file mode 100644
index 00000000..3a019148
--- /dev/null
+++ b/contracts/access/MidasPauseManager.sol
@@ -0,0 +1,355 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
+
+import {WithMidasAccessControl} from "./WithMidasAccessControl.sol";
+import {IMidasAccessControlManaged} from "../interfaces/IMidasAccessControlManaged.sol";
+import {IMidasPauseManager} from "../interfaces/IMidasPauseManager.sol";
+import {MidasAuthLibrary} from "../libraries/MidasAuthLibrary.sol";
+import {IMidasAccessControl} from "../interfaces/IMidasAccessControl.sol";
+
+/**
+ * @title MidasPauseManager
+ * @notice Global manager for pausing and unpausing functions
+ * @author RedDuck Software
+ */
+contract MidasPauseManager is WithMidasAccessControl, IMidasPauseManager {
+ using MidasAuthLibrary for IMidasAccessControl;
+
+ /**
+ * @notice static delay for setting pause delay
+ */
+ uint32 public constant DELAY_FOR_SET_DELAY = 2 days;
+
+ /**
+ * @dev admin role for the pause manager
+ */
+ bytes32 private constant _PAUSE_ADMIN_ROLE = keccak256("PAUSE_ADMIN_ROLE");
+
+ /**
+ * @notice contract => paused status
+ */
+ mapping(address => bool) public contractPaused;
+
+ /**
+ * @notice contract => function id => paused status
+ */
+ mapping(address => mapping(bytes4 => bool)) public contractFnPaused;
+
+ /**
+ * @notice pause delay
+ */
+ uint32 public pauseDelay;
+
+ /**
+ * @notice unpause delay
+ */
+ uint32 public unpauseDelay;
+
+ /**
+ * @notice global paused status
+ */
+ bool public globalPaused;
+
+ /**
+ * @dev validates that caller has access to the `contractAddr` contract admin role
+ * overrides delay for the invocation with pause delay
+ * @param contractAddr address of the contract
+ */
+ modifier onlyPausableContractAdminPause(address contractAddr) {
+ _validateContractAdminAccess(contractAddr, pauseDelay);
+ _;
+ }
+
+ /**
+ * @dev validates that caller has access to the `contractAddr` contract admin role
+ * overrides delay for the invocation with unpause delay
+ * @param contractAddr address of the contract
+ */
+ modifier onlyPausableContractAdminUnpause(address contractAddr) {
+ _validateContractAdminAccess(contractAddr, unpauseDelay);
+ _;
+ }
+
+ /**
+ * @dev validates that caller has access to the pause admin role
+ * overrides delay for the invocation with pause delay
+ */
+ modifier onlyAdminPause() {
+ _validateFunctionAccessWithTimelock(
+ contractAdminRole(),
+ pauseDelay,
+ false,
+ msg.sender,
+ true
+ );
+ _;
+ }
+
+ /**
+ * @dev validates that caller has access to the unpause admin role
+ * overrides delay for the invocation with unpause delay
+ */
+ modifier onlyAdminUnpause() {
+ _validateFunctionAccessWithTimelock(
+ contractAdminRole(),
+ unpauseDelay,
+ false,
+ msg.sender,
+ true
+ );
+ _;
+ }
+
+ /**
+ * @notice upgradeable pattern contract`s initializer
+ * @param _accessControl address of MidasAccessControl contract
+ * @param _pauseDelay pause delay
+ * @param _unpauseDelay unpause delay
+ */
+ function initialize(
+ address _accessControl,
+ uint32 _pauseDelay,
+ uint32 _unpauseDelay
+ ) external initializer {
+ __WithMidasAccessControl_init(_accessControl);
+
+ _validateDelay(_pauseDelay);
+ _validateDelay(_unpauseDelay);
+
+ pauseDelay = _pauseDelay;
+ unpauseDelay = _unpauseDelay;
+ }
+
+ /**
+ * @inheritdoc IMidasPauseManager
+ */
+ function setPauseDelay(uint32 _pauseDelay)
+ external
+ onlyRoleDelayOverride(contractAdminRole(), DELAY_FOR_SET_DELAY, true)
+ {
+ _validateDelay(_pauseDelay);
+ pauseDelay = _pauseDelay;
+ emit SetPauseDelay(_pauseDelay);
+ }
+
+ /**
+ * @inheritdoc IMidasPauseManager
+ */
+ function setUnpauseDelay(uint32 _unpauseDelay)
+ external
+ onlyRoleDelayOverride(contractAdminRole(), DELAY_FOR_SET_DELAY, true)
+ {
+ _validateDelay(_unpauseDelay);
+ unpauseDelay = _unpauseDelay;
+ emit SetUnpauseDelay(_unpauseDelay);
+ }
+
+ /**
+ * @inheritdoc IMidasPauseManager
+ */
+ function globalPause() external onlyAdminPause {
+ if (globalPaused) {
+ return;
+ }
+
+ globalPaused = true;
+ emit GlobalPauseStatusChange(true);
+ }
+
+ /**
+ * @inheritdoc IMidasPauseManager
+ */
+ function globalUnpause() external onlyAdminUnpause {
+ if (!globalPaused) {
+ return;
+ }
+
+ globalPaused = false;
+ emit GlobalPauseStatusChange(false);
+ }
+
+ /**
+ * @inheritdoc IMidasPauseManager
+ */
+ function bulkPauseContract(address[] calldata contractAddrs)
+ external
+ onlyAdminPause
+ {
+ for (uint256 i = 0; i < contractAddrs.length; ++i) {
+ _changeContratPauseStatus(contractAddrs[i], true);
+ }
+ }
+
+ /**
+ * @inheritdoc IMidasPauseManager
+ */
+ function bulkUnpauseContract(address[] calldata contractAddrs)
+ external
+ onlyAdminUnpause
+ {
+ for (uint256 i = 0; i < contractAddrs.length; ++i) {
+ _changeContratPauseStatus(contractAddrs[i], false);
+ }
+ }
+
+ /**
+ * @inheritdoc IMidasPauseManager
+ */
+ function bulkPauseContractFn(
+ address[] calldata contractAddrs,
+ bytes4[] calldata selectors
+ ) external onlyAdminPause {
+ for (uint256 i = 0; i < contractAddrs.length; ++i) {
+ address contractAddr = contractAddrs[i];
+
+ for (uint256 j = 0; j < selectors.length; ++j) {
+ bytes4 selector = selectors[j];
+
+ if (contractFnPaused[contractAddr][selector]) {
+ continue;
+ }
+
+ contractFnPaused[contractAddr][selector] = true;
+ emit FnPauseStatusChange(contractAddr, selector, true);
+ }
+ }
+ }
+
+ /**
+ * @inheritdoc IMidasPauseManager
+ */
+ function bulkUnpauseContractFn(
+ address[] calldata contractAddrs,
+ bytes4[] calldata selectors
+ ) external onlyAdminUnpause {
+ for (uint256 i = 0; i < contractAddrs.length; ++i) {
+ address contractAddr = contractAddrs[i];
+
+ for (uint256 j = 0; j < selectors.length; ++j) {
+ bytes4 selector = selectors[j];
+
+ if (!contractFnPaused[contractAddr][selector]) {
+ continue;
+ }
+
+ contractFnPaused[contractAddr][selector] = false;
+ emit FnPauseStatusChange(contractAddr, selector, false);
+ }
+ }
+ }
+
+ /**
+ * @inheritdoc IMidasPauseManager
+ */
+ function contractAdminPause(address contractAddr)
+ external
+ onlyPausableContractAdminPause(contractAddr)
+ {
+ _changeContratPauseStatus(contractAddr, true);
+ }
+
+ /**
+ * @inheritdoc IMidasPauseManager
+ */
+ function contractAdminUnpause(address contractAddr)
+ external
+ onlyPausableContractAdminUnpause(contractAddr)
+ {
+ _changeContratPauseStatus(contractAddr, false);
+ }
+
+ /**
+ * @inheritdoc IMidasPauseManager
+ */
+ function isPaused(address contractAddr, bytes4 selector)
+ external
+ view
+ returns (bool)
+ {
+ return
+ globalPaused ||
+ contractPaused[contractAddr] ||
+ isFunctionPaused(contractAddr, selector);
+ }
+
+ /**
+ * @inheritdoc IMidasPauseManager
+ */
+ function isFunctionPaused(address contractAddr, bytes4 selector)
+ public
+ view
+ returns (bool)
+ {
+ return contractFnPaused[contractAddr][selector];
+ }
+
+ /**
+ * @inheritdoc IMidasPauseManager
+ */
+ function pauseAdminRole() public view returns (bytes32) {
+ return contractAdminRole();
+ }
+
+ /**
+ * @inheritdoc WithMidasAccessControl
+ */
+ function contractAdminRole() public pure override returns (bytes32) {
+ return _PAUSE_ADMIN_ROLE;
+ }
+
+ /**
+ * @dev changes the pause status of the `contractAddr` contract
+ * @param contractAddr address of the contract
+ * @param paused paused status
+ */
+ function _changeContratPauseStatus(address contractAddr, bool paused)
+ private
+ {
+ if (contractPaused[contractAddr] == paused) {
+ return;
+ }
+
+ contractPaused[contractAddr] = paused;
+ emit ContractPauseStatusChange(contractAddr, paused);
+ }
+
+ /**
+ * @dev validates that caller has access to the `contractAddr` contract admin role
+ * @param contractAddr address of the contract
+ */
+ function _validateContractAdminAccess(
+ address contractAddr,
+ uint32 overrideDelay
+ ) private view {
+ bytes32 role = _getPausableRole(contractAddr);
+
+ _validateFunctionAccessWithTimelock(
+ role,
+ overrideDelay,
+ false,
+ msg.sender,
+ true
+ );
+ }
+
+ /**
+ * @dev validates that the delay is within the maximum delay
+ * @param delay delay to validate
+ */
+ function _validateDelay(uint32 delay) private view {
+ MidasAuthLibrary.validateTimelockDelay(delay);
+ }
+
+ /**
+ * @dev gets the pauser role and validate function role for the `contractAddr` contract
+ * @param contractAddr address of the contract
+ * @return role pauser role
+ */
+ function _getPausableRole(address contractAddr)
+ private
+ view
+ returns (bytes32)
+ {
+ return IMidasAccessControlManaged(contractAddr).contractAdminRole();
+ }
+}
diff --git a/contracts/access/MidasTimelockController.sol b/contracts/access/MidasTimelockController.sol
index 279f9004..5c1094b7 100644
--- a/contracts/access/MidasTimelockController.sol
+++ b/contracts/access/MidasTimelockController.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol";
diff --git a/contracts/access/MidasTimelockManager.sol b/contracts/access/MidasTimelockManager.sol
new file mode 100644
index 00000000..896627d7
--- /dev/null
+++ b/contracts/access/MidasTimelockManager.sol
@@ -0,0 +1,913 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
+
+import {WithMidasAccessControl} from "../access/WithMidasAccessControl.sol";
+import {TimelockControllerUpgradeable as TimelockController} from "@openzeppelin/contracts-upgradeable/governance/TimelockControllerUpgradeable.sol";
+import {IMidasTimelockManager, GetOperationStatusResult, TimelockOperationStatus} from "../interfaces/IMidasTimelockManager.sol";
+import {MidasAuthLibrary} from "../libraries/MidasAuthLibrary.sol";
+import {IMidasAccessControl} from "../interfaces/IMidasAccessControl.sol";
+import {IMidasPauseManager} from "../interfaces/IMidasPauseManager.sol";
+import {EnumerableSetUpgradeable as EnumerableSet} from "@openzeppelin/contracts-upgradeable/utils/structs/EnumerableSetUpgradeable.sol";
+import {ReentrancyGuardUpgradeable as ReentrancyGuard} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
+
+/**
+ * @title MidasTimelockManager
+ * @notice Manages timelock scheduling, security council votes and operation details
+ * @author RedDuck Software
+ */
+contract MidasTimelockManager is
+ IMidasTimelockManager,
+ WithMidasAccessControl,
+ ReentrancyGuard
+{
+ using MidasAuthLibrary for IMidasAccessControl;
+ using EnumerableSet for EnumerableSet.AddressSet;
+ using EnumerableSet for EnumerableSet.Bytes32Set;
+
+ /**
+ * @dev internal storage for a timelock operation details
+ */
+ struct TimelockOperationDetails {
+ EnumerableSet.AddressSet votersForExecution;
+ EnumerableSet.AddressSet votersForVeto;
+ uint256 councilVersion;
+ bytes32 dataHash;
+ TimelockOperationStatus status;
+ uint8 pauseReasonCode;
+ bool isSetCouncilOperation;
+ uint32 createdAt;
+ uint32 executionApprovedAt;
+ uint32 pausedAt;
+ address operationProposer;
+ address pauser;
+ }
+
+ /**
+ * @notice role that can pause timelock operations
+ */
+ bytes32 public constant TIMELOCK_OPERATION_PAUSER_ROLE =
+ keccak256("TIMELOCK_OPERATION_PAUSER_ROLE");
+
+ /**
+ * @notice role that can set security council
+ */
+ bytes32 public constant SECURITY_COUNCIL_MANAGER_ROLE =
+ keccak256("SECURITY_COUNCIL_MANAGER_ROLE");
+
+ /**
+ * @notice min security council members
+ */
+ uint256 public constant SECURITY_COUNCIL_MIN_MEMBERS = 5;
+
+ /**
+ * @notice max security council members
+ */
+ uint256 public constant SECURITY_COUNCIL_MAX_MEMBERS = 15;
+
+ /**
+ * @notice time after schedule when operation expires
+ */
+ uint256 public constant EXPIRY_PERIOD = 45 days;
+
+ /**
+ * @notice dispute period after execution approval
+ */
+ uint256 public constant DISPUTE_PERIOD = 3 days;
+
+ /**
+ * @notice hard cap for max pending operations per proposer
+ */
+ uint256 public constant MAX_OF_MAX_PENDING_OPERATIONS_PER_PROPOSER = 100;
+
+ /**
+ * @inheritdoc IMidasTimelockManager
+ */
+ mapping(bytes32 => uint256) public dataHashIndexes;
+
+ /**
+ * @inheritdoc IMidasTimelockManager
+ */
+ mapping(address => uint256) public proposerPendingOperationsCount;
+
+ /**
+ * @dev set of security council addresses by version
+ */
+ mapping(uint256 => EnumerableSet.AddressSet) private _securityCouncils;
+
+ /**
+ * @dev mapping, operationId to operation details
+ */
+ mapping(bytes32 => TimelockOperationDetails) private _operationDetails;
+
+ /**
+ * @dev set of pending operation ids
+ */
+ EnumerableSet.Bytes32Set private _pendingOperations;
+
+ /**
+ * @inheritdoc IMidasTimelockManager
+ */
+ address public timelock;
+
+ /**
+ * @inheritdoc IMidasTimelockManager
+ */
+ uint256 public maxPendingOperationsPerProposer;
+
+ /**
+ * @inheritdoc IMidasTimelockManager
+ */
+ uint256 public securityCouncilVersion;
+
+ /**
+ * @inheritdoc IMidasTimelockManager
+ */
+ bytes32 public pendingSetCouncilOperationId;
+
+ /**
+ * @dev leaving a storage gap for futures updates
+ */
+ uint256[50] private __gap;
+
+ /**
+ * @dev validates that the caller has the contract admin role without timelock
+ * @param validateFunctionRole whether to validate the function role
+ */
+ modifier onlyContractAdminNoTimelock(bool validateFunctionRole) {
+ _validateFunctionAccessWithoutTimelock(
+ contractAdminRole(),
+ false,
+ msg.sender,
+ validateFunctionRole
+ );
+ _;
+ }
+
+ /**
+ * @dev validates that the caller has the contract admin role without function role
+ */
+ modifier onlyContractAdminNoFunctionRole() {
+ _validateFunctionAccessWithTimelock(
+ contractAdminRole(),
+ MidasAuthLibrary.NULL_DELAY,
+ false,
+ msg.sender,
+ false
+ );
+ _;
+ }
+
+ /**
+ * @notice Initializes the contract
+ * @param _accessControl MidasAccessControl address
+ * @param _maxPendingOperationsPerProposer max pending ops per proposer
+ * @param _initSecurityCouncil initial security council members
+ */
+ function initialize(
+ address _accessControl,
+ uint256 _maxPendingOperationsPerProposer,
+ address[] calldata _initSecurityCouncil
+ ) external initializer {
+ __WithMidasAccessControl_init(_accessControl);
+
+ _setMaxPendingOperationsPerProposer(_maxPendingOperationsPerProposer);
+
+ _setSecurityCouncil(_initSecurityCouncil, securityCouncilVersion);
+ }
+
+ /**
+ * @notice Initializes the timelock controller
+ * @param _timelock timelock controller address
+ */
+ function initializeTimelock(address _timelock)
+ external
+ onlyContractAdminNoTimelock(false)
+ {
+ require(timelock == address(0), TimelockAlreadySet());
+ require(_timelock != address(0), InvalidAddress(_timelock));
+ timelock = _timelock;
+ }
+
+ /**
+ * @inheritdoc IMidasTimelockManager
+ */
+ function setMaxPendingOperationsPerProposer(
+ uint256 _maxPendingOperationsPerProposer
+ ) external onlyContractAdminNoFunctionRole {
+ _setMaxPendingOperationsPerProposer(_maxPendingOperationsPerProposer);
+ }
+
+ /**
+ * @inheritdoc IMidasTimelockManager
+ */
+ function setSecurityCouncil(address[] calldata members)
+ external
+ onlyRole(SECURITY_COUNCIL_MANAGER_ROLE, false)
+ {
+ if (
+ msg.sender != timelock && pendingSetCouncilOperationId != bytes32(0)
+ ) {
+ revert PendingSetCouncilOperationExists();
+ }
+
+ uint256 version = securityCouncilVersion + 1;
+ securityCouncilVersion = version;
+
+ _setSecurityCouncil(members, version);
+ }
+
+ /**
+ * @inheritdoc IMidasTimelockManager
+ */
+ function bulkScheduleTimelockOperation(
+ ScheduleTimelockOperationParams[] calldata params
+ ) external {
+ for (uint256 i = 0; i < params.length; ++i) {
+ ScheduleTimelockOperationParams calldata param = params[i];
+ _scheduleTimelockOperation(param.target, param.data);
+ }
+ }
+
+ /**
+ * @inheritdoc IMidasTimelockManager
+ */
+ function scheduleTimelockOperation(
+ ScheduleTimelockOperationParams calldata params
+ ) external {
+ _scheduleTimelockOperation(params.target, params.data);
+ }
+
+ /**
+ * @inheritdoc IMidasTimelockManager
+ */
+ function executeTimelockOperation(
+ address target,
+ bytes calldata data,
+ bool revertOnFailure
+ ) external nonReentrant onlyContractAdminNoTimelock(true) {
+ TimelockController _timelock = TimelockController(payable(timelock));
+
+ (
+ bytes32 operationId,
+ bytes32 dataHash,
+ uint256 dataHashIndex
+ ) = _getOperationId(_timelock, target, data);
+
+ (
+ TimelockOperationStatus status,
+ TimelockOperationDetails storage opDetails
+ ) = _getOperationStatus(operationId);
+
+ require(
+ status == TimelockOperationStatus.Pending ||
+ status == TimelockOperationStatus.ReadyToExecute,
+ UnexpectedOperationStatus(status)
+ );
+
+ require(
+ _timelock.isOperationReady(operationId),
+ TimelockOperationNotReady()
+ );
+ bool success = true;
+
+ _resetPendingSetCouncilOperation(opDetails);
+
+ --proposerPendingOperationsCount[opDetails.operationProposer];
+ require(_pendingOperations.remove(operationId), OperationNotPending());
+
+ try
+ _timelock.execute(
+ target,
+ 0,
+ data,
+ bytes32(0),
+ bytes32(dataHashIndex)
+ )
+ {
+ opDetails.status = TimelockOperationStatus.Executed;
+ } catch (bytes memory err) {
+ if (revertOnFailure) {
+ assembly ("memory-safe") {
+ revert(add(err, 32), mload(err))
+ }
+ } else {
+ _timelock.cancel(operationId);
+ success = false;
+ opDetails.status = TimelockOperationStatus.ExecutedWithFailure;
+ }
+ }
+
+ // updating hash after execution to be able to get current proposer
+ // from the executing operation
+ // in case of reentrancy timelock.execute will revert
+ dataHashIndexes[dataHash] = dataHashIndex + 1;
+
+ emit ExecuteTimelockOperation(msg.sender, operationId, success);
+ }
+
+ /**
+ * @inheritdoc IMidasTimelockManager
+ */
+ function pauseOperation(bytes32 operationId, uint8 pauseReasonCode)
+ external
+ onlyRoleNoTimelock(TIMELOCK_OPERATION_PAUSER_ROLE, false)
+ {
+ require(_isPendingOperation(operationId), OperationNotPending());
+
+ (
+ TimelockOperationStatus status,
+ TimelockOperationDetails storage opDetails
+ ) = _getOperationStatus(operationId);
+
+ require(
+ status == TimelockOperationStatus.Pending,
+ UnexpectedOperationStatus(status)
+ );
+
+ uint256 councilVersion = securityCouncilVersion;
+ opDetails.status = TimelockOperationStatus.Paused;
+ opDetails.pauseReasonCode = pauseReasonCode;
+ opDetails.councilVersion = councilVersion;
+ opDetails.pauser = msg.sender;
+ opDetails.pausedAt = uint32(block.timestamp);
+
+ emit PauseTimelockOperation(
+ msg.sender,
+ operationId,
+ pauseReasonCode,
+ councilVersion
+ );
+ }
+
+ /**
+ * @inheritdoc IMidasTimelockManager
+ */
+ function voteForVeto(bytes32 operationId) external {
+ (
+ TimelockOperationStatus status,
+ TimelockOperationDetails storage opDetails
+ ) = _getOperationStatus(operationId);
+
+ require(
+ isInSecurityCouncil(opDetails.councilVersion, msg.sender),
+ NotInSecurityCouncil()
+ );
+
+ require(
+ status == TimelockOperationStatus.Paused ||
+ status == TimelockOperationStatus.ApprovedExecution ||
+ status == TimelockOperationStatus.ReadyToExecute,
+ UnexpectedOperationStatus(status)
+ );
+
+ require(opDetails.votersForVeto.add(msg.sender), AlreadyVoted());
+
+ if (
+ opDetails.votersForVeto.length() >=
+ councilQuorum(opDetails.councilVersion)
+ ) {
+ opDetails.status = TimelockOperationStatus.ReadyToAbort;
+ }
+
+ emit PausedProposalVoteCast(msg.sender, operationId, false);
+ }
+
+ /**
+ * @inheritdoc IMidasTimelockManager
+ */
+ function voteForExecution(bytes32 operationId) external {
+ (
+ TimelockOperationStatus status,
+ TimelockOperationDetails storage opDetails
+ ) = _getOperationStatus(operationId);
+
+ require(
+ isInSecurityCouncil(opDetails.councilVersion, msg.sender),
+ NotInSecurityCouncil()
+ );
+
+ require(
+ status == TimelockOperationStatus.Paused,
+ UnexpectedOperationStatus(status)
+ );
+
+ require(opDetails.votersForExecution.add(msg.sender), AlreadyVoted());
+ require(!opDetails.votersForVeto.contains(msg.sender), AlreadyVoted());
+
+ if (
+ opDetails.votersForExecution.length() >=
+ councilQuorum(opDetails.councilVersion)
+ ) {
+ opDetails.status = TimelockOperationStatus.ApprovedExecution;
+ opDetails.executionApprovedAt = uint32(block.timestamp);
+ }
+
+ emit PausedProposalVoteCast(msg.sender, operationId, true);
+ }
+
+ /**
+ * @inheritdoc IMidasTimelockManager
+ */
+ function abortOperation(bytes32 operationId) external {
+ (
+ TimelockOperationStatus status,
+ TimelockOperationDetails storage opDetails
+ ) = _getOperationStatus(operationId);
+
+ uint256 dataHashIndex = dataHashIndexes[opDetails.dataHash];
+
+ require(
+ status == TimelockOperationStatus.ReadyToAbort ||
+ status == TimelockOperationStatus.Expired,
+ UnexpectedOperationStatus(status)
+ );
+
+ _resetPendingSetCouncilOperation(opDetails);
+
+ dataHashIndexes[opDetails.dataHash] = dataHashIndex + 1;
+ opDetails.status = TimelockOperationStatus.Aborted;
+ --proposerPendingOperationsCount[opDetails.operationProposer];
+ require(_pendingOperations.remove(operationId), OperationNotPending());
+
+ TimelockController(payable(timelock)).cancel(operationId);
+
+ emit AbortTimelockOperation(msg.sender, operationId, status);
+ }
+
+ /**
+ * @inheritdoc IMidasTimelockManager
+ */
+ function getOriginalProposer(address target, bytes calldata data)
+ external
+ view
+ returns (address)
+ {
+ TimelockController _timelock = TimelockController(payable(timelock));
+ (bytes32 operationId, , ) = _getOperationId(_timelock, target, data);
+ return _operationDetails[operationId].operationProposer;
+ }
+
+ /**
+ * @inheritdoc IMidasTimelockManager
+ */
+ function councilQuorum(uint256 version) public view returns (uint8) {
+ return uint8(_securityCouncils[version].length()) / 2 + 1;
+ }
+
+ /**
+ * @inheritdoc IMidasTimelockManager
+ */
+ function getCouncilMemberVoteStatus(
+ bytes32 operationId,
+ address councilMember
+ ) external view returns (bool votedForExecution, bool votedForVeto) {
+ (, TimelockOperationDetails storage opDetails) = _getOperationStatus(
+ operationId
+ );
+ return (
+ opDetails.votersForExecution.contains(councilMember),
+ opDetails.votersForVeto.contains(councilMember)
+ );
+ }
+
+ /**
+ * @inheritdoc IMidasTimelockManager
+ */
+ function getPendingOperations() external view returns (bytes32[] memory) {
+ return _pendingOperations.values();
+ }
+
+ /**
+ * @inheritdoc IMidasTimelockManager
+ */
+ function getOperationDetails(bytes32 operationId)
+ external
+ view
+ returns (GetOperationStatusResult memory result)
+ {
+ (
+ TimelockOperationStatus status,
+ TimelockOperationDetails storage opDetails
+ ) = _getOperationStatus(operationId);
+
+ result.status = status;
+ result.createdAt = opDetails.createdAt;
+ result.executionApprovedAt = opDetails.executionApprovedAt;
+ result.pauseReasonCode = opDetails.pauseReasonCode;
+ result.councilVersion = opDetails.councilVersion;
+ result.operationProposer = opDetails.operationProposer;
+ result.pauser = opDetails.pauser;
+ result.dataHash = opDetails.dataHash;
+ result.votesForExecution = uint8(opDetails.votersForExecution.length());
+ result.votesForVeto = uint8(opDetails.votersForVeto.length());
+ result.isSetCouncilOperation = opDetails.isSetCouncilOperation;
+ result.pausedAt = opDetails.pausedAt;
+ }
+
+ /**
+ * @inheritdoc IMidasTimelockManager
+ */
+ function getOperationStatus(bytes32 operationId)
+ external
+ view
+ returns (TimelockOperationStatus status)
+ {
+ (status, ) = _getOperationStatus(operationId);
+ }
+
+ /**
+ * @inheritdoc IMidasTimelockManager
+ */
+ function getOperationStatusRaw(bytes32 operationId)
+ external
+ view
+ returns (TimelockOperationStatus status)
+ {
+ return _operationDetails[operationId].status;
+ }
+
+ /**
+ * @inheritdoc IMidasTimelockManager
+ */
+ function getSecurityCouncilMembers(uint256 version)
+ external
+ view
+ returns (address[] memory)
+ {
+ return _securityCouncils[version].values();
+ }
+
+ /**
+ * @inheritdoc IMidasTimelockManager
+ */
+ function getOperationId(address target, bytes calldata data)
+ external
+ view
+ returns (bytes32 operationId)
+ {
+ (operationId, , ) = _getOperationId(
+ TimelockController(payable(timelock)),
+ target,
+ data
+ );
+ }
+
+ /**
+ * @inheritdoc IMidasTimelockManager
+ */
+ function getTargetRole(
+ address target,
+ bytes calldata data,
+ address proposer
+ )
+ public
+ view
+ returns (
+ bytes32, /* role */
+ uint32 /* overrideDelay */
+ )
+ {
+ (bool success, bytes memory err) = target.staticcall(
+ MidasAuthLibrary.appendProposer(data, proposer)
+ );
+ require(!success, PreflightCallUnexpectedSuccess());
+ bytes4 selector = _getFunctionSelector(data);
+
+ (
+ bytes32 role,
+ uint32 overrideDelay,
+ bool roleIsFunctionOperator,
+ bool validateFunctionRole
+ ) = _decodePreflightSucceededError(err);
+
+ return (
+ accessControl.validateFunctionAccess(
+ target,
+ role,
+ overrideDelay,
+ roleIsFunctionOperator,
+ proposer,
+ selector,
+ validateFunctionRole
+ ),
+ overrideDelay
+ );
+ }
+
+ /**
+ * @inheritdoc IMidasTimelockManager
+ */
+ function isInSecurityCouncil(uint256 version, address account)
+ public
+ view
+ returns (bool)
+ {
+ return _securityCouncils[version].contains(account);
+ }
+
+ /**
+ * @inheritdoc WithMidasAccessControl
+ */
+ function contractAdminRole() public pure override returns (bytes32) {
+ return _DEFAULT_ADMIN_ROLE;
+ }
+
+ /**
+ * @dev calculates and returns the actual status of an operation
+ * @param operationId operation id
+ * @return status actual operation status
+ * @return opDetails operation details
+ */
+ function _getOperationStatus(bytes32 operationId)
+ private
+ view
+ returns (
+ TimelockOperationStatus status,
+ TimelockOperationDetails storage opDetails
+ )
+ {
+ opDetails = _operationDetails[operationId];
+ status = opDetails.status;
+
+ if (
+ status != TimelockOperationStatus.Pending &&
+ status != TimelockOperationStatus.Paused &&
+ status != TimelockOperationStatus.ApprovedExecution
+ ) {
+ return (status, opDetails);
+ }
+
+ uint256 passedSinceCreated = block.timestamp - opDetails.createdAt;
+
+ if (passedSinceCreated >= EXPIRY_PERIOD) {
+ status = TimelockOperationStatus.Expired;
+ return (status, opDetails);
+ }
+
+ if (
+ status == TimelockOperationStatus.ApprovedExecution &&
+ block.timestamp - opDetails.executionApprovedAt >= DISPUTE_PERIOD
+ ) {
+ status = TimelockOperationStatus.ReadyToExecute;
+ return (status, opDetails);
+ }
+
+ return (status, opDetails);
+ }
+
+ /**
+ * @dev schedules a timelock operation
+ * @param target target contract
+ * @param data operation data
+ */
+ function _scheduleTimelockOperation(address target, bytes calldata data)
+ private
+ {
+ require(target != timelock, InvalidAddress(target));
+
+ address proposer = msg.sender;
+
+ (bytes32 targetRole, uint32 overrideDelay) = getTargetRole(
+ target,
+ data,
+ proposer
+ );
+
+ (uint32 delay, ) = accessControl.getRoleTimelockDelay(
+ targetRole,
+ overrideDelay
+ );
+
+ require(delay != 0, NoTimelockDelayForRole());
+
+ TimelockController _timelock = TimelockController(payable(timelock));
+
+ (
+ bytes32 operationId,
+ bytes32 dataHash,
+ uint256 dataHashIndex
+ ) = _getOperationId(_timelock, target, data);
+
+ bool isSetCouncil = target == address(this) &&
+ _getFunctionSelector(data) == this.setSecurityCouncil.selector;
+
+ ++proposerPendingOperationsCount[proposer];
+
+ require(
+ proposerPendingOperationsCount[proposer] <=
+ maxPendingOperationsPerProposer,
+ TooManyPendingOperations()
+ );
+
+ TimelockOperationDetails storage opDetails = _operationDetails[
+ operationId
+ ];
+
+ if (isSetCouncil) {
+ require(
+ pendingSetCouncilOperationId == bytes32(0),
+ PendingSetCouncilOperationExists()
+ );
+ opDetails.isSetCouncilOperation = true;
+ pendingSetCouncilOperationId = operationId;
+ }
+
+ opDetails.dataHash = dataHash;
+ opDetails.operationProposer = proposer;
+ opDetails.createdAt = uint32(block.timestamp);
+ opDetails.status = TimelockOperationStatus.Pending;
+
+ require(_pendingOperations.add(operationId), OperationAlreadyPending());
+
+ _timelock.schedule(
+ target,
+ 0,
+ data,
+ bytes32(0),
+ bytes32(dataHashIndex),
+ delay
+ );
+
+ emit ScheduleTimelockOperation(proposer, operationId);
+ }
+
+ /**
+ * @dev sets security council under a specific version
+ * @param members council member addresses
+ * @param version council version
+ */
+ function _setSecurityCouncil(address[] calldata members, uint256 version)
+ private
+ {
+ require(
+ members.length >= SECURITY_COUNCIL_MIN_MEMBERS &&
+ members.length <= SECURITY_COUNCIL_MAX_MEMBERS,
+ InvalidSecurityCouncilMembersLength()
+ );
+
+ EnumerableSet.AddressSet storage securityCouncil = _securityCouncils[
+ version
+ ];
+
+ for (uint256 i = 0; i < members.length; ++i) {
+ require(members[i] != address(0), InvalidAddress(members[i]));
+ require(
+ securityCouncil.add(members[i]),
+ InvalidAddress(members[i])
+ );
+ }
+
+ emit SetSecurityCouncil(version, members);
+ }
+
+ /**
+ * @dev sets max pending operations per proposer
+ * @param _maxPendingOperationsPerProposer max pending operations per proposer
+ */
+ function _setMaxPendingOperationsPerProposer(
+ uint256 _maxPendingOperationsPerProposer
+ ) private {
+ require(
+ _maxPendingOperationsPerProposer > 0 &&
+ _maxPendingOperationsPerProposer <=
+ MAX_OF_MAX_PENDING_OPERATIONS_PER_PROPOSER,
+ InvalidMaxPendingOperationsPerProposer()
+ );
+ maxPendingOperationsPerProposer = _maxPendingOperationsPerProposer;
+
+ emit SetMaxPendingOperationsPerProposer(
+ _maxPendingOperationsPerProposer
+ );
+ }
+
+ /**
+ * @dev resets the pending set-council operation
+ * if the operation is a set-council operation
+ * @param opDetails operation details
+ */
+ function _resetPendingSetCouncilOperation(
+ TimelockOperationDetails storage opDetails
+ ) private {
+ if (!opDetails.isSetCouncilOperation) {
+ return;
+ }
+
+ pendingSetCouncilOperationId = bytes32(0);
+ }
+
+ /**
+ * @dev gets the timelock operation id for a given target and data
+ * @param _timelock timelock controller
+ * @param target target contract
+ * @param data operation data
+ * @return operationId operation id
+ * @return dataHash data hash
+ * @return dataHashIndex data hash index
+ */
+ function _getOperationId(
+ TimelockController _timelock,
+ address target,
+ bytes calldata data
+ )
+ private
+ view
+ returns (
+ bytes32 operationId,
+ bytes32 dataHash,
+ uint256 dataHashIndex
+ )
+ {
+ dataHash = _getDataHash(target, data);
+ dataHashIndex = dataHashIndexes[dataHash];
+
+ operationId = _timelock.hashOperation(
+ target,
+ 0,
+ data,
+ bytes32(0),
+ bytes32(dataHashIndex)
+ );
+ }
+
+ /**
+ * @dev checks if an operation is pending
+ * @param operationId operation id
+ * @return true if the operation is pending
+ */
+ function _isPendingOperation(bytes32 operationId)
+ private
+ view
+ returns (bool)
+ {
+ return _pendingOperations.contains(operationId);
+ }
+
+ /**
+ * @dev gets the function selector from operation data
+ * @param data operation data
+ * @return function selector
+ */
+ function _getFunctionSelector(bytes calldata data)
+ private
+ pure
+ returns (bytes4)
+ {
+ return bytes4(data);
+ }
+
+ /**
+ * @dev gets the keccak256 hash of a given target and data
+ * @param target target contract
+ * @param data operation data
+ * @return data hash
+ */
+ function _getDataHash(address target, bytes calldata data)
+ private
+ pure
+ returns (bytes32)
+ {
+ // adding 0 as msg.value to make hash generation future-proof
+ return keccak256(abi.encodePacked(target, uint256(0), data));
+ }
+
+ /**
+ * @dev decodes a `RolePreflightSucceeded` error
+ * @param err error bytes
+ * @return role role
+ * @return overrideDelay override delay for the invocation
+ * @return roleIsFunctionOperator whether the role is a function operator role
+ * @return validateFunctionRole whether to validate the function role
+ */
+ function _decodePreflightSucceededError(bytes memory err)
+ private
+ pure
+ returns (
+ bytes32 role,
+ uint32 overrideDelay,
+ bool roleIsFunctionOperator,
+ bool validateFunctionRole
+ )
+ {
+ require(err.length == 132, InvalidPreflightError(err));
+
+ bytes4 selector;
+
+ // getting the selector of custom error
+ assembly {
+ selector := mload(add(err, 32))
+ }
+
+ // checking if the error is a RolePreflightSucceeded error
+ require(
+ selector == RolePreflightSucceeded.selector,
+ InvalidPreflightError(err)
+ );
+
+ assembly {
+ role := mload(add(err, 36))
+ overrideDelay := mload(add(err, 68))
+ roleIsFunctionOperator := mload(add(err, 100))
+ validateFunctionRole := mload(add(err, 132))
+ }
+ }
+}
diff --git a/contracts/access/Pausable.sol b/contracts/access/Pausable.sol
deleted file mode 100644
index 289f311c..00000000
--- a/contracts/access/Pausable.sol
+++ /dev/null
@@ -1,89 +0,0 @@
-// SPDX-License-Identifier: UNLICENSED
-pragma solidity 0.8.9;
-
-import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
-import "../access/WithMidasAccessControl.sol";
-
-/**
- * @title Pausable
- * @notice Base contract that implements basic functions and modifiers
- * with pause functionality
- * @author RedDuck Software
- */
-abstract contract Pausable is WithMidasAccessControl, PausableUpgradeable {
- mapping(bytes4 => bool) public fnPaused;
-
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @param caller caller address (msg.sender)
- * @param fn function id
- */
- event PauseFn(address indexed caller, bytes4 fn);
-
- /**
- * @param caller caller address (msg.sender)
- * @param fn function id
- */
- event UnpauseFn(address indexed caller, bytes4 fn);
-
- modifier whenFnNotPaused(bytes4 fn) {
- _requireNotPaused();
- require(!fnPaused[fn], "Pausable: fn paused");
- _;
- }
- /**
- * @dev checks that a given `account`
- * has a determinedPauseAdminRole
- */
- modifier onlyPauseAdmin() {
- _onlyRole(pauseAdminRole(), msg.sender);
- _;
- }
-
- /**
- * @dev upgradeable pattern contract`s initializer
- * @param _accessControl MidasAccessControl contract address
- */
- // solhint-disable-next-line func-name-mixedcase
- function __Pausable_init(address _accessControl) internal onlyInitializing {
- super.__Pausable_init();
- __WithMidasAccessControl_init(_accessControl);
- }
-
- function pause() external onlyPauseAdmin {
- _pause();
- }
-
- function unpause() external onlyPauseAdmin {
- _unpause();
- }
-
- /**
- * @dev pause specific function
- * @param fn function id
- */
- function pauseFn(bytes4 fn) external onlyPauseAdmin {
- require(!fnPaused[fn], "Pausable: fn paused");
- fnPaused[fn] = true;
- emit PauseFn(msg.sender, fn);
- }
-
- /**
- * @dev unpause specific function
- * @param fn function id
- */
- function unpauseFn(bytes4 fn) external onlyPauseAdmin {
- require(fnPaused[fn], "Pausable: fn unpaused");
- fnPaused[fn] = false;
- emit UnpauseFn(msg.sender, fn);
- }
-
- /**
- * @dev virtual function to determine pauseAdmin role
- */
- function pauseAdminRole() public view virtual returns (bytes32);
-}
diff --git a/contracts/access/WithMidasAccessControl.sol b/contracts/access/WithMidasAccessControl.sol
index 22a74154..4c668047 100644
--- a/contracts/access/WithMidasAccessControl.sol
+++ b/contracts/access/WithMidasAccessControl.sol
@@ -1,8 +1,10 @@
-// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
-import "./MidasAccessControl.sol";
-import "../abstract/MidasInitializable.sol";
+import {IMidasAccessControl} from "../interfaces/IMidasAccessControl.sol";
+import {MidasInitializable} from "../abstract/MidasInitializable.sol";
+import {MidasAuthLibrary} from "../libraries/MidasAuthLibrary.sol";
+import {IMidasAccessControlManaged} from "../interfaces/IMidasAccessControlManaged.sol";
/**
* @title WithMidasAccessControl
@@ -11,17 +13,20 @@ import "../abstract/MidasInitializable.sol";
*/
abstract contract WithMidasAccessControl is
MidasInitializable,
- MidasAccessControlRoles
+ IMidasAccessControlManaged
{
+ using MidasAuthLibrary for IMidasAccessControl;
+
/**
* @notice admin role
*/
- bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
+ bytes32 internal constant _DEFAULT_ADMIN_ROLE = 0x00;
/**
* @notice MidasAccessControl contract address
+ * @custom:oz-retyped-from MidasAccessControl
*/
- MidasAccessControl public accessControl;
+ IMidasAccessControl public accessControl;
/**
* @dev leaving a storage gap for futures updates
@@ -29,18 +34,73 @@ abstract contract WithMidasAccessControl is
uint256[50] private __gap;
/**
- * @dev checks that given `address` have `role`
+ * @notice error when the value is the same as the previous value
+ * @param value value
+ */
+ error SameBoolValue(bool value);
+
+ /**
+ * @dev validates that the caller has the function role with timelock
+ * @param role base role to validate
+ * @param validateFunctionRole whether to validate the function role
+ */
+ modifier onlyRole(bytes32 role, bool validateFunctionRole) {
+ _validateFunctionAccessWithTimelock(
+ role,
+ MidasAuthLibrary.NULL_DELAY,
+ false,
+ msg.sender,
+ validateFunctionRole
+ );
+ _;
+ }
+
+ /**
+ * @dev validates that the caller has the function role with timelock
+ * @param role base role to validate
+ * @param overrideDelay override delay for the invocation
+ * @param validateFunctionRole whether to validate the function role
*/
- modifier onlyRole(bytes32 role, address account) {
- _onlyRole(role, account);
+ modifier onlyRoleDelayOverride(
+ bytes32 role,
+ uint32 overrideDelay,
+ bool validateFunctionRole
+ ) {
+ _validateFunctionAccessWithTimelock(
+ role,
+ overrideDelay,
+ false,
+ msg.sender,
+ validateFunctionRole
+ );
_;
}
/**
- * @dev checks that given `address` do not have `role`
+ * @dev validates that the caller has the function role without timelock
+ * @param role base role to validate
*/
- modifier onlyNotRole(bytes32 role, address account) {
- _onlyNotRole(role, account);
+ modifier onlyRoleNoTimelock(bytes32 role, bool validateFunctionRole) {
+ _validateFunctionAccessWithoutTimelock(
+ role,
+ false,
+ msg.sender,
+ validateFunctionRole
+ );
+ _;
+ }
+
+ /**
+ * @dev validates that the caller has the contract admin role or function operator role
+ */
+ modifier onlyContractAdmin() {
+ _validateFunctionAccessWithTimelock(
+ contractAdminRole(),
+ MidasAuthLibrary.NULL_DELAY,
+ false,
+ msg.sender,
+ true
+ );
_;
}
@@ -52,21 +112,60 @@ abstract contract WithMidasAccessControl is
internal
onlyInitializing
{
- require(_accessControl != address(0), "zero address");
- accessControl = MidasAccessControl(_accessControl);
+ require(_accessControl != address(0), InvalidAddress(_accessControl));
+ accessControl = IMidasAccessControl(_accessControl);
}
/**
- * @dev checks that given `address` have `role`
+ * @dev validates that the function access is valid with timelock
+ * @param role base role to validate
+ * @param overrideDelay override delay for the invocation
+ * @param roleIsFunctionOperator whether the role is a function operator
+ * @param account account to validate
+ * @param validateFunctionRole whether to validate the function role
*/
- function _onlyRole(bytes32 role, address account) internal view {
- require(accessControl.hasRole(role, account), "WMAC: hasnt role");
+ function _validateFunctionAccessWithTimelock(
+ bytes32 role,
+ uint32 overrideDelay,
+ bool roleIsFunctionOperator,
+ address account,
+ bool validateFunctionRole
+ ) internal view virtual {
+ accessControl.validateFunctionAccessWithTimelock(
+ role,
+ overrideDelay,
+ roleIsFunctionOperator,
+ account,
+ validateFunctionRole
+ );
}
/**
- * @dev checks that given `address` do not have `role`
+ * @dev validates that the function access is valid without timelock
+ * @param role base role to validate
+ * @param roleIsFunctionOperator whether the role is a function operator
+ * @param account account to validate
+ * @param validateFunctionRole whether to validate the function role
*/
- function _onlyNotRole(bytes32 role, address account) internal view {
- require(!accessControl.hasRole(role, account), "WMAC: has role");
+ function _validateFunctionAccessWithoutTimelock(
+ bytes32 role,
+ bool roleIsFunctionOperator,
+ address account,
+ bool validateFunctionRole
+ ) internal view {
+ accessControl.validateFunctionAccess(
+ address(this),
+ role,
+ MidasAuthLibrary.NO_DELAY,
+ roleIsFunctionOperator,
+ account,
+ msg.sig,
+ validateFunctionRole
+ );
}
+
+ /**
+ * @dev main admin role for the contract
+ */
+ function contractAdminRole() public view virtual returns (bytes32);
}
diff --git a/contracts/feeds/CompositeDataFeed.sol b/contracts/feeds/CompositeDataFeed.sol
index 81deddb0..cb0dcb97 100644
--- a/contracts/feeds/CompositeDataFeed.sol
+++ b/contracts/feeds/CompositeDataFeed.sol
@@ -1,8 +1,11 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
-import "../access/WithMidasAccessControl.sol";
-import "../interfaces/IDataFeed.sol";
+import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
+
+import {MidasInitializable} from "../abstract/MidasInitializable.sol";
+import {WithMidasAccessControl} from "../access/WithMidasAccessControl.sol";
+import {IDataFeed} from "../interfaces/IDataFeed.sol";
/**
* @title CompositeDataFeed
@@ -13,6 +16,32 @@ import "../interfaces/IDataFeed.sol";
* @author RedDuck Software
*/
contract CompositeDataFeed is WithMidasAccessControl, IDataFeed {
+ /**
+ * @param _numeratorFeed new IDataFeed contract address
+ */
+ event ChangeNumeratorFeed(address indexed _numeratorFeed);
+
+ /**
+ * @param _denominatorFeed new IDataFeed contract address
+ */
+ event ChangeDenominatorFeed(address indexed _denominatorFeed);
+
+ /**
+ * @param _maxExpectedAnswer new max expected answer
+ * @param _minExpectedAnswer new min expected answer
+ */
+ event SetMinMaxExpectedAnswer(
+ uint256 indexed _maxExpectedAnswer,
+ uint256 indexed _minExpectedAnswer
+ );
+
+ /**
+ * @notice contract admin role
+ * @custom:oz-upgrades-unsafe-allow state-variable-immutable
+ */
+ // solhint-disable-next-line var-name-mixedcase
+ bytes32 private immutable _CONTRACT_ADMIN_ROLE;
+
/**
* @notice price feed used as the numerator in the ratio calculation.
* @dev typically represents the asset of interest (e.g., cbBTC/USD).
@@ -40,6 +69,15 @@ contract CompositeDataFeed is WithMidasAccessControl, IDataFeed {
*/
uint256[50] private __gap;
+ /**
+ * @notice constructor
+ * @param _contractAdminRole contract admin role
+ * @custom:oz-upgrades-unsafe-allow constructor
+ */
+ constructor(bytes32 _contractAdminRole) MidasInitializable() {
+ _CONTRACT_ADMIN_ROLE = _contractAdminRole;
+ }
+
/**
* @notice upgradeable pattern contract`s initializer
* @param _ac MidasAccessControl contract address
@@ -55,19 +93,14 @@ contract CompositeDataFeed is WithMidasAccessControl, IDataFeed {
uint256 _minExpectedAnswer,
uint256 _maxExpectedAnswer
) external initializer {
+ __WithMidasAccessControl_init(_ac);
+ _setMinMaxExpectedAnswer(_maxExpectedAnswer, _minExpectedAnswer);
+
require(_numeratorFeed != address(0), "CDF: invalid address");
require(_denominatorFeed != address(0), "CDF: invalid address");
- require(
- _maxExpectedAnswer >= _minExpectedAnswer,
- "CDF: invalid exp. prices"
- );
-
- __WithMidasAccessControl_init(_ac);
numeratorFeed = IDataFeed(_numeratorFeed);
denominatorFeed = IDataFeed(_denominatorFeed);
- minExpectedAnswer = _minExpectedAnswer;
- maxExpectedAnswer = _maxExpectedAnswer;
}
/**
@@ -77,11 +110,12 @@ contract CompositeDataFeed is WithMidasAccessControl, IDataFeed {
*/
function changeNumeratorFeed(address _numeratorFeed)
external
- onlyRole(feedAdminRole(), msg.sender)
+ onlyContractAdmin
{
require(_numeratorFeed != address(0), "CDF: invalid address");
numeratorFeed = IDataFeed(_numeratorFeed);
+ emit ChangeNumeratorFeed(_numeratorFeed);
}
/**
@@ -91,43 +125,24 @@ contract CompositeDataFeed is WithMidasAccessControl, IDataFeed {
*/
function changeDenominatorFeed(address _denominatorFeed)
external
- onlyRole(feedAdminRole(), msg.sender)
+ onlyContractAdmin
{
require(_denominatorFeed != address(0), "CDF: invalid address");
denominatorFeed = IDataFeed(_denominatorFeed);
+ emit ChangeDenominatorFeed(_denominatorFeed);
}
/**
- * @dev updates `minExpectedAnswer` value
- * @param _minExpectedAnswer min value
+ * @notice updates `minExpectedAnswer` and `maxExpectedAnswer` values
+ * @param _maxExpectedAnswer new max expected answer
+ * @param _minExpectedAnswer new min expected answer
*/
- function setMinExpectedAnswer(uint256 _minExpectedAnswer)
- external
- onlyRole(feedAdminRole(), msg.sender)
- {
- require(
- maxExpectedAnswer >= _minExpectedAnswer,
- "CDF: invalid exp. prices"
- );
-
- minExpectedAnswer = _minExpectedAnswer;
- }
-
- /**
- * @dev updates `maxExpectedAnswer` value
- * @param _maxExpectedAnswer max value
- */
- function setMaxExpectedAnswer(uint256 _maxExpectedAnswer)
- external
- onlyRole(feedAdminRole(), msg.sender)
- {
- require(
- _maxExpectedAnswer >= minExpectedAnswer,
- "CDF: invalid exp. prices"
- );
-
- maxExpectedAnswer = _maxExpectedAnswer;
+ function setMinMaxExpectedAnswer(
+ uint256 _maxExpectedAnswer,
+ uint256 _minExpectedAnswer
+ ) external onlyContractAdmin {
+ _setMinMaxExpectedAnswer(_maxExpectedAnswer, _minExpectedAnswer);
}
/**
@@ -147,6 +162,13 @@ contract CompositeDataFeed is WithMidasAccessControl, IDataFeed {
);
}
+ /**
+ * @inheritdoc WithMidasAccessControl
+ */
+ function contractAdminRole() public view override returns (bytes32) {
+ return _CONTRACT_ADMIN_ROLE;
+ }
+
/**
* @dev computes the composite price by dividing numerator by denominator
* @param numerator numerator value from the first feed
@@ -164,9 +186,24 @@ contract CompositeDataFeed is WithMidasAccessControl, IDataFeed {
}
/**
- * @inheritdoc IDataFeed
+ * @dev sets the min and max expected answer
+ * @param _maxExpectedAnswer the new max expected answer
+ * @param _minExpectedAnswer the new min expected answer
*/
- function feedAdminRole() public pure virtual override returns (bytes32) {
- return DEFAULT_ADMIN_ROLE;
+ function _setMinMaxExpectedAnswer(
+ uint256 _maxExpectedAnswer,
+ uint256 _minExpectedAnswer
+ ) private {
+ require(_maxExpectedAnswer > 0, "CDF: invalid max exp. price");
+ require(_minExpectedAnswer > 0, "CDF: invalid min exp. price");
+ require(
+ _maxExpectedAnswer >= _minExpectedAnswer,
+ "CDF: invalid exp. prices"
+ );
+
+ maxExpectedAnswer = _maxExpectedAnswer;
+ minExpectedAnswer = _minExpectedAnswer;
+
+ emit SetMinMaxExpectedAnswer(_maxExpectedAnswer, _minExpectedAnswer);
}
}
diff --git a/contracts/feeds/CompositeDataFeedMultiply.sol b/contracts/feeds/CompositeDataFeedMultiply.sol
index 6654e93b..fa598aeb 100644
--- a/contracts/feeds/CompositeDataFeedMultiply.sol
+++ b/contracts/feeds/CompositeDataFeedMultiply.sol
@@ -1,7 +1,7 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
-import "./CompositeDataFeed.sol";
+import {CompositeDataFeed} from "./CompositeDataFeed.sol";
/**
* @title CompositeDataFeedMultiply
@@ -13,6 +13,20 @@ import "./CompositeDataFeed.sol";
* @author RedDuck Software
*/
contract CompositeDataFeedMultiply is CompositeDataFeed {
+ /**
+ * @dev leaving a storage gap for futures updates
+ */
+ uint256[50] private __gap;
+
+ /**
+ * @notice constructor
+ * @param _contractAdminRole contract admin role
+ * @custom:oz-upgrades-unsafe-allow constructor
+ */
+ constructor(bytes32 _contractAdminRole)
+ CompositeDataFeed(_contractAdminRole)
+ {}
+
/**
* @dev computes the composite price by multiplying the two feed values
* @param firstFeedValue value from the first feed
diff --git a/contracts/feeds/CustomAggregatorV3CompatibleFeed.sol b/contracts/feeds/CustomAggregatorV3CompatibleFeed.sol
index 939d728a..e7e27f12 100644
--- a/contracts/feeds/CustomAggregatorV3CompatibleFeed.sol
+++ b/contracts/feeds/CustomAggregatorV3CompatibleFeed.sol
@@ -1,12 +1,12 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
-import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PausableUpgradeable.sol";
-import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
+import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
-import "../access/WithMidasAccessControl.sol";
-import "../libraries/DecimalsCorrectionLibrary.sol";
-import "../interfaces/IDataFeed.sol";
+import {MidasInitializable} from "../abstract/MidasInitializable.sol";
+import {WithMidasAccessControl} from "../access/WithMidasAccessControl.sol";
+import {DecimalsCorrectionLibrary} from "../libraries/DecimalsCorrectionLibrary.sol";
+import {IDataFeed} from "../interfaces/IDataFeed.sol";
/**
* @title CustomAggregatorV3CompatibleFeed
@@ -25,6 +25,13 @@ contract CustomAggregatorV3CompatibleFeed is
uint80 answeredInRound;
}
+ /**
+ * @notice contract admin role
+ * @custom:oz-upgrades-unsafe-allow state-variable-immutable
+ */
+ // solhint-disable-next-line var-name-mixedcase
+ bytes32 private immutable _CONTRACT_ADMIN_ROLE;
+
/**
* @notice feed description
*/
@@ -56,6 +63,16 @@ contract CustomAggregatorV3CompatibleFeed is
*/
mapping(uint80 => RoundData) private _roundData;
+ /**
+ * @dev leaving a storage gap for futures updates
+ */
+ uint256[50] private __gap;
+
+ /**
+ * @param data data value
+ * @param roundId round id
+ * @param timestamp timestamp
+ */
event AnswerUpdated(
int256 indexed data,
uint256 indexed roundId,
@@ -63,11 +80,23 @@ contract CustomAggregatorV3CompatibleFeed is
);
/**
- * @dev checks that msg.sender do have a feedAdminRole() role
+ * @param maxAnswerDeviation the new max answer deviation
+ */
+ event MaxAnswerDeviationUpdated(uint256 indexed maxAnswerDeviation);
+
+ /**
+ * @param minAnswer the new min answer
+ * @param maxAnswer the new max answer
*/
- modifier onlyAggregatorAdmin() {
- _onlyRole(feedAdminRole(), msg.sender);
- _;
+ event SetMinMaxAnswer(int192 indexed minAnswer, int192 indexed maxAnswer);
+
+ /**
+ * @notice constructor
+ * @param _contractAdminRole contract admin role
+ * @custom:oz-upgrades-unsafe-allow constructor
+ */
+ constructor(bytes32 _contractAdminRole) MidasInitializable() {
+ _CONTRACT_ADMIN_ROLE = _contractAdminRole;
}
/**
@@ -87,18 +116,30 @@ contract CustomAggregatorV3CompatibleFeed is
) public virtual initializer {
__WithMidasAccessControl_init(_accessControl);
- require(_minAnswer < _maxAnswer, "CA: !min/max");
+ _setMinMaxAnswer(_minAnswer, _maxAnswer);
+
require(
_maxAnswerDeviation <= 100 * (10**decimals()),
"CA: !max deviation"
);
- minAnswer = _minAnswer;
- maxAnswer = _maxAnswer;
maxAnswerDeviation = _maxAnswerDeviation;
description = _description;
}
+ /**
+ * @notice sets the min and max answer
+ * @dev the min and max answer are the minimum and maximum allowed values for the answer
+ * @param _minAnswer the new min answer
+ * @param _maxAnswer the new max answer
+ */
+ function setMinMaxAnswer(int192 _minAnswer, int192 _maxAnswer)
+ external
+ onlyContractAdmin
+ {
+ _setMinMaxAnswer(_minAnswer, _maxAnswer);
+ }
+
/**
* @notice works as `setRoundData()`, but also checks the
* deviation with the lattest submitted data, and that at least
@@ -125,10 +166,10 @@ contract CustomAggregatorV3CompatibleFeed is
/**
* @notice sets the data for `latestRound` + 1 round id
* @dev `_data` should be >= `minAnswer` and <= `maxAnswer`.
- * Function should be called only from address with `feedAdminRole()`
+ * Function should be called only from address with `contractAdminRole()`
* @param _data data value
*/
- function setRoundData(int256 _data) public onlyAggregatorAdmin {
+ function setRoundData(int256 _data) public onlyContractAdmin {
require(
_data >= minAnswer && _data <= maxAnswer,
"CA: out of [min;max]"
@@ -149,6 +190,23 @@ contract CustomAggregatorV3CompatibleFeed is
emit AnswerUpdated(_data, roundId, block.timestamp);
}
+ /**
+ * @notice sets the max answer deviation
+ * @dev the max answer deviation is the maximum allowed deviation from the latest price
+ * @param _maxAnswerDeviation the new max answer deviation
+ */
+ function setMaxAnswerDeviation(uint256 _maxAnswerDeviation)
+ external
+ onlyContractAdmin
+ {
+ require(
+ _maxAnswerDeviation <= 100 * (10**decimals()),
+ "CA: !max deviation"
+ );
+ maxAnswerDeviation = _maxAnswerDeviation;
+ emit MaxAnswerDeviationUpdated(_maxAnswerDeviation);
+ }
+
/**
* @inheritdoc AggregatorV3Interface
*/
@@ -212,11 +270,10 @@ contract CustomAggregatorV3CompatibleFeed is
}
/**
- * @dev describes a role, owner of which can update prices in this feed
- * @return role descriptor
+ * @inheritdoc WithMidasAccessControl
*/
- function feedAdminRole() public view virtual returns (bytes32) {
- return DEFAULT_ADMIN_ROLE;
+ function contractAdminRole() public view override returns (bytes32) {
+ return _CONTRACT_ADMIN_ROLE;
}
/**
@@ -242,4 +299,16 @@ contract CustomAggregatorV3CompatibleFeed is
deviation = deviation < 0 ? deviation * -1 : deviation;
return uint256(deviation);
}
+
+ /**
+ * @dev sets the min and max answer
+ * @param _minAnswer the new min answer
+ * @param _maxAnswer the new max answer
+ */
+ function _setMinMaxAnswer(int192 _minAnswer, int192 _maxAnswer) private {
+ require(_minAnswer < _maxAnswer, "CA: !min/max");
+ minAnswer = _minAnswer;
+ maxAnswer = _maxAnswer;
+ emit SetMinMaxAnswer(_minAnswer, _maxAnswer);
+ }
}
diff --git a/contracts/feeds/CustomAggregatorV3CompatibleFeedAdjusted.sol b/contracts/feeds/CustomAggregatorV3CompatibleFeedAdjusted.sol
index 604dbe26..ba9a5de3 100644
--- a/contracts/feeds/CustomAggregatorV3CompatibleFeedAdjusted.sol
+++ b/contracts/feeds/CustomAggregatorV3CompatibleFeedAdjusted.sol
@@ -1,7 +1,7 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
-import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
+import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
/**
* @title CustomAggregatorV3CompatibleFeedAdjusted
diff --git a/contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol b/contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol
index d95bd2d7..eee2e9ca 100644
--- a/contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol
+++ b/contracts/feeds/CustomAggregatorV3CompatibleFeedGrowth.sol
@@ -1,10 +1,11 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
-import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
+import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
-import "../access/WithMidasAccessControl.sol";
-import "../interfaces/IAggregatorV3CompatibleFeedGrowth.sol";
+import {MidasInitializable} from "../abstract/MidasInitializable.sol";
+import {WithMidasAccessControl} from "../access/WithMidasAccessControl.sol";
+import {IAggregatorV3CompatibleFeedGrowth} from "../interfaces/IAggregatorV3CompatibleFeedGrowth.sol";
/**
* @title CustomAggregatorV3CompatibleFeedGrowth
@@ -25,6 +26,13 @@ contract CustomAggregatorV3CompatibleFeedGrowth is
uint256 updatedAt;
}
+ /**
+ * @notice contract admin role
+ * @custom:oz-upgrades-unsafe-allow state-variable-immutable
+ */
+ // solhint-disable-next-line var-name-mixedcase
+ bytes32 private immutable _CONTRACT_ADMIN_ROLE;
+
/**
* @dev decimals of the aggregator
*/
@@ -88,11 +96,17 @@ contract CustomAggregatorV3CompatibleFeedGrowth is
uint256[50] private __gap;
/**
- * @dev checks that msg.sender do have a feedAdminRole() role
+ * @dev having a second gap here to match with the gap of previous implementations
*/
- modifier onlyAggregatorAdmin() {
- _onlyRole(feedAdminRole(), msg.sender);
- _;
+ uint256[50] private ___gap;
+
+ /**
+ * @notice constructor
+ * @param _contractAdminRole contract admin role
+ * @custom:oz-upgrades-unsafe-allow constructor
+ */
+ constructor(bytes32 _contractAdminRole) MidasInitializable() {
+ _CONTRACT_ADMIN_ROLE = _contractAdminRole;
}
/**
@@ -135,18 +149,28 @@ contract CustomAggregatorV3CompatibleFeedGrowth is
/**
* @inheritdoc IAggregatorV3CompatibleFeedGrowth
*/
- function setOnlyUp(bool _onlyUp) external override onlyAggregatorAdmin {
+ function setOnlyUp(bool _onlyUp) external override onlyContractAdmin {
onlyUp = _onlyUp;
emit OnlyUpUpdated(_onlyUp);
}
+ /**
+ * @inheritdoc IAggregatorV3CompatibleFeedGrowth
+ */
+ function setMinMaxAnswer(int192 _minAnswer, int192 _maxAnswer)
+ external
+ onlyContractAdmin
+ {
+ _setMinMaxAnswer(_minAnswer, _maxAnswer);
+ }
+
/**
* @inheritdoc IAggregatorV3CompatibleFeedGrowth
*/
function setMaxGrowthApr(int80 _maxGrowthApr)
external
override
- onlyAggregatorAdmin
+ onlyContractAdmin
{
require(_maxGrowthApr >= minGrowthApr, "CAG: !max growth");
maxGrowthApr = _maxGrowthApr;
@@ -159,13 +183,25 @@ contract CustomAggregatorV3CompatibleFeedGrowth is
function setMinGrowthApr(int80 _minGrowthApr)
external
override
- onlyAggregatorAdmin
+ onlyContractAdmin
{
require(_minGrowthApr <= maxGrowthApr, "CAG: !min growth");
minGrowthApr = _minGrowthApr;
emit MinGrowthAprUpdated(_minGrowthApr);
}
+ /**
+ * @inheritdoc IAggregatorV3CompatibleFeedGrowth
+ */
+ function setMaxAnswerDeviation(uint256 _maxAnswerDeviation)
+ external
+ onlyContractAdmin
+ {
+ require(_maxAnswerDeviation <= 100 * _ONE, "CAG: !max deviation");
+ maxAnswerDeviation = _maxAnswerDeviation;
+ emit MaxAnswerDeviationUpdated(_maxAnswerDeviation);
+ }
+
/**
* @inheritdoc IAggregatorV3CompatibleFeedGrowth
*/
@@ -211,7 +247,7 @@ contract CustomAggregatorV3CompatibleFeedGrowth is
int256 _data,
uint256 _dataTimestamp,
int80 _growthApr
- ) public onlyAggregatorAdmin {
+ ) public onlyContractAdmin {
require(
_data >= minAnswer && _data <= maxAnswer,
"CAG: out of [min;max]"
@@ -391,11 +427,10 @@ contract CustomAggregatorV3CompatibleFeedGrowth is
}
/**
- * @dev describes a role, owner of which can update prices in this feed
- * @return role descriptor
+ * @inheritdoc WithMidasAccessControl
*/
- function feedAdminRole() public view virtual returns (bytes32) {
- return DEFAULT_ADMIN_ROLE;
+ function contractAdminRole() public view override returns (bytes32) {
+ return _CONTRACT_ADMIN_ROLE;
}
/**
@@ -461,4 +496,16 @@ contract CustomAggregatorV3CompatibleFeedGrowth is
deviation = deviation < 0 ? deviation * -1 : deviation;
return uint256(deviation);
}
+
+ /**
+ * @dev sets the min and max answer
+ * @param _minAnswer the new min answer
+ * @param _maxAnswer the new max answer
+ */
+ function _setMinMaxAnswer(int192 _minAnswer, int192 _maxAnswer) private {
+ require(_minAnswer < _maxAnswer, "CA: !min/max");
+ minAnswer = _minAnswer;
+ maxAnswer = _maxAnswer;
+ emit SetMinMaxAnswer(_minAnswer, _maxAnswer);
+ }
}
diff --git a/contracts/feeds/DataFeed.sol b/contracts/feeds/DataFeed.sol
index 48ce93b4..ca3cf7f1 100644
--- a/contracts/feeds/DataFeed.sol
+++ b/contracts/feeds/DataFeed.sol
@@ -1,12 +1,12 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
-import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PausableUpgradeable.sol";
-import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
+import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
-import "../access/WithMidasAccessControl.sol";
-import "../libraries/DecimalsCorrectionLibrary.sol";
-import "../interfaces/IDataFeed.sol";
+import {MidasInitializable} from "../abstract/MidasInitializable.sol";
+import {WithMidasAccessControl} from "../access/WithMidasAccessControl.sol";
+import {DecimalsCorrectionLibrary} from "../libraries/DecimalsCorrectionLibrary.sol";
+import {IDataFeed} from "../interfaces/IDataFeed.sol";
/**
* @title DataFeed
@@ -16,6 +16,32 @@ import "../interfaces/IDataFeed.sol";
contract DataFeed is WithMidasAccessControl, IDataFeed {
using DecimalsCorrectionLibrary for uint256;
+ /**
+ * @param _aggregator new AggregatorV3Interface contract address
+ */
+ event ChangeAggregator(address indexed _aggregator);
+
+ /**
+ * @param _healthyDiff new healthy diff value
+ */
+ event SetHealthyDiff(uint256 indexed _healthyDiff);
+
+ /**
+ * @param _maxExpectedAnswer new max expected answer
+ * @param _minExpectedAnswer new min expected answer
+ */
+ event SetMinMaxExpectedAnswer(
+ int256 indexed _maxExpectedAnswer,
+ int256 indexed _minExpectedAnswer
+ );
+
+ /**
+ * @notice contract admin role
+ * @custom:oz-upgrades-unsafe-allow state-variable-immutable
+ */
+ // solhint-disable-next-line var-name-mixedcase
+ bytes32 private immutable _CONTRACT_ADMIN_ROLE;
+
/**
* @notice AggregatorV3Interface contract address
*/
@@ -41,6 +67,20 @@ contract DataFeed is WithMidasAccessControl, IDataFeed {
*/
uint256[50] private __gap;
+ /**
+ * @dev having a second gap here to match with the gap of previous implementations
+ */
+ uint256[50] private ___gap;
+
+ /**
+ * @notice constructor
+ * @param _contractAdminRole contract admin role
+ * @custom:oz-upgrades-unsafe-allow constructor
+ */
+ constructor(bytes32 _contractAdminRole) MidasInitializable() {
+ _CONTRACT_ADMIN_ROLE = _contractAdminRole;
+ }
+
/**
* @notice upgradeable pattern contract`s initializer
* @param _ac MidasAccessControl contract address
@@ -56,95 +96,85 @@ contract DataFeed is WithMidasAccessControl, IDataFeed {
int256 _minExpectedAnswer,
int256 _maxExpectedAnswer
) external initializer {
+ __WithMidasAccessControl_init(_ac);
+ _setMinMaxExpectedAnswer(_maxExpectedAnswer, _minExpectedAnswer);
+
require(_aggregator != address(0), "DF: invalid address");
require(_healthyDiff > 0, "DF: invalid diff");
- require(_minExpectedAnswer > 0, "DF: invalid min exp. price");
- require(_maxExpectedAnswer > 0, "DF: invalid max exp. price");
- require(
- _maxExpectedAnswer > _minExpectedAnswer,
- "DF: invalid exp. prices"
- );
- __WithMidasAccessControl_init(_ac);
aggregator = AggregatorV3Interface(_aggregator);
healthyDiff = _healthyDiff;
- minExpectedAnswer = _minExpectedAnswer;
- maxExpectedAnswer = _maxExpectedAnswer;
}
/**
* @notice updates `aggregator` address
* @param _aggregator new AggregatorV3Interface contract address
*/
- function changeAggregator(address _aggregator)
- external
- onlyRole(feedAdminRole(), msg.sender)
- {
+ function changeAggregator(address _aggregator) external onlyContractAdmin {
require(_aggregator != address(0), "DF: invalid address");
aggregator = AggregatorV3Interface(_aggregator);
+ emit ChangeAggregator(_aggregator);
}
/**
* @dev updates `healthyDiff` value
* @param _healthyDiff new value
*/
- function setHealthyDiff(uint256 _healthyDiff)
- external
- onlyRole(feedAdminRole(), msg.sender)
- {
+ function setHealthyDiff(uint256 _healthyDiff) external onlyContractAdmin {
require(_healthyDiff > 0, "DF: invalid diff");
healthyDiff = _healthyDiff;
+ emit SetHealthyDiff(_healthyDiff);
}
/**
- * @dev updates `minExpectedAnswer` value
- * @param _minExpectedAnswer min value
+ * @notice updates `minExpectedAnswer` and `maxExpectedAnswer` values
+ * @param _maxExpectedAnswer new max expected answer
+ * @param _minExpectedAnswer new min expected answer
*/
- function setMinExpectedAnswer(int256 _minExpectedAnswer)
- external
- onlyRole(feedAdminRole(), msg.sender)
- {
- require(_minExpectedAnswer > 0, "DF: invalid min exp. price");
- require(
- maxExpectedAnswer > _minExpectedAnswer,
- "DF: invalid exp. prices"
- );
-
- minExpectedAnswer = _minExpectedAnswer;
+ function setMinMaxExpectedAnswer(
+ int256 _maxExpectedAnswer,
+ int256 _minExpectedAnswer
+ ) external onlyContractAdmin {
+ _setMinMaxExpectedAnswer(_maxExpectedAnswer, _minExpectedAnswer);
}
/**
- * @dev updates `maxExpectedAnswer` value
- * @param _maxExpectedAnswer max value
+ * @inheritdoc IDataFeed
*/
- function setMaxExpectedAnswer(int256 _maxExpectedAnswer)
- external
- onlyRole(feedAdminRole(), msg.sender)
- {
- require(_maxExpectedAnswer > 0, "DF: invalid max exp. price");
- require(
- _maxExpectedAnswer > minExpectedAnswer,
- "DF: invalid exp. prices"
- );
-
- maxExpectedAnswer = _maxExpectedAnswer;
+ function getDataInBase18() external view returns (uint256 answer) {
+ (, answer) = _getDataInBase18();
}
/**
- * @inheritdoc IDataFeed
+ * @inheritdoc WithMidasAccessControl
*/
- function getDataInBase18() external view returns (uint256 answer) {
- (, answer) = _getDataInBase18();
+ function contractAdminRole() public view override returns (bytes32) {
+ return _CONTRACT_ADMIN_ROLE;
}
/**
- * @inheritdoc IDataFeed
+ * @dev sets the min and max expected answer
+ * @param _maxExpectedAnswer the new max expected answer
+ * @param _minExpectedAnswer the new min expected answer
*/
- function feedAdminRole() public pure virtual override returns (bytes32) {
- return DEFAULT_ADMIN_ROLE;
+ function _setMinMaxExpectedAnswer(
+ int256 _maxExpectedAnswer,
+ int256 _minExpectedAnswer
+ ) private {
+ require(_maxExpectedAnswer > 0, "DF: invalid max exp. price");
+ require(_minExpectedAnswer > 0, "DF: invalid min exp. price");
+ require(
+ _maxExpectedAnswer >= _minExpectedAnswer,
+ "DF: invalid exp. prices"
+ );
+
+ maxExpectedAnswer = _maxExpectedAnswer;
+ minExpectedAnswer = _minExpectedAnswer;
+
+ emit SetMinMaxExpectedAnswer(_maxExpectedAnswer, _minExpectedAnswer);
}
/**
diff --git a/contracts/interfaces/IAggregatorV3CompatibleFeedGrowth.sol b/contracts/interfaces/IAggregatorV3CompatibleFeedGrowth.sol
index 874ab7d5..55123194 100644
--- a/contracts/interfaces/IAggregatorV3CompatibleFeedGrowth.sol
+++ b/contracts/interfaces/IAggregatorV3CompatibleFeedGrowth.sol
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+pragma solidity 0.8.34;
-import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
+import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
/**
* @title IAggregatorV3CompatibleFeedGrowth
@@ -23,6 +23,11 @@ interface IAggregatorV3CompatibleFeedGrowth is AggregatorV3Interface {
int80 growthApr
);
+ /**
+ * @param maxAnswerDeviation the new max answer deviation
+ */
+ event MaxAnswerDeviationUpdated(uint256 indexed maxAnswerDeviation);
+
/**
* @notice emitted when max growth apr is updated
*
@@ -44,6 +49,12 @@ interface IAggregatorV3CompatibleFeedGrowth is AggregatorV3Interface {
*/
event OnlyUpUpdated(bool newOnlyUp);
+ /**
+ * @param minAnswer the new min answer
+ * @param maxAnswer the new max answer
+ */
+ event SetMinMaxAnswer(int192 indexed minAnswer, int192 indexed maxAnswer);
+
/**
* @notice updates onlyUp flag
*
@@ -65,6 +76,20 @@ interface IAggregatorV3CompatibleFeedGrowth is AggregatorV3Interface {
*/
function setMinGrowthApr(int80 _minGrowthApr) external;
+ /**
+ * @notice sets the max answer deviation
+ * @dev the max answer deviation is the maximum allowed deviation from the latest price
+ * @param _maxAnswerDeviation the new max answer deviation in %
+ */
+ function setMaxAnswerDeviation(uint256 _maxAnswerDeviation) external;
+
+ /**
+ * @notice sets the min and max answer
+ * @param _minAnswer the new min answer
+ * @param _maxAnswer the new max answer
+ */
+ function setMinMaxAnswer(int192 _minAnswer, int192 _maxAnswer) external;
+
/**
* @notice works as `setRoundData()`, but also checks the
* deviation with the lattest submitted data
diff --git a/contracts/interfaces/IDataFeed.sol b/contracts/interfaces/IDataFeed.sol
index 4edc4381..77e1a4fc 100644
--- a/contracts/interfaces/IDataFeed.sol
+++ b/contracts/interfaces/IDataFeed.sol
@@ -1,11 +1,7 @@
// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.9;
+pragma solidity 0.8.34;
-import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PausableUpgradeable.sol";
-import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
-
-import "../access/WithMidasAccessControl.sol";
-import "../libraries/DecimalsCorrectionLibrary.sol";
+import {DecimalsCorrectionLibrary} from "../libraries/DecimalsCorrectionLibrary.sol";
/**
* @title IDataFeed
@@ -18,10 +14,4 @@ interface IDataFeed {
* @return answer fetched aggregator answer
*/
function getDataInBase18() external view returns (uint256 answer);
-
- /**
- * @dev describes a role, owner of which can manage this feed
- * @return role descriptor
- */
- function feedAdminRole() external view returns (bytes32);
}
diff --git a/contracts/interfaces/IDepositVault.sol b/contracts/interfaces/IDepositVault.sol
index 309687c2..94aeccc4 100644
--- a/contracts/interfaces/IDepositVault.sol
+++ b/contracts/interfaces/IDepositVault.sol
@@ -1,24 +1,40 @@
// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.9;
+pragma solidity 0.8.34;
-import "./IManageableVault.sol";
+import {IManageableVault, RequestStatus} from "./IManageableVault.sol";
/**
* @notice Mint request scruct
- * @param sender user address who create
- * @param tokenIn tokenIn address
- * @param status request status
- * @param depositedUsdAmount amout USD, tokenIn -> USD
- * @param usdAmountWithoutFees amout USD, tokenIn - fees -> USD
- * @param tokenOutRate rate of mToken at request creation time
*/
struct Request {
- address sender;
+ /// @notice address who will receive the mTokens
+ address recipient;
+ /// @notice tokenIn address
address tokenIn;
+ /// @notice request status
RequestStatus status;
+ /// @notice amout USD, tokenIn -> USD
uint256 depositedUsdAmount;
+ /// @notice amout USD, tokenIn - fees -> USD
uint256 usdAmountWithoutFees;
+ /// @notice rate of mToken at request creation time
uint256 tokenOutRate;
+ /// @notice amount of tokenIn that was deposited instantly in USD
+ uint256 depositedInstantUsdAmount;
+ /// @notice approved tokenOut rate
+ uint256 approvedTokenOutRate;
+ /// @notice amount of mToken that was minted
+ uint256 amountMToken;
+}
+
+/**
+ * @notice Deposit vault init params
+ */
+struct DepositVaultInitParams {
+ /// @notice minimal USD amount for first user`s deposit
+ uint256 minMTokenAmountForFirstDeposit;
+ /// @notice max amount per request in mToken
+ uint256 maxAmountPerRequest;
}
/**
@@ -27,144 +43,120 @@ struct Request {
*/
interface IDepositVault is IManageableVault {
/**
- * @param caller function caller (msg.sender)
* @param newValue new min amount to deposit value
*/
- event SetMinMTokenAmountForFirstDeposit(
- address indexed caller,
- uint256 newValue
- );
-
- /**
- * @param caller function caller (msg.sender)
- * @param newValue new max supply cap value
- */
- event SetMaxSupplyCap(address indexed caller, uint256 newValue);
+ event SetMinMTokenAmountForFirstDeposit(uint256 newValue);
/**
- * @param user function caller (msg.sender)
- * @param tokenIn address of tokenIn
- * @param amountUsd amount of tokenIn converted to USD
- * @param amountToken amount of tokenIn
- * @param fee fee amount in tokenIn
- * @param minted amount of minted mTokens
- * @param referrerId referrer id
+ * @param newValue new max amount per request
*/
- event DepositInstant(
- address indexed user,
- address indexed tokenIn,
- uint256 amountUsd,
- uint256 amountToken,
- uint256 fee,
- uint256 minted,
- bytes32 referrerId
- );
+ event SetMaxAmountPerRequest(uint256 newValue);
/**
* @param user function caller (msg.sender)
* @param tokenIn address of tokenIn
* @param recipient address that receives the mTokens
- * @param amountUsd amount of tokenIn converted to USD
- * @param amountToken amount of tokenIn
- * @param fee fee amount in tokenIn
- * @param minted amount of minted mTokens
+ * @param amountTokenIn amount of tokenIn
+ * @param feeAmount fee amount in tokenIn
+ * @param amountMToken amount of minted mTokens
+ * @param mTokenRate mToken rate
+ * @param tokenInRate tokenIn rate
* @param referrerId referrer id
*/
- event DepositInstantWithCustomRecipient(
+ event DepositInstant(
address indexed user,
address indexed tokenIn,
- address recipient,
- uint256 amountUsd,
- uint256 amountToken,
- uint256 fee,
- uint256 minted,
+ address indexed recipient,
+ uint256 amountTokenIn,
+ uint256 feeAmount,
+ uint256 amountMToken,
+ uint256 mTokenRate,
+ uint256 tokenInRate,
bytes32 referrerId
);
/**
* @param requestId mint request id
* @param user function caller (msg.sender)
+ * @param recipient address that receives the mTokens
* @param tokenIn address of tokenIn
- * @param amountToken amount of tokenIn
- * @param amountUsd amount of tokenIn converted to USD
- * @param fee fee amount in tokenIn
- * @param tokenOutRate mToken rate
+ * @param amountTokenIn amount of tokenIn
+ * @param amountTokenInInstant amount of tokenIn that was deposited instantly
+ * @param feeAmount fee amount in tokenIn
+ * @param mTokenRate mToken rate
+ * @param tokenInRate tokenIn rate
* @param referrerId referrer id
*/
event DepositRequest(
uint256 indexed requestId,
address indexed user,
address indexed tokenIn,
- uint256 amountToken,
- uint256 amountUsd,
- uint256 fee,
- uint256 tokenOutRate,
+ address recipient,
+ uint256 amountTokenIn,
+ uint256 amountTokenInInstant,
+ uint256 feeAmount,
+ uint256 mTokenRate,
+ uint256 tokenInRate,
bytes32 referrerId
);
/**
* @param requestId mint request id
- * @param user function caller (msg.sender)
- * @param recipient address that receives the mTokens
- * @param tokenIn address of tokenIn
- * @param amountToken amount of tokenIn
- * @param amountUsd amount of tokenIn converted to USD
- * @param fee fee amount in tokenIn
- * @param tokenOutRate mToken rate
- * @param referrerId referrer id
+ * @param newOutRate mToken rate inputted by admin
+ * @param isSafe if true, approval is safe
+ * @param isAvgRate if true, newOutRate is avg rate
*/
- event DepositRequestWithCustomRecipient(
+ event ApproveRequest(
uint256 indexed requestId,
- address indexed user,
- address indexed tokenIn,
- address recipient,
- uint256 amountToken,
- uint256 amountUsd,
- uint256 fee,
- uint256 tokenOutRate,
- bytes32 referrerId
+ uint256 newOutRate,
+ bool isSafe,
+ bool isAvgRate
);
/**
* @param requestId mint request id
- * @param newOutRate mToken rate inputted by admin
*/
- event ApproveRequest(uint256 indexed requestId, uint256 newOutRate);
+ event RejectRequest(uint256 indexed requestId);
/**
- * @param requestId mint request id
- * @param newOutRate mToken rate inputted by admin
+ * @notice first deposit mint amount is below minimum
+ * @param amountMTokenWithoutFee mint amount after fee (decimals 18)
+ * @param minAmount minimum first deposit mint amount
*/
- event SafeApproveRequest(uint256 indexed requestId, uint256 newOutRate);
+ error LessThanMinAmountFirstDeposit(
+ uint256 amountMTokenWithoutFee,
+ uint256 minAmount
+ );
/**
- * @param requestId mint request id
- * @param user address of user
+ * @notice when token supply cap is exceeded
*/
- event RejectRequest(uint256 indexed requestId, address indexed user);
+ error SupplyCapExceeded();
/**
- * @param user address that was freed from min deposit check
+ * @notice when max amount per request is exceeded
+ * @param estimatedMintAmount estimated mint amount
*/
- event FreeFromMinDeposit(address indexed user);
+ error MaxAmountPerRequestExceeded(uint256 estimatedMintAmount);
/**
* @notice depositing proccess with auto mint if
* account fit daily limit and token allowance.
* Transfers token from the user.
- * Transfers fee in tokenIn to feeReceiver.
+ * Transfers fee in tokenIn to tokensReceiver.
* Mints mToken to user.
* @param tokenIn address of tokenIn
* @param amountToken amount of `tokenIn` that will be taken from user (decimals 18)
* @param minReceiveAmount minimum expected amount of mToken to receive (decimals 18)
* @param referrerId referrer id
+ * @return mintAmount amount of mToken that was minted
*/
function depositInstant(
address tokenIn,
uint256 amountToken,
uint256 minReceiveAmount,
bytes32 referrerId
- ) external;
+ ) external returns (uint256);
/**
* @notice Does the same as original `depositInstant` but allows specifying a custom tokensReceiver address.
@@ -173,6 +165,7 @@ interface IDepositVault is IManageableVault {
* @param minReceiveAmount minimum expected amount of mToken to receive (decimals 18)
* @param referrerId referrer id
* @param tokensReceiver address to receive the tokens (instead of msg.sender)
+ * @return mintAmount amount of mToken that was minted
*/
function depositInstant(
address tokenIn,
@@ -180,13 +173,13 @@ interface IDepositVault is IManageableVault {
uint256 minReceiveAmount,
bytes32 referrerId,
address tokensReceiver
- ) external;
+ ) external returns (uint256);
/**
* @notice depositing proccess with mint request creating if
* account fit token allowance.
* Transfers token from the user.
- * Transfers fee in tokenIn to feeReceiver.
+ * Transfers fee in tokenIn to tokensReceiver.
* Creates mint request.
* @param tokenIn address of tokenIn
* @param amountToken amount of `tokenIn` that will be taken from user (decimals 18)
@@ -200,24 +193,31 @@ interface IDepositVault is IManageableVault {
) external returns (uint256);
/**
- * @notice Does the same as original `depositRequest` but allows specifying a custom tokensReceiver address.
+ * @notice Instantly deposits `instantShare` amount of `amountMTokenIn` and creates a request for the remaining amount.
* @param tokenIn address of tokenIn
* @param amountToken amount of `tokenIn` that will be taken from user (decimals 18)
* @param referrerId referrer id
- * @param recipient address that receives the mTokens
+ * @param recipientRequest address that receives the mTokens for the request part
+ * @param instantShare % amount of `amountToken` that will be deposited instantly
+ * @param minReceiveAmountInstantShare min receive amount for the instant share
+ * @param recipientInstant address that receives the mTokens for the instant part
* @return request id
+ * @return instantMintAmount amount of mToken that was minted instantly
*/
function depositRequest(
address tokenIn,
uint256 amountToken,
bytes32 referrerId,
- address recipient
- ) external returns (uint256);
+ address recipientRequest,
+ uint256 instantShare,
+ uint256 minReceiveAmountInstantShare,
+ address recipientInstant
+ ) external returns (uint256, uint256);
/**
* @notice approving requests from the `requestIds` array
* with the mToken rate from the request.
- * Does same validation as `safeApproveRequest`.
+ * Validates that new mToken rate does not exceed variation tolerance
* Mints mToken to request users.
* Sets request flags to Processed.
* @param requestIds request ids array
@@ -228,16 +228,27 @@ interface IDepositVault is IManageableVault {
/**
* @notice approving requests from the `requestIds` array
* with the current mToken rate.
- * Does same validation as `safeApproveRequest`.
+ * Validates that new mToken rate does not exceed variation tolerance
* Mints mToken to request users.
* Sets request flags to Processed.
* @param requestIds request ids array
*/
function safeBulkApproveRequest(uint256[] calldata requestIds) external;
+ /**
+ * @notice approving requests from the `requestIds` array
+ * with the current mToken rate.
+ * Validates that new mToken rate does not exceed variation tolerance
+ * Mints mToken to request users.
+ * Sets request flags to Processed.
+ * @param requestIds request ids array
+ */
+ function safeBulkApproveRequestAvgRate(uint256[] calldata requestIds)
+ external;
+
/**
* @notice approving requests from the `requestIds` array using the `newOutRate`.
- * Does same validation as `safeApproveRequest`.
+ * Validates that new mToken rate does not exceed variation tolerance
* Mints mToken to request users.
* Sets request flags to Processed.
* @param requestIds request ids array
@@ -249,13 +260,17 @@ interface IDepositVault is IManageableVault {
) external;
/**
- * @notice approving request if inputted token rate fit price deviation percent
- * Mints mToken to user.
- * Sets request flag to Processed.
- * @param requestId request id
- * @param newOutRate mToken rate inputted by vault admin
+ * @notice approving requests from the `requestIds` array using the `newOutRate`.
+ * Validates that new mToken rate does not exceed variation tolerance
+ * Mints mToken to request users.
+ * Sets request flags to Processed.
+ * @param requestIds request ids array
+ * @param avgMTokenRate avg mToken rate inputted by vault admin
*/
- function safeApproveRequest(uint256 requestId, uint256 newOutRate) external;
+ function safeBulkApproveRequestAvgRate(
+ uint256[] calldata requestIds,
+ uint256 avgMTokenRate
+ ) external;
/**
* @notice approving request without price deviation check
@@ -263,8 +278,13 @@ interface IDepositVault is IManageableVault {
* Sets request flag to Processed.
* @param requestId request id
* @param newOutRate mToken rate inputted by vault admin
+ * @param isAvgRate if true, newOutRate is avg rate
*/
- function approveRequest(uint256 requestId, uint256 newOutRate) external;
+ function approveRequest(
+ uint256 requestId,
+ uint256 newOutRate,
+ bool isAvgRate
+ ) external;
/**
* @notice rejecting request
@@ -281,9 +301,9 @@ interface IDepositVault is IManageableVault {
function setMinMTokenAmountForFirstDeposit(uint256 newValue) external;
/**
- * @notice sets new max supply cap value
+ * @notice sets new max amount per request
* can be called only from vault`s admin
- * @param newValue new max supply cap value
+ * @param newValue new max amount per request
*/
- function setMaxSupplyCap(uint256 newValue) external;
+ function setMaxAmountPerRequest(uint256 newValue) external;
}
diff --git a/contracts/interfaces/IMToken.sol b/contracts/interfaces/IMToken.sol
index 5bc3bbd8..2481d637 100644
--- a/contracts/interfaces/IMToken.sol
+++ b/contracts/interfaces/IMToken.sol
@@ -1,16 +1,76 @@
// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.9;
+pragma solidity 0.8.34;
-import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
+import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
/**
* @title IMToken
* @author RedDuck Software
*/
interface IMToken is IERC20Upgradeable {
+ /**
+ * @param clawbackReceiver address to which clawback tokens will be sent
+ */
+ event ClawbackReceiverSet(address indexed clawbackReceiver);
+
+ /**
+ * @param name new name
+ * @param symbol new symbol
+ */
+ event SetNameSymbol(string indexed name, string indexed symbol);
+
+ /**
+ * @param isPermissioned if true then the token is permissioned
+ */
+ event SetIsPermissioned(bool indexed isPermissioned);
+
+ /**
+ * @param isMinHoldingBalanceEnforced if true then the token has a minimum holding balance enforced
+ */
+ event SetIsMinHoldingBalanceEnforced(
+ bool indexed isMinHoldingBalanceEnforced
+ );
+
+ /**
+ * @param key metadata key
+ * @param data metadata data
+ */
+ event SetMetadata(bytes32 indexed key, bytes data);
+
+ /**
+ * @param from address to clawback tokens from
+ * @param to address to clawback tokens to
+ * @param amount amount to clawback
+ */
+ event Clawback(address indexed from, address indexed to, uint256 amount);
+
+ /**
+ * @param maxSupplyCap new maximum supply cap
+ */
+ event SetMaxSupplyCap(uint256 maxSupplyCap);
+
+ /**
+ * @notice when new limit is invalid
+ * @param newLimit new limit
+ * @param existingLimit existing limit
+ */
+ error InvalidNewLimit(uint256 newLimit, uint256 existingLimit);
+
+ /**
+ * @notice when the balance is not met
+ * @param balance balance
+ */
+ error MinBalanceNotMet(uint256 balance);
+
+ /**
+ * @notice when the maximum supply cap is exceeded
+ */
+ error MaxSupplyCapExceeded();
+
/**
* @notice mints mToken token `amount` to a given `to` address.
* should be called only from permissioned actor
+ * bypasses the timelock entirely
* @param to addres to mint tokens to
* @param amount amount to mint
*/
@@ -19,20 +79,57 @@ interface IMToken is IERC20Upgradeable {
/**
* @notice burns mToken token `amount` from a given `from` address.
* should be called only from permissioned actor
+ * bypasses the timelock entirely
* @param from addres to burn tokens from
* @param amount amount to burn
*/
function burn(address from, uint256 amount) external;
+ /**
+ * @notice mints mToken token `amount` to a given `to` address,
+ * requires the timelock to pass
+ * should be called only from permissioned actor
+ * @param to address to mint tokens to
+ * @param amount amount to mint
+ */
+ function mintGoverned(address to, uint256 amount) external;
+
/**
* @notice burns mToken token `amount` from a given `from` address,
* bypassing blacklist checks.
+ * requires the timelock to pass
* should be called only from permissioned actor
* @param from address to burn tokens from
* @param amount amount to burn
*/
function burnGoverned(address from, uint256 amount) external;
+ /**
+ * @notice claws back tokens from a given address
+ * @param amount amount to clawback
+ * @param from address to clawback tokens from
+ */
+ function clawback(uint256 amount, address from) external;
+
+ /**
+ * @notice sets the address to which clawback tokens will be sent
+ * @param clawbackReceiver address to which clawback tokens will be sent
+ */
+ function setClawbackReceiver(address clawbackReceiver) external;
+
+ /**
+ * @notice sets the maximum supply cap for the token
+ * @param maxSupplyCap new maximum supply cap
+ */
+ function setMaxSupplyCap(uint256 maxSupplyCap) external;
+
+ /**
+ * @notice sets the name and symbol of the token
+ * @param name_ new name
+ * @param symbol_ new symbol
+ */
+ function setNameSymbol(string memory name_, string memory symbol_) external;
+
/**
* @notice updates contract`s metadata.
* should be called only from permissioned actor
@@ -42,14 +139,53 @@ interface IMToken is IERC20Upgradeable {
function setMetadata(bytes32 key, bytes memory data) external;
/**
- * @notice puts mToken token on pause.
- * should be called only from permissioned actor
+ * @notice increases mint rate limit for a given window
+ * @param window window duration in seconds
+ * @param newLimit limit amount per window
*/
- function pause() external;
+ function increaseMintRateLimit(uint256 window, uint256 newLimit) external;
/**
- * @notice puts mToken token on pause.
- * should be called only from permissioned actor
+ * @notice decreases mint rate limit for a given window
+ * @param window window duration in seconds
+ * @param newLimit limit amount per window
+ */
+ function decreaseMintRateLimit(uint256 window, uint256 newLimit) external;
+
+ /**
+ * @notice removes mint rate limit config for a given window
+ * @param window window duration in seconds
+ */
+ function removeMintRateLimitConfig(uint256 window) external;
+
+ /**
+ * @notice sets the permissioned status of the token
+ * @param isPermissioned if true then the token is permissioned
+ */
+ function setIsPermissioned(bool isPermissioned) external;
+
+ /**
+ * @notice sets the min holding balance enforced status of the token
+ * @param isMinHoldingBalanceEnforced if true then the token has a minimum holding balance enforced
+ */
+ function setMinHoldingBalanceEnforced(bool isMinHoldingBalanceEnforced)
+ external;
+
+ /**
+ * @notice role that grants min balance exempt rights to the contract
+ * @return role bytes32 role
+ */
+ function minBalanceExemptRole() external view returns (bytes32);
+
+ /**
+ * @notice sets the role that grants greenlisted rights to the contract
+ * @return role bytes32 role
+ */
+ function greenlistedRole() external view returns (bytes32);
+
+ /**
+ * @notice returns the maximum supply cap for the token
+ * @return maxSupplyCap maximum supply cap
*/
- function unpause() external;
+ function maxSupplyCap() external view returns (uint256);
}
diff --git a/contracts/interfaces/IManageableVault.sol b/contracts/interfaces/IManageableVault.sol
index ce174d21..8dfdcae4 100644
--- a/contracts/interfaces/IManageableVault.sol
+++ b/contracts/interfaces/IManageableVault.sol
@@ -1,18 +1,20 @@
// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.9;
+pragma solidity 0.8.34;
-import "./IMToken.sol";
-import "./IDataFeed.sol";
+import {IMToken} from "./IMToken.sol";
+import {IDataFeed} from "./IDataFeed.sol";
/**
- * @param dataFeed data feed token/USD address
- * @param fee fee by token, 1% = 100
- * @param allowance token allowance (decimals 18)
+ * @notice Payment token config
*/
struct TokenConfig {
+ /// @notice data feed token/USD address
address dataFeed;
+ /// @notice fee by token, 1% = 100
uint256 fee;
+ /// @notice token allowance (decimals 18)
uint256 allowance;
+ /// @notice stablecoin flag
bool stable;
}
@@ -22,17 +24,36 @@ enum RequestStatus {
Canceled
}
-struct MTokenInitParams {
+/**
+ * @notice Common vault init params
+ */
+struct CommonVaultInitParams {
+ /// @notice variation tolerance of mToken rates for "safe" requests approve
+ uint256 variationTolerance;
+ /// @notice minimum amount for operations in mToken
+ uint256 minAmount;
+ /// @notice fee for initial operations 1% = 100
+ uint256 instantFee;
+ /// @notice address of the access control contract
+ address ac;
+ /// @notice address of the sanctions list contract
+ address sanctionsList;
+ /// @notice mToken address
address mToken;
+ /// @notice mToken data feed address
address mTokenDataFeed;
-}
-struct ReceiversInitParams {
+ /// @notice address to which proceeds will be sent
address tokensReceiver;
- address feeReceiver;
-}
-struct InstantInitParams {
- uint256 instantFee;
- uint256 instantDailyLimit;
+ /// @notice minimum instant fee
+ uint256 minInstantFee;
+ /// @notice maximum instant fee
+ uint256 maxInstantFee;
+ /// @notice maximum instant share value in basis points (100 = 1%)
+ uint256 maxInstantShare;
+ /// @notice max requestId that can be approved
+ uint256 maxApproveRequestId;
+ /// @notice enforce sequential request processing flag
+ bool sequentialRequestProcessing;
}
/**
@@ -41,20 +62,17 @@ struct InstantInitParams {
*/
interface IManageableVault {
/**
- * @param caller function caller (msg.sender)
* @param token token that was withdrawn
* @param withdrawTo address to which tokens were withdrawn
* @param amount `token` transfer amount
*/
event WithdrawToken(
- address indexed caller,
address indexed token,
address indexed withdrawTo,
uint256 amount
);
/**
- * @param caller function caller (msg.sender)
* @param token address of token that
* @param dataFeed token dataFeed address
* @param fee fee 1% = 100
@@ -62,7 +80,6 @@ interface IManageableVault {
* @param stable stablecoin flag
*/
event AddPaymentToken(
- address indexed caller,
address indexed token,
address indexed dataFeed,
uint256 fee,
@@ -72,82 +89,65 @@ interface IManageableVault {
/**
* @param token address of token that
- * @param caller function caller (msg.sender)
* @param allowance new allowance
*/
- event ChangeTokenAllowance(
- address indexed token,
- address indexed caller,
- uint256 allowance
- );
+ event ChangeTokenAllowance(address indexed token, uint256 allowance);
/**
* @param token address of token that
- * @param caller function caller (msg.sender)
* @param fee new fee
*/
- event ChangeTokenFee(
- address indexed token,
- address indexed caller,
- uint256 fee
- );
+ event ChangeTokenFee(address indexed token, uint256 fee);
/**
* @param token address of token that
- * @param caller function caller (msg.sender)
*/
- event RemovePaymentToken(address indexed token, address indexed caller);
+ event RemovePaymentToken(address indexed token);
/**
* @param account address of account
- * @param caller function caller (msg.sender)
+ * @param enable is enabled
*/
- event AddWaivedFeeAccount(address indexed account, address indexed caller);
+ event SetWaivedFeeAccount(address indexed account, bool enable);
/**
- * @param account address of account
- * @param caller function caller (msg.sender)
+ * @param newFee new operation fee value
*/
- event RemoveWaivedFeeAccount(
- address indexed account,
- address indexed caller
- );
+ event SetInstantFee(uint256 newFee);
/**
- * @param caller function caller (msg.sender)
- * @param newFee new operation fee value
+ * @param newMinInstantFee new minimum instant fee
+ * @param newMaxInstantFee new maximum instant fee
*/
- event SetInstantFee(address indexed caller, uint256 newFee);
+ event SetMinMaxInstantFee(
+ uint256 newMinInstantFee,
+ uint256 newMaxInstantFee
+ );
/**
- * @param caller function caller (msg.sender)
* @param newAmount new min amount for operation
*/
- event SetMinAmount(address indexed caller, uint256 newAmount);
+ event SetMinAmount(uint256 newAmount);
/**
- * @param caller function caller (msg.sender)
- * @param newLimit new operation daily limit
+ * @param newMaxInstantShare new maximum instant share value in basis points (100 = 1%)
*/
- event SetInstantDailyLimit(address indexed caller, uint256 newLimit);
+ event SetMaxInstantShare(uint256 newMaxInstantShare);
/**
- * @param caller function caller (msg.sender)
* @param newTolerance percent of price diviation 1% = 100
*/
- event SetVariationTolerance(address indexed caller, uint256 newTolerance);
+ event SetVariationTolerance(uint256 newTolerance);
/**
- * @param caller function caller (msg.sender)
- * @param reciever new reciever address
+ * @param receiver new receiver address
*/
- event SetFeeReceiver(address indexed caller, address indexed reciever);
+ event SetTokensReceiver(address indexed receiver);
/**
- * @param caller function caller (msg.sender)
- * @param reciever new reciever address
+ * @param newMaxApproveRequestId new max requestId that can be approved
*/
- event SetTokensReceiver(address indexed caller, address indexed reciever);
+ event SetMaxApproveRequestId(uint256 newMaxApproveRequestId);
/**
* @param user user address
@@ -155,6 +155,153 @@ interface IManageableVault {
*/
event FreeFromMinAmount(address indexed user, bool enable);
+ /**
+ * @param enforce enforce sequential request processing flag
+ */
+ event SetSequentialRequestProcessing(bool enforce);
+
+ /**
+ * @notice Payment token is already added
+ * @param token token address
+ */
+ error PaymentTokenAlreadyAdded(address token);
+
+ /**
+ * @notice Payment token is not in the list
+ * @param token token address
+ */
+ error PaymentTokenNotExists(address token);
+
+ /**
+ * @notice Value is the same as the current one
+ * @param account account address
+ */
+ error SameAddressValue(address account);
+
+ /**
+ * @notice Min instant fee is greater than max instant fee
+ * @param minFee minimum instant fee
+ * @param maxFee maximum instant fee
+ */
+ error InvalidMinMaxInstantFee(uint256 minFee, uint256 maxFee);
+
+ /**
+ * @notice Amount does not match token decimals after rounding
+ * @param amount input amount
+ * @param requiredAmount amount after round-trip conversion
+ */
+ error InvalidRounding(uint256 amount, uint256 requiredAmount);
+
+ /**
+ * @notice Payment token is not supported
+ * @param token token address
+ */
+ error UnknownPaymentToken(address token);
+
+ /**
+ * @notice Operation amount exceeds token allowance
+ * @param prevAllowance current allowance
+ * @param amount requested amount
+ */
+ error AllowanceExceeded(uint256 prevAllowance, uint256 amount);
+
+ /**
+ * @notice Instant fee is outside min/max range
+ * @param instantFee current instant fee
+ */
+ error InstantFeeOutOfBounds(uint256 instantFee);
+
+ /**
+ * @notice Price change is too large
+ * @param difPercent actual price change percent (1% = 100)
+ * @param variationTolerance max allowed change percent (1% = 100)
+ */
+ error PriceVariationExceeded(
+ uint256 difPercent,
+ uint256 variationTolerance
+ );
+
+ /**
+ * @notice Fee is out of allowed range
+ * @param fee fee value (1% = 100)
+ */
+ error InvalidFee(uint256 fee);
+
+ /**
+ * @notice Token rate is zero or invalid
+ * @param tokenRate token rate value
+ */
+ error InvalidTokenRate(uint256 tokenRate);
+
+ /**
+ * @notice Received amount is below minimum
+ * @param minReceiveAmount minimum expected amount
+ * @param actualReceiveAmount actual received amount
+ */
+ error SlippageExceeded(
+ uint256 minReceiveAmount,
+ uint256 actualReceiveAmount
+ );
+
+ /**
+ * @notice Request id is above max approve id
+ * @param requestId request id
+ * @param maxApproveRequestId max request id that can be approved
+ */
+ error RequestIdTooHigh(uint256 requestId, uint256 maxApproveRequestId);
+
+ /**
+ * @notice New mToken rate must be greater than zero
+ */
+ error InvalidNewMTokenRate();
+
+ /**
+ * @notice Avg rate must be greater than zero
+ */
+ error InvalidAvgRate();
+
+ /**
+ * @notice Request does not exist
+ * @param requestId request id
+ */
+ error RequestNotExists(uint256 requestId);
+
+ /**
+ * @notice Request has wrong status
+ * @param requestId request id
+ * @param status current request status
+ */
+ error UnexpectedRequestStatus(uint256 requestId, RequestStatus status);
+
+ /**
+ * @notice Instant share is above max allowed
+ * @param instantShare instant share in basis points (100 = 1%)
+ * @param maxInstantShare max allowed instant share
+ */
+ error InstantShareTooHigh(uint256 instantShare, uint256 maxInstantShare);
+
+ /**
+ * @notice Amount must be greater than zero
+ */
+ error InvalidAmount();
+
+ /**
+ * @notice Amount is below minimum
+ * @param amount requested amount
+ * @param minAmount minimum allowed amount
+ */
+ error AmountLessThanMin(uint256 amount, uint256 minAmount);
+
+ /**
+ * @notice Request id is not the next expected one
+ * @param requestId request id
+ * @param nextExpectedRequestIdToProcess next request id to process
+ */
+ error InvalidRequestSequence(
+ uint256 requestId,
+ uint256 nextExpectedRequestIdToProcess
+ );
+
/**
* @notice The mTokenDataFeed contract address.
* @return The address of the mTokenDataFeed contract.
@@ -167,19 +314,6 @@ interface IManageableVault {
*/
function mToken() external view returns (IMToken);
- /**
- * @notice withdraws `amount` of a given `token` from the contract.
- * can be called only from permissioned actor.
- * @param token token address
- * @param amount token amount
- * @param withdrawTo withdraw destination address
- */
- function withdrawToken(
- address token,
- uint256 amount,
- address withdrawTo
- ) external;
-
/**
* @notice adds a token to the stablecoins list.
* can be called only from permissioned actor.
@@ -206,7 +340,7 @@ interface IManageableVault {
/**
* @notice set new token allowance.
- * if MAX_UINT = infinite allowance
+ * if type(uint256).max = infinite allowance
* prev allowance rewrites by new
* can be called only from permissioned actor.
* @param token token address
@@ -237,46 +371,63 @@ interface IManageableVault {
function setMinAmount(uint256 newAmount) external;
/**
- * @notice adds a account to waived fee restriction.
+ * @notice sets a account to waived fee restriction.
* can be called only from permissioned actor.
* @param account user address
+ * @param enable is enabled
*/
- function addWaivedFeeAccount(address account) external;
+ function setWaivedFeeAccount(address account, bool enable) external;
/**
- * @notice removes a account from waived fee restriction.
+ * @notice set new receiver for tokens.
* can be called only from permissioned actor.
- * @param account user address
+ * @param receiver new token receiver address
*/
- function removeWaivedFeeAccount(address account) external;
+ function setTokensReceiver(address receiver) external;
/**
- * @notice set new reciever for fees.
+ * @notice set operation fee percent.
* can be called only from permissioned actor.
- * @param reciever new fee reciever address
+ * @param newInstantFee new instant operations fee percent 1& = 100
*/
- function setFeeReceiver(address reciever) external;
+ function setInstantFee(uint256 newInstantFee) external;
/**
- * @notice set new reciever for tokens.
- * can be called only from permissioned actor.
- * @param reciever new token reciever address
+ * @notice set new minimum/maximum instant fee
+ * @param newMinInstantFee new minimum instant fee
+ * @param newMaxInstantFee new maximum instant fee
*/
- function setTokensReceiver(address reciever) external;
+ function setMinMaxInstantFee(
+ uint256 newMinInstantFee,
+ uint256 newMaxInstantFee
+ ) external;
/**
- * @notice set operation fee percent.
+ * @notice set operation limit configs.
* can be called only from permissioned actor.
- * @param newInstantFee new instant operations fee percent 1& = 100
+ * @param window window duration in seconds
+ * @param limit limit amount per window
*/
- function setInstantFee(uint256 newInstantFee) external;
+ function setInstantLimitConfig(uint256 window, uint256 limit) external;
+
+ /**
+ * @notice set maximum instant share value in basis points (100 = 1%)
+ * @param newMaxInstantShare new maximum instant share value in basis points (100 = 1%)
+ */
+ function setMaxInstantShare(uint256 newMaxInstantShare) external;
+
+ /**
+ * @notice sets max requestId that can be approved
+ * @param newMaxApproveRequestId new max requestId that can be approved
+ */
+ function setMaxApproveRequestId(uint256 newMaxApproveRequestId) external;
/**
- * @notice set operation daily limit.
+ * @notice remove operation limit config.
* can be called only from permissioned actor.
- * @param newInstantDailyLimit new operation daily limit (decimals 18)
+ * @param window window duration in seconds
*/
- function setInstantDailyLimit(uint256 newInstantDailyLimit) external;
+ function removeInstantLimitConfig(uint256 window) external;
/**
* @notice frees given `user` from the minimal deposit
@@ -284,4 +435,25 @@ interface IManageableVault {
* @param user address of user
*/
function freeFromMinAmount(address user, bool enable) external;
+
+ /**
+ * @notice set enforce sequential request processing flag
+ * @param enforce enforce sequential request processing flag
+ */
+ function setSequentialRequestProcessing(bool enforce) external;
+
+ /**
+ * @notice withdraws `amount` of a given `token` from the contract
+ * to the `tokensReceiver` address
+ * @param token token address
+ * @param amount token amount
+ */
+ function withdrawToken(address token, uint256 amount) external;
+
+ /**
+ * @notice check if the account is waived from fee restriction
+ * @param account account address
+ * @return true if the account is waived from fee restriction, false otherwise
+ */
+ function waivedFeeRestriction(address account) external view returns (bool);
}
diff --git a/contracts/interfaces/IMidasAccessControl.sol b/contracts/interfaces/IMidasAccessControl.sol
new file mode 100644
index 00000000..2caeb7cd
--- /dev/null
+++ b/contracts/interfaces/IMidasAccessControl.sol
@@ -0,0 +1,379 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.34;
+
+import {IAccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/IAccessControlUpgradeable.sol";
+
+interface IMidasAccessControl is IAccessControlUpgradeable {
+ /**
+ * @notice Set user facing role params
+ */
+ struct SetUserFacingRoleParams {
+ /// @notice OZ role for the scope
+ bytes32 role;
+ /// @notice whether that role is user facing
+ bool enabled;
+ }
+
+ /**
+ * @notice Set function access grant operator params
+ */
+ struct SetGrantOperatorRoleParams {
+ /// @notice delay value
+ uint32 delay;
+ /// @notice selector of the scoped function.
+ bytes4 functionSelector;
+ /// @notice address that may call `setFunctionPermission` for this scope.
+ address operator;
+ /// @notice grant or revoke grant-operator status.
+ bool enabled;
+ }
+
+ /**
+ * @notice Set function permission params
+ */
+ struct SetPermissionRoleParams {
+ /// @notice scoped function selector
+ bytes4 functionSelector;
+ /// @notice address receiving or losing permission
+ address account;
+ /// @notice delay value
+ uint32 delay;
+ /// @notice grant or revoke permission
+ bool enabled;
+ }
+
+ /**
+ * @notice Grant role params
+ */
+ struct GrantRoleMultParams {
+ /// @notice role to be granted
+ bytes32 role;
+ /// @notice account to be granted the role
+ address account;
+ /// @notice delay value
+ uint32 delay;
+ }
+
+ /**
+ * @notice Revoke role params
+ */
+ struct RevokeRoleMultParams {
+ /// @notice role to be revoked
+ bytes32 role;
+ /// @notice account to be revoked the role
+ address account;
+ }
+
+ /**
+ * @notice Set role delay params
+ */
+ struct SetRoleDelayParams {
+ /// @notice role to be set the delay for
+ bytes32 role;
+ /// @notice delay value
+ uint32 delay;
+ }
+
+ /**
+ * @param role OZ role for the scope
+ * @param enabled whether that role is user facing
+ */
+ event SetUserFacingRole(bytes32 indexed role, bool enabled);
+
+ /**
+ * @param masterRole OZ role for the scope
+ * @param targetContract contract whose function is scoped.
+ * @param functionSelector selector of the scoped function.
+ * @param operator address that may call `setFunctionPermission` for this scope.
+ * @param enabled grant or revoke grant-operator status.
+ */
+ event SetGrantOperatorRole(
+ bytes32 indexed masterRole,
+ address indexed targetContract,
+ address indexed operator,
+ bytes4 functionSelector,
+ bool enabled
+ );
+
+ /**
+ * @param masterRole OZ role for the scope
+ * @param targetContract contract whose function is scoped.
+ * @param account address receiving or losing permission
+ * @param functionSelector selector of the scoped function.
+ * @param enabled grant or revoke
+ */
+ event SetPermissionRole(
+ bytes32 indexed masterRole,
+ address indexed targetContract,
+ address indexed account,
+ bytes4 functionSelector,
+ bool enabled
+ );
+
+ /**
+ * @param defaultDelay new default delay
+ */
+ event SetDefaultDelay(uint32 defaultDelay);
+
+ /**
+ * @param params array of SetRoleDelayParams
+ */
+ event SetRoleDelays(SetRoleDelayParams[] params);
+
+ /**
+ * @param role role id
+ * @param delay delay value
+ */
+ event SetRoleDelay(bytes32 role, uint32 delay);
+
+ /**
+ * @notice when the array is empty
+ */
+ error EmptyArray();
+
+ /**
+ * @notice when the arrays have different lengths
+ * @param length1 length of the first array
+ * @param length2 length of the second array
+ */
+ error MismatchArrays(uint256 length1, uint256 length2);
+
+ /**
+ * @notice error when the function is forbidden
+ */
+ error Forbidden();
+
+ /**
+ * @notice when the role is being revoked from the self
+ * @param role role to be revoked
+ * @param account account to be revoked
+ */
+ error CannotRevokeFromSelf(bytes32 role, address account);
+
+ /**
+ * @notice when the delay is already set
+ */
+ error DelayIsAlreadySet();
+
+ /**
+ * @notice when the role admin mismatch
+ * @param role role to be revoked
+ * @param adminRole admin role
+ */
+ error RoleAdminMismatch(bytes32 role, bytes32 adminRole);
+
+ /**
+ * @notice when the function selector mismatch
+ * @param actualFunctionSelector actual function selector
+ * @param requiredFunctionSelector required function selector
+ */
+ error FunctionSelectorMismatch(
+ bytes4 actualFunctionSelector,
+ bytes4 requiredFunctionSelector
+ );
+
+ /**
+ * @notice Enable or disable which OZ role may administer function-access scopes for that role.
+ * @dev Only `DEFAULT_ADMIN_ROLE` can call this function.
+ * Prevents unrelated role admins from spamming access mappings.
+ * @param params array of SetUserFacingRoleParams
+ */
+ function setUserFacingRoleMult(SetUserFacingRoleParams[] calldata params)
+ external;
+
+ /**
+ * @notice Add or remove a grant operator for a specific contract function scope.
+ * @dev `targetContract` must implement `IMidasAccessControlManaged` interface;
+ * Caller must hold `contractAdminRole` of a target contract;
+ * @param targetContract scoped contract
+ * @param params array of SetGrantOperatorRoleParams
+ */
+ function setGrantOperatorRoleMult(
+ address targetContract,
+ SetGrantOperatorRoleParams[] calldata params
+ ) external;
+
+ /**
+ * @notice Grant or revoke function access for an account
+ * @dev caller must be a grant operator for the scope or have the master role
+ * target contract must implement `IMidasAccessControlManaged` interface;
+ * @param targetContract scoped contract
+ * @param params array of SetPermissionRoleParams
+ */
+ function setPermissionRoleMult(
+ address targetContract,
+ SetPermissionRoleParams[] calldata params
+ ) external;
+
+ /**
+ * @notice Grant or revoke function access for an account
+ * @dev caller must be a grant operator for the scope or have the master role
+ * @param masterRole OZ role for the scope
+ * @param targetContract scoped contract
+ * @param params array of SetPermissionRoleParams
+ */
+ function setPermissionRoleMult(
+ bytes32 masterRole,
+ address targetContract,
+ SetPermissionRoleParams[] calldata params
+ ) external;
+
+ /**
+ * @notice Grant a role to an account with a delay
+ * @param role role id
+ * @param account account to grant the role to
+ * @param delay delay value
+ */
+ function grantRole(
+ bytes32 role,
+ address account,
+ uint32 delay
+ ) external;
+
+ /**
+ * @notice grant multiple roles to multiple users in one transaction
+ * @param params array of GrantRoleMultParams
+ */
+ function grantRoleMult(GrantRoleMultParams[] calldata params) external;
+
+ /**
+ * @notice revoke multiple roles from multiple users in one transaction
+ * @param params array of RevokeRoleMultParams
+ */
+ function revokeRoleMult(RevokeRoleMultParams[] calldata params) external;
+
+ /**
+ * @notice Sets the default delay
+ * @param _defaultDelay default delay in seconds
+ */
+ function setDefaultDelay(uint32 _defaultDelay) external;
+
+ /**
+ * @notice Sets timelock delay per role
+ * @param params array of SetRoleDelayParams
+ */
+ function setRoleDelayMult(SetRoleDelayParams[] calldata params) external;
+
+ /**
+ * @notice set the admin role for a specific role
+ * @dev can be called only by the address that holds `DEFAULT_ADMIN_ROLE`
+ * @param role the role to set the admin role for
+ * @param newAdminRole the new admin role
+ */
+ function setRoleAdmin(bytes32 role, bytes32 newAdminRole) external;
+
+ /**
+ * @notice Whether `role` is user facing.
+ * @param role OZ role for the scope
+ * @return enabled whether `role` is user facing
+ */
+ function isUserFacingRole(bytes32 role) external view returns (bool);
+
+ /**
+ * @notice Whether `operator` may call `setFunctionPermission` for the function scope
+ * @param masterRole OZ role for the scope
+ * @param targetContract scoped contract
+ * @param functionSelector scoped function
+ * @param operator address checked for grant-operator status
+ * @return allowed whether `operator` is a grant operator for the scope
+ */
+ function isFunctionAccessGrantOperator(
+ bytes32 masterRole,
+ address targetContract,
+ bytes4 functionSelector,
+ address operator
+ ) external view returns (bool);
+
+ /**
+ * @notice Whether `operator` may call `setFunctionPermission` for the function scope
+ * @param key operator permission key
+ * @param operator address checked for grant-operator status
+ * @return allowed whether `operator` is a grant operator for the scope
+ */
+ function isFunctionAccessGrantOperator(bytes32 key, address operator)
+ external
+ view
+ returns (bool);
+
+ /**
+ * @notice Whether `account` may call the scoped function on `targetContract`.
+ * @param masterRole OZ role for the scope
+ * @param targetContract scoped contract
+ * @param functionSelector scoped function
+ * @param account address checked for permissio.
+ * @return allowed whether `account` has function access for the scope
+ */
+ function hasFunctionPermission(
+ bytes32 masterRole,
+ address targetContract,
+ bytes4 functionSelector,
+ address account
+ ) external view returns (bool);
+
+ /**
+ * @notice Whether `account` has function access for the scope.
+ * @param key the base key for function permission mappings
+ * @param account address checked for permission
+ * @return allowed whether `account` has function access for the scope
+ */
+ function hasFunctionPermission(bytes32 key, address account)
+ external
+ view
+ returns (bool);
+
+ /**
+ * @notice calculates the base key for function permission mappings
+ * @param masterRole OZ role
+ * @param targetContract scoped contract
+ * @param functionSelector scoped function of a `targetContract`
+ * @return key the base key for function permission mappings
+ */
+ function permissionRoleKey(
+ bytes32 masterRole,
+ address targetContract,
+ bytes4 functionSelector
+ ) external pure returns (bytes32);
+
+ /**
+ * @notice calculates the base key for function permission mappings
+ * @param masterRole OZ role
+ * @param targetContract scoped contract
+ * @param functionSelector scoped function of a `targetContract`
+ * @return key the base key for function permission mappings
+ */
+ function grantOperatorRoleKey(
+ bytes32 masterRole,
+ address targetContract,
+ bytes4 functionSelector
+ ) external pure returns (bytes32);
+
+ /**
+ * @notice Returns timelock delay for a role
+ * @param role role id
+ * @param overrideDelay override delay for the invocation
+ * @return delay effective delay in seconds
+ * @return isDefault true if role uses default delay
+ */
+ function getRoleTimelockDelay(bytes32 role, uint32 overrideDelay)
+ external
+ view
+ returns (uint32 delay, bool isDefault);
+
+ /**
+ * @notice Default timelock delay when role delay is not set
+ * @return delay delay in seconds
+ */
+ function defaultDelay() external view returns (uint32 delay);
+
+ /**
+ * @notice address of the timelock manager
+ * @return timelockManager address of the timelock manager
+ */
+ function timelockManager() external view returns (address);
+
+ /**
+ * @notice address of the pause manager
+ * @return pauseManager address of the pause manager
+ */
+ function pauseManager() external view returns (address);
+}
diff --git a/contracts/interfaces/IMidasAccessControlManaged.sol b/contracts/interfaces/IMidasAccessControlManaged.sol
new file mode 100644
index 00000000..b76f926d
--- /dev/null
+++ b/contracts/interfaces/IMidasAccessControlManaged.sol
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.34;
+
+/**
+ * @title IMidasAccessControlManaged
+ * @notice Interface for contracts that are managed by the MidasAccessControl
+ * @author RedDuck Software
+ */
+interface IMidasAccessControlManaged {
+ /**
+ * @notice returns the role that can pause the contract
+ * @return role role descriptor
+ */
+ function contractAdminRole() external view returns (bytes32);
+}
diff --git a/contracts/interfaces/IMidasPauseManager.sol b/contracts/interfaces/IMidasPauseManager.sol
new file mode 100644
index 00000000..04aa9623
--- /dev/null
+++ b/contracts/interfaces/IMidasPauseManager.sol
@@ -0,0 +1,161 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.34;
+
+/**
+ * @title IMidasPauseManager
+ * @notice Interface for the MidasPauseManager
+ * @author RedDuck Software
+ */
+interface IMidasPauseManager {
+ /**
+ * @param contractAddr contract address
+ * @param fn function id
+ * @param isPaused paused status
+ */
+ event FnPauseStatusChange(
+ address indexed contractAddr,
+ bytes4 indexed fn,
+ bool isPaused
+ );
+
+ /**
+ * @param contractAddr contract address
+ * @param isPaused paused status
+ */
+ event ContractPauseStatusChange(
+ address indexed contractAddr,
+ bool isPaused
+ );
+
+ /**
+ * @param isPaused paused status
+ */
+ event GlobalPauseStatusChange(bool isPaused);
+
+ /**
+ * @param pauseDelay pause delay
+ */
+ event SetPauseDelay(uint32 pauseDelay);
+
+ /**
+ * @param unpauseDelay unpause delay
+ */
+ event SetUnpauseDelay(uint32 unpauseDelay);
+
+ /**
+ * @notice sets the pause delay
+ * @dev can be called only by the pause manager admin or function admin
+ * @param _pauseDelay pause delay
+ */
+ function setPauseDelay(uint32 _pauseDelay) external;
+
+ /**
+ * @notice sets the unpause delay
+ * @dev can be called only by the pause manager admin or function admin
+ * @param _unpauseDelay unpause delay
+ */
+ function setUnpauseDelay(uint32 _unpauseDelay) external;
+
+ /**
+ * @notice pauses the protocol
+ * @dev can be called only by the pause manager admin
+ */
+ function globalPause() external;
+
+ /**
+ * @notice unpauses the protocol
+ * @dev can be called only by the pause manager admin
+ */
+ function globalUnpause() external;
+
+ /**
+ * @notice pauses an array of contracts
+ * @dev can be called only by the pause manager admin or function admin
+ * @param contractAddrs array of contract addresses
+ */
+ function bulkPauseContract(address[] calldata contractAddrs) external;
+
+ /**
+ * @notice unpauses an array of contracts
+ * @dev can be called only by the pause manager admin or function admin
+ * @param contractAddrs array of contract addresses
+ */
+ function bulkUnpauseContract(address[] calldata contractAddrs) external;
+
+ /**
+ * @notice pauses functions on an array of contracts
+ * @dev can be called only by the pause manager admin or function admin
+ * @param contractAddrs array of contract addresses
+ * @param selectors function ids to pause on the contracts
+ */
+ function bulkPauseContractFn(
+ address[] calldata contractAddrs,
+ bytes4[] calldata selectors
+ ) external;
+
+ /**
+ * @notice unpauses functions on an array of contracts
+ * @dev can be called only by the pause manager admin or function admin
+ * @param contractAddrs array of contract addresses
+ * @param selectors function ids to unpause on the contracts
+ */
+ function bulkUnpauseContractFn(
+ address[] calldata contractAddrs,
+ bytes4[] calldata selectors
+ ) external;
+
+ /**
+ * @notice pauses a contract
+ * @dev can be called only by admin of a contract or function admin that
+ * is managed by the admin of the contract
+ * @param contractAddr address of the contract
+ */
+ function contractAdminPause(address contractAddr) external;
+
+ /**
+ * @notice unpauses a contract
+ * @dev can be called only by admin of a contract or function admin that
+ * is managed by the admin of the contract
+ * @param contractAddr address of the contract
+ */
+ function contractAdminUnpause(address contractAddr) external;
+
+ /**
+ * @notice checks if function of a contract is paused
+ * @param contractAddr contract address
+ * @param selector function id
+ * @return paused true if the function is paused
+ */
+ function isFunctionPaused(address contractAddr, bytes4 selector)
+ external
+ view
+ returns (bool);
+
+ /**
+ * @notice checks if function or contract or protocol is paused
+ * @param contractAddr contract address
+ * @return paused true if paused
+ */
+ function isPaused(address contractAddr, bytes4 selector)
+ external
+ view
+ returns (bool);
+
+ /**
+ * @notice returns the admin role for the pause manager
+ * @return role admin role
+ */
+ function pauseAdminRole() external view returns (bytes32);
+
+ /**
+ * @notice returns the pause delay
+ * @return pause delay
+ */
+ function pauseDelay() external view returns (uint32);
+
+ /**
+ * @notice returns the unpause delay
+ * @return unpause delay
+ */
+ function unpauseDelay() external view returns (uint32);
+}
diff --git a/contracts/interfaces/IMidasTimelockManager.sol b/contracts/interfaces/IMidasTimelockManager.sol
new file mode 100644
index 00000000..ca3d16af
--- /dev/null
+++ b/contracts/interfaces/IMidasTimelockManager.sol
@@ -0,0 +1,451 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.34;
+
+/**
+ * @notice Timelock operation status
+ * @dev Computed status may differ from stored status (expiry, dispute period).
+ */
+enum TimelockOperationStatus {
+ NotExist,
+ Pending,
+ Paused,
+ ApprovedExecution,
+ ReadyToExecute,
+ ReadyToAbort,
+ Expired,
+ Aborted,
+ Executed,
+ ExecutedWithFailure
+}
+
+/**
+ * @notice Operation details returned by `getOperationDetails`
+ */
+struct GetOperationStatusResult {
+ /// @notice current status
+ TimelockOperationStatus status;
+ /// @notice block timestamp when operation was scheduled
+ uint32 createdAt;
+ /// @notice block timestamp when execution was approved by council
+ uint32 executionApprovedAt;
+ /// @notice pause reason code set by pauser
+ uint8 pauseReasonCode;
+ /// @notice security council version at pause time
+ uint256 councilVersion;
+ /// @notice address that scheduled the operation
+ address operationProposer;
+ /// @notice address that paused the operation
+ address pauser;
+ /// @notice block timestamp when operation was paused
+ uint32 pausedAt;
+ /// @notice hash of target, value and data
+ bytes32 dataHash;
+ /// @notice number of council votes for execution
+ uint8 votesForExecution;
+ /// @notice number of council votes for veto
+ uint8 votesForVeto;
+ /// @notice true if operation updates security council
+ bool isSetCouncilOperation;
+}
+
+/**
+ * @title IMidasTimelockManager
+ * @notice Interface for the MidasTimelockManager
+ * @author RedDuck Software
+ */
+interface IMidasTimelockManager {
+ /**
+ * @notice Parameters for scheduling a timelock operation
+ * @param target target contract
+ * @param data calldata
+ */
+ struct ScheduleTimelockOperationParams {
+ /// @notice target contract
+ address target;
+ /// @notice calldata
+ bytes data;
+ }
+
+ /**
+ * @param maxPendingOperationsPerProposer new limit
+ */
+ event SetMaxPendingOperationsPerProposer(
+ uint256 maxPendingOperationsPerProposer
+ );
+
+ /**
+ * @param version new security council version
+ * @param members council member addresses
+ */
+ event SetSecurityCouncil(uint256 indexed version, address[] members);
+
+ /**
+ * @param caller operation proposer
+ * @param operationId scheduled operation id
+ */
+ event ScheduleTimelockOperation(
+ address indexed caller,
+ bytes32 indexed operationId
+ );
+
+ /**
+ * @param caller pauser address
+ * @param operationId paused operation id
+ * @param pauseReasonCode pause reason code
+ * @param councilVersion security council version at pause
+ */
+ event PauseTimelockOperation(
+ address indexed caller,
+ bytes32 indexed operationId,
+ uint8 indexed pauseReasonCode,
+ uint256 councilVersion
+ );
+
+ /**
+ * @param caller executor address
+ * @param operationId executed operation id
+ * @param success true if operation executed successfully, false otherwise
+ */
+ event ExecuteTimelockOperation(
+ address indexed caller,
+ bytes32 indexed operationId,
+ bool success
+ );
+
+ /**
+ * @param caller council member address
+ * @param operationId operation id
+ * @param votedForExecution true for execution vote, false for veto vote
+ */
+ event PausedProposalVoteCast(
+ address indexed caller,
+ bytes32 indexed operationId,
+ bool indexed votedForExecution
+ );
+
+ /**
+ * @param caller address that aborted the operation
+ * @param operationId aborted operation id
+ * @param status status before abort
+ */
+ event AbortTimelockOperation(
+ address indexed caller,
+ bytes32 indexed operationId,
+ TimelockOperationStatus status
+ );
+
+ /**
+ * @notice Preflight call succeeded with role info
+ * @param role role used for the call
+ * @param overrideDelay override delay for the invocation
+ * @param roleIsFunctionOperator true if role is function operator
+ * @param validateFunctionRole true if function role should be validated
+ */
+ error RolePreflightSucceeded(
+ bytes32 role,
+ uint32 overrideDelay,
+ bool roleIsFunctionOperator,
+ bool validateFunctionRole
+ );
+
+ /**
+ * @notice Timelock address is already set
+ */
+ error TimelockAlreadySet();
+
+ /**
+ * @notice Operation status is not valid for this action
+ * @param actualStatus current operation status
+ */
+ error UnexpectedOperationStatus(TimelockOperationStatus actualStatus);
+
+ /**
+ * @notice Operation is not in the pending set
+ */
+ error OperationNotPending();
+
+ /**
+ * @notice Operation is already pending
+ */
+ error OperationAlreadyPending();
+
+ /**
+ * @notice Timelock delay has not passed yet
+ */
+ error TimelockOperationNotReady();
+
+ /**
+ * @notice Caller is not a security council member for this operation
+ */
+ error NotInSecurityCouncil();
+
+ /**
+ * @notice Council member already voted
+ */
+ error AlreadyVoted();
+
+ /**
+ * @notice Role has no timelock delay configured
+ */
+ error NoTimelockDelayForRole();
+
+ /**
+ * @notice Proposer has too many pending operations
+ */
+ error TooManyPendingOperations();
+
+ /**
+ * @notice Pending set-council operation already exists
+ */
+ error PendingSetCouncilOperationExists();
+
+ /**
+ * @notice Security council size is out of allowed range
+ */
+ error InvalidSecurityCouncilMembersLength();
+
+ /**
+ * @notice Max pending operations value is invalid
+ */
+ error InvalidMaxPendingOperationsPerProposer();
+
+ /**
+ * @notice Target call should have reverted on preflight
+ */
+ error PreflightCallUnexpectedSuccess();
+
+ /**
+ * @notice Preflight revert data is invalid
+ * @param err revert bytes
+ */
+ error InvalidPreflightError(bytes err);
+
+ /**
+ * @notice Sets max pending operations per proposer
+ * @param _maxPendingOperationsPerProposer new limit
+ */
+ function setMaxPendingOperationsPerProposer(
+ uint256 _maxPendingOperationsPerProposer
+ ) external;
+
+ /**
+ * @notice Sets a new security council version
+ * @param members council member addresses
+ */
+ function setSecurityCouncil(address[] calldata members) external;
+
+ /**
+ * @notice Schedules multiple timelock operations
+ * @param params array of schedule timelock operation parameters
+ */
+ function bulkScheduleTimelockOperation(
+ ScheduleTimelockOperationParams[] calldata params
+ ) external;
+
+ /**
+ * @notice Schedules one timelock operation
+ * @param params schedule timelock operation parameters
+ */
+ function scheduleTimelockOperation(
+ ScheduleTimelockOperationParams calldata params
+ ) external;
+
+ /**
+ * @notice Executes a scheduled timelock operation
+ * @param target target contract
+ * @param data operation data
+ * @param revertOnFailure true if execution should revert on failure
+ */
+ function executeTimelockOperation(
+ address target,
+ bytes calldata data,
+ bool revertOnFailure
+ ) external;
+
+ /**
+ * @notice Pauses a pending operation
+ * @param operationId operation id
+ * @param pauseReasonCode reason code set by pauser
+ */
+ function pauseOperation(bytes32 operationId, uint8 pauseReasonCode)
+ external;
+
+ /**
+ * @notice Security council votes to abort the operation
+ * @dev can vote even if member is already voted for execution
+ * @param operationId operation id
+ */
+ function voteForVeto(bytes32 operationId) external;
+
+ /**
+ * @notice Security council votes to allow execution
+ * @dev cannot vote if member is already voted for veto
+ * @param operationId operation id
+ */
+ function voteForExecution(bytes32 operationId) external;
+
+ /**
+ * @notice Aborts operation after veto quorum or expiry
+ * @param operationId operation id
+ */
+ function abortOperation(bytes32 operationId) external;
+
+ /**
+ * @notice Returns original proposer for a pending operation
+ * @param target target contract
+ * @param data operation data
+ * @return proposer address
+ */
+ function getOriginalProposer(address target, bytes calldata data)
+ external
+ view
+ returns (address);
+
+ /**
+ * @notice Votes needed for council quorum at a version
+ * @param version security council version
+ * @return quorum required votes
+ */
+ function councilQuorum(uint256 version)
+ external
+ view
+ returns (uint8 quorum);
+
+ /**
+ * @notice Whether a council member voted on an operation
+ * @param operationId operation id
+ * @param councilMember member address
+ * @return votedForExecution true if voted for execution
+ * @return votedForVeto true if voted for veto
+ */
+ function getCouncilMemberVoteStatus(
+ bytes32 operationId,
+ address councilMember
+ ) external view returns (bool votedForExecution, bool votedForVeto);
+
+ /**
+ * @notice Returns all pending operation ids
+ * @return operationIds pending operation ids
+ */
+ function getPendingOperations()
+ external
+ view
+ returns (bytes32[] memory operationIds);
+
+ /**
+ * @notice Returns full operation details
+ * @param operationId operation id
+ * @return result operation details
+ */
+ function getOperationDetails(bytes32 operationId)
+ external
+ view
+ returns (GetOperationStatusResult memory result);
+
+ /**
+ * @notice Returns operation status (with expiry/dispute rules applied)
+ * @param operationId operation id
+ * @return status current status
+ */
+ function getOperationStatus(bytes32 operationId)
+ external
+ view
+ returns (TimelockOperationStatus status);
+
+ /**
+ * @notice Returns stored operation status without adjustments
+ * @param operationId operation id
+ * @return status stored status
+ */
+ function getOperationStatusRaw(bytes32 operationId)
+ external
+ view
+ returns (TimelockOperationStatus status);
+
+ /**
+ * @notice Returns security council members for a version
+ * @param version security council version
+ * @return members member addresses
+ */
+ function getSecurityCouncilMembers(uint256 version)
+ external
+ view
+ returns (address[] memory members);
+
+ /**
+ * @notice Returns operation id for target and data
+ * @param target target contract
+ * @param data operation data
+ * @return operationId operation id
+ */
+ function getOperationId(address target, bytes calldata data)
+ external
+ view
+ returns (bytes32 operationId);
+
+ /**
+ * @dev gets the target role for a given operation
+ * @param target target contract
+ * @param data operation data
+ * @param proposer operation proposer address
+ * @return role target role
+ * @return overrideDelay override delay for the invocation
+ */
+ function getTargetRole(
+ address target,
+ bytes calldata data,
+ address proposer
+ ) external view returns (bytes32 role, uint32 overrideDelay);
+
+ /**
+ * @notice Checks if an account is in the security council for a given version
+ * @param version security council version
+ * @param account account to check
+ * @return true if the account is in the security council
+ */
+ function isInSecurityCouncil(uint256 version, address account)
+ external
+ view
+ returns (bool);
+
+ /**
+ * @notice Timelock controller address
+ * @return timelockAddress timelock controller
+ */
+ function timelock() external view returns (address timelockAddress);
+
+ /**
+ * @notice Max pending operations per proposer
+ * @return value current limit
+ */
+ function maxPendingOperationsPerProposer() external view returns (uint256);
+
+ /**
+ * @notice Current security council version
+ * @return version council version
+ */
+ function securityCouncilVersion() external view returns (uint256);
+
+ /**
+ * @notice Data hash index used for operation id salt
+ * @param dataHash operation data hash
+ * @return index current index for this data hash
+ */
+ function dataHashIndexes(bytes32 dataHash) external view returns (uint256);
+
+ /**
+ * @notice Pending operations count for a proposer
+ * @param proposer proposer address
+ * @return count pending count
+ */
+ function proposerPendingOperationsCount(address proposer)
+ external
+ view
+ returns (uint256);
+
+ /**
+ * @notice Pending set-security-council operation id, if any
+ * @return operationId operation id or zero
+ */
+ function pendingSetCouncilOperationId() external view returns (bytes32);
+}
diff --git a/contracts/interfaces/IRedemptionVault.sol b/contracts/interfaces/IRedemptionVault.sol
index 2c21f659..2215910d 100644
--- a/contracts/interfaces/IRedemptionVault.sol
+++ b/contracts/interfaces/IRedemptionVault.sol
@@ -1,30 +1,64 @@
// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.9;
+pragma solidity 0.8.34;
-import "./IManageableVault.sol";
+import {IManageableVault, RequestStatus} from "./IManageableVault.sol";
/**
* @notice Redeem request scruct
- * @param sender user address who create
- * @param tokenOut tokenOut address
- * @param status request status
- * @param amountMToken amount mToken
- * @param mTokenRate rate of mToken at request creation time
- * @param tokenOutRate rate of tokenOut at request creation time
*/
struct Request {
- address sender;
+ /// @notice user address which will receive the mTokens
+ address recipient;
+ /// @notice tokenOut address
address tokenOut;
+ /// @notice request status
RequestStatus status;
+ /// @notice fixed fee percent that was calculated at request creation time
+ uint256 feePercent;
+ /// @notice amount of mToken
uint256 amountMToken;
+ /// @notice rate of mToken at request creation time
uint256 mTokenRate;
+ /// @notice rate of tokenOut at request creation time
uint256 tokenOutRate;
+ /// @notice amount of mToken that was redeemed instantly
+ uint256 amountMTokenInstant;
+ /// @notice approved mToken rate
+ uint256 approvedMTokenRate;
+ /// @notice amount of tokenOut that was redeeme
+ uint256 amountTokenOut;
}
-struct FiatRedeptionInitParams {
- uint256 fiatAdditionalFee;
- uint256 fiatFlatFee;
- uint256 minFiatRedeemAmount;
+/**
+ * @notice Redemption vault init params
+ */
+struct RedemptionVaultInitParams {
+ /// @notice address of request redeemer
+ address requestRedeemer;
+ /// @notice address of loan liquidity provider
+ address loanLp;
+ /// @notice address of loan repayment address
+ address loanRepaymentAddress;
+ /// @notice address of loan swapper vault
+ address loanSwapperVault;
+ /// @notice loan APR value in basis points (100 = 1%)
+ uint256 loanApr;
+}
+
+/**
+ * @notice Liquidity provider loan request struct
+ */
+struct LiquidityProviderLoanRequest {
+ /// @notice tokenOut address
+ address tokenOut;
+ /// @notice amount of tokenOut
+ uint256 amountTokenOut;
+ /// @notice amount of tokenOut fee
+ uint256 amountFee;
+ /// @notice timestamp of the request creation
+ uint256 createdAt;
+ /// @notice status of the loan
+ RequestStatus status;
}
/**
@@ -35,123 +69,149 @@ interface IRedemptionVault is IManageableVault {
/**
* @param user function caller (msg.sender)
* @param tokenOut address of tokenOut
- * @param amount amount of mToken
- * @param feeAmount fee amount in mToken
+ * @param recipient recipient address
+ * @param amountMToken amount of mToken
+ * @param feeAmount fee amount in tokenOut
* @param amountTokenOut amount of tokenOut
+ * @param mTokenRate mToken rate
+ * @param tokenOutRate tokenOut rate
*/
event RedeemInstant(
address indexed user,
address indexed tokenOut,
- uint256 amount,
+ address recipient,
+ uint256 amountMToken,
uint256 feeAmount,
- uint256 amountTokenOut
+ uint256 amountTokenOut,
+ uint256 mTokenRate,
+ uint256 tokenOutRate
);
/**
+ * @param requestId request id
* @param user function caller (msg.sender)
* @param tokenOut address of tokenOut
- * @param recipient address that receives tokens
- * @param amount amount of mToken
- * @param feeAmount fee amount in mToken
- * @param amountTokenOut amount of tokenOut
+ * @param recipient recipient address
+ * @param amountMToken amount of mToken
+ * @param amountMTokenInstant amount of mToken that was redeemed instantly
+ * @param feePercent fee percent
+ * @param mTokenRate mToken rate
+ * @param tokenOutRate tokenOut rate
*/
- event RedeemInstantWithCustomRecipient(
+ event RedeemRequest(
+ uint256 indexed requestId,
address indexed user,
address indexed tokenOut,
address recipient,
- uint256 amount,
- uint256 feeAmount,
- uint256 amountTokenOut
+ uint256 amountMToken,
+ uint256 amountMTokenInstant,
+ uint256 feePercent,
+ uint256 mTokenRate,
+ uint256 tokenOutRate
);
/**
- * @param requestId request id
- * @param user function caller (msg.sender)
- * @param tokenOut address of tokenOut
- * @param amountMTokenIn amount of mToken
- * @param feeAmount fee amount in mToken
+ * @param loanId loan id
+ * @param tokenOut tokenOut address
+ * @param amountTokenOut amount of tokenOut
+ * @param amountFee fee amount in payment token
+ * @param mTokenRate mToken rate
+ * @param tokenOutRate tokenOut rate
*/
- event RedeemRequest(
- uint256 indexed requestId,
- address indexed user,
+ event CreateLiquidityProviderLoanRequest(
+ uint256 indexed loanId,
address indexed tokenOut,
- uint256 amountMTokenIn,
- uint256 feeAmount
+ uint256 amountTokenOut,
+ uint256 amountFee,
+ uint256 mTokenRate,
+ uint256 tokenOutRate
);
/**
- * @param requestId request id
- * @param user function caller (msg.sender)
- * @param tokenOut address of tokenOut
- * @param recipient address that receives tokens
- * @param amountMTokenIn amount of mToken
- * @param feeAmount fee amount in mToken
+ * @param requestId mint request id
+ * @param newMTokenRate new mToken rate
+ * @param isSafe if true, approval is safe
+ * @param isAvgRate if true, newMTokenRate is avg rate
*/
- event RedeemRequestWithCustomRecipient(
+ event ApproveRequest(
uint256 indexed requestId,
- address indexed user,
- address indexed tokenOut,
- address recipient,
- uint256 amountMTokenIn,
- uint256 feeAmount
+ uint256 newMTokenRate,
+ bool isSafe,
+ bool isAvgRate
);
/**
* @param requestId mint request id
- * @param newMTokenRate net mToken rate
*/
- event ApproveRequest(uint256 indexed requestId, uint256 newMTokenRate);
+ event RejectRequest(uint256 indexed requestId);
/**
- * @param requestId mint request id
- * @param newMTokenRate net mToken rate
+ * @param redeemer new address of request redeemer
*/
- event SafeApproveRequest(uint256 indexed requestId, uint256 newMTokenRate);
+ event SetRequestRedeemer(address indexed redeemer);
/**
- * @param requestId mint request id
- * @param user address of user
+ * @param newLoanLp new address of loan liquidity provider
*/
- event RejectRequest(uint256 indexed requestId, address indexed user);
+ event SetLoanLp(address indexed newLoanLp);
/**
- * @param caller function caller (msg.sender)
- * @param newMinAmount new min amount for fiat requests
+ * @param newLoanRepaymentAddress new address of loan repayment address
*/
- event SetMinFiatRedeemAmount(address indexed caller, uint256 newMinAmount);
+ event SetLoanRepaymentAddress(address newLoanRepaymentAddress);
/**
- * @param caller function caller (msg.sender)
- * @param feeInMToken fee amount in mToken
+ * @param newLoanSwapperVault new address of loan swapper vault
*/
- event SetFiatFlatFee(address indexed caller, uint256 feeInMToken);
+ event SetLoanSwapperVault(address newLoanSwapperVault);
/**
- * @param caller function caller (msg.sender)
- * @param newfee new fiat fee percent 1% = 100
+ * @param newLoanApr new loan APR value in basis points (100 = 1%)
*/
- event SetFiatAdditionalFee(address indexed caller, uint256 newfee);
+ event SetLoanApr(uint256 newLoanApr);
/**
- * @param caller function caller (msg.sender)
- * @param redeemer new address of request redeemer
+ * @param newLoanLpFirst new flag to determine if the loan LP liquidity should be used first
*/
- event SetRequestRedeemer(address indexed caller, address redeemer);
+ event SetPreferLoanLiquidity(bool newLoanLpFirst);
+
+ /**
+ * @param requestId request id
+ * @param amountFee amount of fee in tokenOut
+ */
+ event RepayLpLoanRequest(uint256 indexed requestId, uint256 amountFee);
+
+ /**
+ * @param requestId request id
+ */
+ event CancelLpLoanRequest(uint256 indexed requestId);
+
+ /**
+ * @notice when fee exceeds amount
+ * @param fee fee
+ * @param amount amount
+ */
+ error FeeExceedsAmount(uint256 fee, uint256 amount);
+
+ /**
+ * @notice when not self call
+ */
+ error NotSelfCall();
/**
* @notice redeem mToken to tokenOut if daily limit and allowance not exceeded
* Burns mToken from the user.
- * Transfers fee in mToken to feeReceiver
* Transfers tokenOut to user.
* @param tokenOut stable coin token address to redeem to
* @param amountMTokenIn amount of mToken to redeem (decimals 18)
* @param minReceiveAmount minimum expected amount of tokenOut to receive (decimals 18)
+ * @return amountTokenOut amount of tokenOut that was received in original decimals
*/
function redeemInstant(
address tokenOut,
uint256 amountMTokenIn,
uint256 minReceiveAmount
- ) external;
+ ) external returns (uint256);
/**
* @notice Does the same as original `redeemInstant` but allows specifying a custom tokensReceiver address.
@@ -159,18 +219,18 @@ interface IRedemptionVault is IManageableVault {
* @param amountMTokenIn amount of mToken to redeem (decimals 18)
* @param minReceiveAmount minimum expected amount of tokenOut to receive (decimals 18)
* @param recipient address that receives tokens
+ * @return amountTokenOut amount of tokenOut that was received in original decimals
*/
function redeemInstant(
address tokenOut,
uint256 amountMTokenIn,
uint256 minReceiveAmount,
address recipient
- ) external;
+ ) external returns (uint256);
/**
- * @notice creating redeem request if tokenOut not fiat
+ * @notice creating redeem request
* Transfers amount in mToken to contract
- * Transfers fee in mToken to feeReceiver
* @param tokenOut stable coin token address to redeem to
* @param amountMTokenIn amount of mToken to redeem (decimals 18)
* @return request id
@@ -180,34 +240,30 @@ interface IRedemptionVault is IManageableVault {
returns (uint256);
/**
- * @notice Does the same as original `redeemRequest` but allows specifying a custom tokensReceiver address.
+ * @notice Instantly redeems `instantShare` amount of `amountMTokenIn` and creates a request for the remaining amount.
* @param tokenOut stable coin token address to redeem to
* @param amountMTokenIn amount of mToken to redeem (decimals 18)
- * @param recipient address that receives tokens
+ * @param recipientRequest address that receives tokens for the request part
+ * @param instantShare % amount of `amountMTokenIn` that will be redeemed instantly
+ * @param minReceiveAmountInstantShare min receive amount for the instant share
+ * @param recipientInstant address that receives tokens for the instant part
* @return request id
+ * @return instantReceivedAmount amount of tokenOut that was received instantly in original decimals
*/
function redeemRequest(
address tokenOut,
uint256 amountMTokenIn,
- address recipient
- ) external returns (uint256);
-
- /**
- * @notice creating redeem request if tokenOut is fiat
- * Transfers amount in mToken to contract
- * Transfers fee in mToken to feeReceiver
- * @param amountMTokenIn amount of mToken to redeem (decimals 18)
- * @return request id
- */
- function redeemFiatRequest(uint256 amountMTokenIn)
- external
- returns (uint256);
+ address recipientRequest,
+ uint256 instantShare,
+ uint256 minReceiveAmountInstantShare,
+ address recipientInstant
+ ) external returns (uint256, uint256);
/**
* @notice approving requests from the `requestIds` array with the mToken rate
* from the request. WONT fail even if there is not enough liquidity
* to process all requests.
- * Does same validation as `safeApproveRequest`.
+ * Validates that new mToken rate does not exceed variation tolerance
* Transfers tokenOut to users
* Sets request flags to Processed.
* @param requestIds request ids array
@@ -219,17 +275,29 @@ interface IRedemptionVault is IManageableVault {
* @notice approving requests from the `requestIds` array with the
* current mToken rate. WONT fail even if there is not enough liquidity
* to process all requests.
- * Does same validation as `safeApproveRequest`.
+ * Validates that new mToken rate does not exceed variation tolerance
* Transfers tokenOut to users
* Sets request flags to Processed.
* @param requestIds request ids array
*/
function safeBulkApproveRequest(uint256[] calldata requestIds) external;
+ /**
+ * @notice approving requests from the `requestIds` array with the
+ * current mToken rate as avg rate. WONT fail even if there is not enough liquidity
+ * to process all requests.
+ * Validates that new mToken rate does not exceed variation tolerance
+ * Transfers tokenOut to users
+ * Sets request flags to Processed.
+ * @param requestIds request ids array
+ */
+ function safeBulkApproveRequestAvgRate(uint256[] calldata requestIds)
+ external;
+
/**
* @notice approving requests from the `requestIds` array using the `newMTokenRate`.
* WONT fail even if there is not enough liquidity to process all requests.
- * Does same validation as `safeApproveRequest`.
+ * Validates that new mToken rate does not exceed variation tolerance
* Transfers tokenOut to user
* Sets request flags to Processed.
* @param requestIds request ids array
@@ -241,25 +309,33 @@ interface IRedemptionVault is IManageableVault {
) external;
/**
- * @notice approving redeem request if not exceed tokenOut allowance
- * Burns amount mToken from contract
+ * @notice approving requests from the `requestIds` array using the `avgMTokenRate`.
+ * WONT fail even if there is not enough liquidity to process all requests.
+ * Validates that new mToken rate does not exceed variation tolerance
* Transfers tokenOut to user
- * Sets flag Processed
- * @param requestId request id
- * @param newMTokenRate new mToken rate inputted by vault admin
+ * Sets request flags to Processed.
+ * @param requestIds request ids array
+ * @param avgMTokenRate avg mToken rate inputted by vault admin
*/
- function approveRequest(uint256 requestId, uint256 newMTokenRate) external;
+ function safeBulkApproveRequestAvgRate(
+ uint256[] calldata requestIds,
+ uint256 avgMTokenRate
+ ) external;
/**
- * @notice approving request if inputted token rate fit price diviation percent
+ * @notice approving redeem request if not exceed tokenOut allowance
* Burns amount mToken from contract
* Transfers tokenOut to user
* Sets flag Processed
* @param requestId request id
* @param newMTokenRate new mToken rate inputted by vault admin
+ * @param isAvgRate if true, newMTokenRate is avg rate
*/
- function safeApproveRequest(uint256 requestId, uint256 newMTokenRate)
- external;
+ function approveRequest(
+ uint256 requestId,
+ uint256 newMTokenRate,
+ bool isAvgRate
+ ) external;
/**
* @notice rejecting request
@@ -269,26 +345,53 @@ interface IRedemptionVault is IManageableVault {
function rejectRequest(uint256 requestId) external;
/**
- * @notice set new min amount for fiat requests
- * @param newValue new min amount
- */
- function setMinFiatRedeemAmount(uint256 newValue) external;
-
- /**
- * @notice set fee amount in mToken for fiat requests
- * @param feeInMToken fee amount in mToken
+ * @notice repaying loan requests from the `requestIds` array
+ * Transfers tokenOut to loan repayment address
+ * Sets request flags to Processed.
+ * @param requestIds request ids array
*/
- function setFiatFlatFee(uint256 feeInMToken) external;
+ function bulkRepayLpLoanRequest(uint256[] calldata requestIds) external;
/**
- * @notice set new fee percent for fiat requests
- * @param newFee new fee percent 1% = 100
+ * @notice canceling loan request
+ * Sets request flags to Canceled.
+ * @param requestId request id
*/
- function setFiatAdditionalFee(uint256 newFee) external;
+ function cancelLpLoanRequest(uint256 requestId) external;
/**
* @notice set address which is designated for standard redemptions, allowing tokens to be pulled from this address
* @param redeemer new address of request redeemer
*/
function setRequestRedeemer(address redeemer) external;
+
+ /**
+ * @notice set address of loan liquidity provider
+ * @param newLoanLp new address of loan liquidity provider
+ */
+ function setLoanLp(address newLoanLp) external;
+
+ /**
+ * @notice set address of loan repayment address
+ * @param newLoanRepaymentAddress new address of loan repayment address
+ */
+ function setLoanRepaymentAddress(address newLoanRepaymentAddress) external;
+
+ /**
+ * @notice set address of loan swapper vault
+ * @param newLoanSwapperVault new address of loan swapper vault
+ */
+ function setLoanSwapperVault(address newLoanSwapperVault) external;
+
+ /**
+ * @notice set loan APR value in basis points (100 = 1%)
+ * @param newLoanApr new loan APR value in basis points (100 = 1%)
+ */
+ function setLoanApr(uint256 newLoanApr) external;
+
+ /**
+ * @notice set flag to determine if the loan LP liquidity should be used first
+ * @param newLoanLpFirst new flag to determine if the loan LP liquidity should be used first
+ */
+ function setPreferLoanLiquidity(bool newLoanLpFirst) external;
}
diff --git a/contracts/interfaces/IRedemptionVaultWithSwapper.sol b/contracts/interfaces/IRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 9fcbd29e..00000000
--- a/contracts/interfaces/IRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,37 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "./IRedemptionVault.sol";
-
-/**
- * @title IRedemptionVaultWithSwapper
- * @author RedDuck Software
- */
-interface IRedemptionVaultWithSwapper is IRedemptionVault {
- /**
- * @param caller caller address (msg.sender)
- * @param provider new LP address
- */
- event SetLiquidityProvider(
- address indexed caller,
- address indexed provider
- );
-
- /**
- * @param caller caller address (msg.sender)
- * @param vault new underlying vault for swapper
- */
- event SetSwapperVault(address indexed caller, address indexed vault);
-
- /**
- * @notice sets new liquidity provider address
- * @param provider new liquidity provider address
- */
- function setLiquidityProvider(address provider) external;
-
- /**
- * @notice sets new underlying vault for swapper
- * @param vault new underlying vault for swapper
- */
- function setSwapperVault(address vault) external;
-}
diff --git a/contracts/interfaces/ISanctionsList.sol b/contracts/interfaces/ISanctionsList.sol
index 5f30dcc9..d0e6853f 100644
--- a/contracts/interfaces/ISanctionsList.sol
+++ b/contracts/interfaces/ISanctionsList.sol
@@ -1,7 +1,9 @@
// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+pragma solidity 0.8.34;
-// TODO: add natspec
+/**
+ * @notice Chainalysis sanctions oracle interface
+ */
interface ISanctionsList {
function isSanctioned(address addr) external view returns (bool);
}
diff --git a/contracts/interfaces/aave/IAaveV3Pool.sol b/contracts/interfaces/aave/IAaveV3Pool.sol
index c239da0f..5c208b91 100644
--- a/contracts/interfaces/aave/IAaveV3Pool.sol
+++ b/contracts/interfaces/aave/IAaveV3Pool.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+pragma solidity 0.8.34;
/**
* @title IAaveV3Pool
diff --git a/contracts/interfaces/acre/IAcreAdapter.sol b/contracts/interfaces/acre/IAcreAdapter.sol
index fd0ab003..20e93711 100644
--- a/contracts/interfaces/acre/IAcreAdapter.sol
+++ b/contracts/interfaces/acre/IAcreAdapter.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+pragma solidity 0.8.34;
/**
* @title IAcreAdapter
diff --git a/contracts/interfaces/buidl/IRedemption.sol b/contracts/interfaces/buidl/IRedemption.sol
deleted file mode 100644
index be026f89..00000000
--- a/contracts/interfaces/buidl/IRedemption.sol
+++ /dev/null
@@ -1,48 +0,0 @@
-/**
- * Copyright 2024 Circle Internet Financial, LTD. All rights reserved.
- *
- * SPDX-License-Identifier: Apache-2.0
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-pragma solidity 0.8.9;
-
-/**
- * @title IRedemption
- */
-interface IRedemption {
- /**
- * @notice The asset being redeemed.
- * @return The address of the asset token.
- */
- function asset() external view returns (address);
-
- /**
- * @notice The liquidity token that the asset is being redeemed for.
- * @return The address of the liquidity token.
- */
- function liquidity() external view returns (address);
-
- /**
- * @notice The settlement contract address.
- * @return The address of the settlement contract.
- */
- function settlement() external view returns (address);
-
- /**
- * @notice Redeems an amount of asset for liquidity
- * @param amount The amount of the asset token to redeem
- */
- function redeem(uint256 amount) external;
-}
diff --git a/contracts/interfaces/morpho/IMorphoVault.sol b/contracts/interfaces/morpho/IMorphoVault.sol
index 34e78c67..120f1dcd 100644
--- a/contracts/interfaces/morpho/IMorphoVault.sol
+++ b/contracts/interfaces/morpho/IMorphoVault.sol
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+pragma solidity 0.8.34;
-import "@openzeppelin/contracts/interfaces/IERC4626.sol";
+import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
/**
* @title IMorphoVault
diff --git a/contracts/interfaces/ustb/IAllowListV2.sol b/contracts/interfaces/ustb/IAllowListV2.sol
index 4f1fa47e..b9ccb927 100644
--- a/contracts/interfaces/ustb/IAllowListV2.sol
+++ b/contracts/interfaces/ustb/IAllowListV2.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+pragma solidity 0.8.34;
interface IAllowListV2 {
type EntityId is uint256;
diff --git a/contracts/interfaces/ustb/ISuperstateToken.sol b/contracts/interfaces/ustb/ISuperstateToken.sol
index d13b6a08..41aa96c8 100644
--- a/contracts/interfaces/ustb/ISuperstateToken.sol
+++ b/contracts/interfaces/ustb/ISuperstateToken.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+pragma solidity 0.8.34;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
diff --git a/contracts/interfaces/ustb/IUSTBRedemption.sol b/contracts/interfaces/ustb/IUSTBRedemption.sol
index 59588410..6929b0d1 100644
--- a/contracts/interfaces/ustb/IUSTBRedemption.sol
+++ b/contracts/interfaces/ustb/IUSTBRedemption.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+pragma solidity 0.8.34;
interface IUSTBRedemption {
function SUPERSTATE_TOKEN() external view returns (address);
diff --git a/contracts/libraries/DecimalsCorrectionLibrary.sol b/contracts/libraries/DecimalsCorrectionLibrary.sol
index 0481385d..b6adb8b9 100644
--- a/contracts/libraries/DecimalsCorrectionLibrary.sol
+++ b/contracts/libraries/DecimalsCorrectionLibrary.sol
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.9;
+pragma solidity 0.8.34;
/**
* @title DecimalsCorrectionLibrary
diff --git a/contracts/libraries/MidasAuthLibrary.sol b/contracts/libraries/MidasAuthLibrary.sol
new file mode 100644
index 00000000..9279f4ee
--- /dev/null
+++ b/contracts/libraries/MidasAuthLibrary.sol
@@ -0,0 +1,366 @@
+// SPDX-License-Identifier: BUSL-1.1
+
+pragma solidity 0.8.34;
+
+import {IMidasAccessControl} from "../interfaces/IMidasAccessControl.sol";
+import {IMidasTimelockManager} from "../interfaces/IMidasTimelockManager.sol";
+
+/**
+ * @title MidasAuthLibrary
+ * @author RedDuck Software
+ */
+library MidasAuthLibrary {
+ /**
+ * @notice error when the function permission is not found
+ * @param roleUsed role used
+ * @param functionSelector function selector
+ * @param account account
+ */
+ error NoFunctionPermission(
+ bytes32 roleUsed,
+ bytes4 functionSelector,
+ address account
+ );
+
+ /**
+ * @notice error when the account is not greenlisted
+ * @param account account
+ * @param greenlistedRole greenlisted role
+ */
+ error NotGreenlisted(address account, bytes32 greenlistedRole);
+
+ /**
+ * @notice error when the account is blacklisted
+ * @param blacklistedRole blacklisted role
+ * @param account account
+ */
+ error Blacklisted(bytes32 blacklistedRole, address account);
+
+ /**
+ * @notice error when the sender is not the timelock
+ * @param roleUsed role used
+ * @param functionSelector function selector
+ * @param sender sender
+ */
+ error SenderIsNotTimelock(
+ bytes32 roleUsed,
+ bytes4 functionSelector,
+ address sender
+ );
+
+ /**
+ * @notice error when the user facing role is not allowed
+ * @param role role
+ */
+ error UserFacingRoleNotAllowed(bytes32 role);
+
+ /**
+ * @notice error when the delay is invalid
+ */
+ error InvalidDelay();
+
+ /**
+ * @notice default role for greenlisted actor
+ */
+ // solhint-disable-next-line private-vars-leading-underscore
+ bytes32 internal constant DEFAULT_GREENLISTED_ROLE =
+ keccak256("GREENLISTED_ROLE");
+
+ /**
+ * @notice default role for blacklisted actor
+ */
+ // solhint-disable-next-line private-vars-leading-underscore
+ bytes32 internal constant DEFAULT_BLACKLISTED_ROLE =
+ keccak256("BLACKLISTED_ROLE");
+
+ /**
+ * @notice timelock value that represents no delay
+ */
+ // solhint-disable-next-line private-vars-leading-underscore
+ uint32 internal constant NO_DELAY = type(uint32).max;
+
+ /**
+ * @notice timelock value that represents non-set delay
+ */
+ // solhint-disable-next-line private-vars-leading-underscore
+ uint32 internal constant NULL_DELAY = 0;
+
+ /**
+ * @notice maximum delay for a role
+ */
+ // solhint-disable-next-line private-vars-leading-underscore
+ uint32 internal constant MAX_DELAY = 7 days;
+
+ /**
+ * @dev validates that the function access is valid with timelock
+ * @param accessControl access control contract
+ * @param contractAdminRole contract admin role
+ * @param roleIsFunctionOperatorRole whether the role is a function operator
+ * @param accountToCheck account to check
+ * @param validateFunctionRole whether to validate the function role
+ * @return actualAccount actual account that has access to the function
+ */
+ function validateFunctionAccessWithTimelock(
+ IMidasAccessControl accessControl,
+ bytes32 contractAdminRole,
+ uint32 overrideDelay,
+ bool roleIsFunctionOperatorRole,
+ address accountToCheck,
+ bool validateFunctionRole
+ )
+ internal
+ view
+ returns (
+ address /* actualAccount */
+ )
+ {
+ IMidasTimelockManager timelockManager = IMidasTimelockManager(
+ accessControl.timelockManager()
+ );
+
+ bool isPreflight = accountToCheck == address(timelockManager);
+
+ if (isPreflight) {
+ revert IMidasTimelockManager.RolePreflightSucceeded(
+ contractAdminRole,
+ overrideDelay,
+ roleIsFunctionOperatorRole,
+ validateFunctionRole
+ );
+ }
+
+ bool isTimelock = accountToCheck == timelockManager.timelock();
+
+ address account = isTimelock
+ ? timelockManager.getOriginalProposer(address(this), msg.data)
+ : accountToCheck;
+
+ bytes32 roleUsed = validateFunctionAccess(
+ accessControl,
+ address(this),
+ contractAdminRole,
+ overrideDelay,
+ roleIsFunctionOperatorRole,
+ account,
+ msg.sig,
+ validateFunctionRole
+ );
+
+ (uint32 delay, ) = accessControl.getRoleTimelockDelay(
+ roleUsed,
+ overrideDelay
+ );
+
+ if (delay > 0) {
+ require(
+ isTimelock,
+ SenderIsNotTimelock(roleUsed, msg.sig, accountToCheck)
+ );
+ }
+
+ return account;
+ }
+
+ /**
+ * @dev validates that the function access is valid
+ * @param accessControl access control contract
+ * @param role admin role
+ * @param overrideDelay override delay for the invocation
+ * @param roleIsFunctionOperatorRole whether the role is a function operator role
+ * @param account account to check
+ * @param functionSelector function selector
+ * @param validateFunctionRole whether to validate the function role
+ * @return roleUsed role used to validate the function access
+ */
+ function validateFunctionAccess(
+ IMidasAccessControl accessControl,
+ address targetContract,
+ bytes32 role,
+ uint32 overrideDelay,
+ bool roleIsFunctionOperatorRole,
+ address account,
+ bytes4 functionSelector,
+ bool validateFunctionRole
+ )
+ internal
+ view
+ returns (
+ bytes32 /* roleUsed */
+ )
+ {
+ validateTimelockDelay(overrideDelay);
+
+ if (roleIsFunctionOperatorRole) {
+ if (accessControl.isFunctionAccessGrantOperator(role, account)) {
+ return role;
+ }
+ revert NoFunctionPermission(role, functionSelector, account);
+ }
+
+ requireNotUserFacingRole(accessControl, role);
+
+ bool hasRootRole = accessControl.hasRole(role, account);
+
+ (bytes32 key, bool hasPermission) = validateFunctionRole
+ ? _hasFunctionPermission(
+ accessControl,
+ targetContract,
+ role,
+ functionSelector,
+ account
+ )
+ : (bytes32(0), false);
+
+ if (!hasPermission && !hasRootRole) {
+ revert NoFunctionPermission(role, functionSelector, account);
+ }
+
+ if (!hasRootRole) {
+ return key;
+ }
+
+ if (!hasPermission) {
+ return role;
+ }
+
+ return resolveAccessRole(accessControl, role, key, overrideDelay);
+ }
+
+ /**
+ * @dev validates that the role is not a user facing role
+ * @param accessControl access control contract
+ * @param role role
+ */
+ function requireNotUserFacingRole(
+ IMidasAccessControl accessControl,
+ bytes32 role
+ ) internal view {
+ require(
+ !accessControl.isUserFacingRole(role),
+ UserFacingRoleNotAllowed(role)
+ );
+ }
+
+ /**
+ * @dev checks that a given `account` has `greenlistedRole`
+ * @param accessControl access control contract
+ * @param account account
+ * @param greenlistedRole greenlisted role
+ */
+ function requireGreenlisted(
+ IMidasAccessControl accessControl,
+ address account,
+ bytes32 greenlistedRole
+ ) internal view {
+ require(
+ accessControl.hasRole(greenlistedRole, account),
+ NotGreenlisted(account, greenlistedRole)
+ );
+ }
+
+ /**
+ * @dev checks that a given `account` doesnt have `blacklistedRole`
+ * @param accessControl access control contract
+ * @param account account
+ * @param blacklistedRole blacklisted role
+ */
+ function requireNotBlacklisted(
+ IMidasAccessControl accessControl,
+ address account,
+ bytes32 blacklistedRole
+ ) internal view {
+ require(
+ !accessControl.hasRole(blacklistedRole, account),
+ Blacklisted(blacklistedRole, account)
+ );
+ }
+
+ /**
+ * @dev resolves the access role based on the shortest delay
+ * @param accessControl access control contract
+ * @param rootRole root role
+ * @param functionRoleKey function key
+ * @param overrideDelay override delay
+ * @return roleUsed role used to validate the function access
+ */
+ function resolveAccessRole(
+ IMidasAccessControl accessControl,
+ bytes32 rootRole,
+ bytes32 functionRoleKey,
+ uint32 overrideDelay
+ ) internal view returns (bytes32 roleUsed) {
+ if (overrideDelay != NULL_DELAY) {
+ return rootRole;
+ }
+ (uint32 rootDelay, ) = accessControl.getRoleTimelockDelay(
+ rootRole,
+ overrideDelay
+ );
+ (uint32 functionDelay, ) = accessControl.getRoleTimelockDelay(
+ functionRoleKey,
+ overrideDelay
+ );
+ return rootDelay <= functionDelay ? rootRole : functionRoleKey;
+ }
+
+ /**
+ * @notice validates that the delay is within the maximum delay
+ * @param delay delay to validate
+ */
+ function validateTimelockDelay(uint32 delay) internal view {
+ if (delay != NO_DELAY) {
+ require(delay <= MAX_DELAY, InvalidDelay());
+ }
+ }
+
+ /**
+ * @dev checks that given `account` has function permission for the given function selector
+ * @param accessControl access control contract
+ * @param role OZ role for the scope
+ * @param targetContract scoped contract
+ * @param functionSelector function selector
+ * @param account address checked for permission
+ */
+ function _hasFunctionPermission(
+ IMidasAccessControl accessControl,
+ address targetContract,
+ bytes32 role,
+ bytes4 functionSelector,
+ address account
+ ) private view returns (bytes32 key, bool hasPermission) {
+ key = accessControl.permissionRoleKey(
+ role,
+ targetContract,
+ functionSelector
+ );
+
+ hasPermission = accessControl.hasFunctionPermission(key, account);
+ }
+
+ /**
+ * @dev appends the proposer to the data
+ * @param data operation data
+ * @param proposer proposer address
+ * @return appended data
+ */
+ function appendProposer(bytes calldata data, address proposer)
+ internal
+ pure
+ returns (bytes memory)
+ {
+ return abi.encodePacked(data, proposer);
+ }
+
+ /**
+ * @dev resolves the proposer from the data
+ * @param data data
+ * @return proposer proposer address
+ */
+ function resolveProposer(bytes calldata data)
+ internal
+ pure
+ returns (address proposer)
+ {
+ return address(bytes20(data[data.length - 20:]));
+ }
+}
diff --git a/contracts/libraries/PauseGuardsLibrary.sol b/contracts/libraries/PauseGuardsLibrary.sol
new file mode 100644
index 00000000..6c32b154
--- /dev/null
+++ b/contracts/libraries/PauseGuardsLibrary.sol
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
+
+import {IMidasAccessControl} from "../interfaces/IMidasAccessControl.sol";
+import {IMidasPauseManager} from "../interfaces/IMidasPauseManager.sol";
+
+/**
+ * @title PauseGuardsLibrary
+ * @notice library for checking pause statuses
+ * @author RedDuck Software
+ */
+library PauseGuardsLibrary {
+ /**
+ * @notice error thrown when a function is paused
+ * @param contractAddr contract address
+ * @param fn function id
+ */
+ error Paused(address contractAddr, bytes4 fn);
+
+ /**
+ * @dev checks that a given `fn` is not paused
+ * @param fn function id
+ */
+ function requireFnNotPaused(IMidasAccessControl accessControl, bytes4 fn)
+ internal
+ view
+ {
+ require(
+ !IMidasPauseManager(accessControl.pauseManager()).isFunctionPaused(
+ address(this),
+ fn
+ ),
+ Paused(address(this), fn)
+ );
+ }
+
+ /**
+ * @dev checks that a given `fn` and contract/global are not paused
+ * @param fn function id
+ */
+ function requireNotPaused(IMidasAccessControl accessControl, bytes4 fn)
+ internal
+ view
+ {
+ require(
+ !IMidasPauseManager(accessControl.pauseManager()).isPaused(
+ address(this),
+ fn
+ ),
+ Paused(address(this), fn)
+ );
+ }
+}
diff --git a/contracts/libraries/RateLimitLibrary.sol b/contracts/libraries/RateLimitLibrary.sol
new file mode 100644
index 00000000..c9ed9d85
--- /dev/null
+++ b/contracts/libraries/RateLimitLibrary.sol
@@ -0,0 +1,247 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
+
+import {EnumerableSetUpgradeable as EnumerableSet} from "@openzeppelin/contracts-upgradeable/utils/structs/EnumerableSetUpgradeable.sol";
+import {MathUpgradeable as Math} from "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol";
+
+/**
+ * @title RateLimitLibrary
+ * @author RedDuck Software
+ * @notice Multi-window linear-decay rate limiting (vault instant flows, mToken mint, etc.).
+ */
+library RateLimitLibrary {
+ using EnumerableSet for EnumerableSet.UintSet;
+
+ uint256 private constant _MIN_WINDOW = 1 minutes;
+
+ /**
+ * @notice when window limit is exceeded
+ * @param window window duration in seconds
+ * @param remaining actual remaining amount
+ * @param requested requested amount
+ */
+ error WindowLimitExceeded(
+ uint256 window,
+ uint256 remaining,
+ uint256 requested
+ );
+
+ /**
+ * @notice when window limit is unknown
+ * @param window window duration in seconds
+ */
+ error UnknownWindowLimit(uint256 window);
+
+ /**
+ * @notice when window is too short
+ * @param window window duration in seconds
+ */
+ error WindowTooShort(uint256 window);
+
+ /**
+ * @notice Emitted when a window limit is set or updated.
+ * @param window window duration in seconds
+ * @param limit max amount per window
+ */
+ event WindowLimitSet(uint256 window, uint256 limit);
+
+ /**
+ * @notice Emitted when a window limit is removed.
+ * @param window window duration in seconds
+ */
+ event WindowLimitRemoved(uint256 window);
+
+ /**
+ * @notice Per-window rate limit (linear decay over `window` seconds).
+ */
+ struct WindowRateLimitConfig {
+ /// @notice max amount per window
+ uint256 limit;
+ /// @notice amount currently counted against the limit
+ uint256 amountInFlight;
+ /// @notice last block timestamp when the limit was checked or updated
+ uint256 lastUpdated;
+ /// @notice window duration in seconds
+ uint256 window;
+ }
+
+ /**
+ * @notice Active windows and their configs (keyed by window duration).
+ */
+ struct WindowRateLimits {
+ EnumerableSet.UintSet windows;
+ mapping(uint256 => WindowRateLimitConfig) configs;
+ }
+
+ /**
+ * @notice Snapshot for one window (view helper).
+ */
+ struct WindowRateLimitStatus {
+ /// @notice decayed in-flight amount
+ uint256 inFlight;
+ /// @notice headroom under `limit`
+ uint256 remaining;
+ /// @notice last update timestamp
+ uint256 lastUpdated;
+ /// @notice window duration in seconds
+ uint256 window;
+ /// @notice max amount per window
+ uint256 limit;
+ }
+
+ /**
+ * @notice Returns a status row per configured window (enumeration order).
+ * @param limits aggregated window state
+ * @return statuses one entry per active window
+ */
+ function getWindowStatuses(WindowRateLimits storage limits)
+ internal
+ view
+ returns (WindowRateLimitStatus[] memory statuses)
+ {
+ uint256 n = limits.windows.length();
+ statuses = new WindowRateLimitStatus[](n);
+ for (uint256 i = 0; i < n; ++i) {
+ uint256 window = limits.windows.at(i);
+ statuses[i] = _getWindowStatus(limits.configs[window]);
+ }
+ }
+
+ /**
+ * @notice Sets or updates the limit for a window (checkpoints first).
+ * @param limits aggregated window state
+ * @param window window duration in seconds
+ * @param limit max amount per window
+ * @return previousLimit previous limit
+ */
+ function setWindowLimit(
+ WindowRateLimits storage limits,
+ uint256 window,
+ uint256 limit
+ ) internal returns (uint256 previousLimit) {
+ if (limits.windows.add(window)) {
+ require(window >= _MIN_WINDOW, WindowTooShort(window));
+ }
+
+ WindowRateLimitConfig storage cfg = limits.configs[window];
+
+ previousLimit = cfg.limit;
+
+ cfg.window = window;
+ cfg.limit = limit;
+
+ _consumeWindowLimit(cfg, 0);
+
+ emit WindowLimitSet(window, limit);
+ }
+
+ /**
+ * @notice Removes a window and clears its config.
+ * @param limits aggregated window state
+ * @param window window duration in seconds
+ */
+ function removeWindowLimit(WindowRateLimits storage limits, uint256 window)
+ internal
+ {
+ require(limits.windows.remove(window), UnknownWindowLimit(window));
+ delete limits.configs[window];
+ emit WindowLimitRemoved(window);
+ }
+
+ /**
+ * @notice Charges `amount` against every window (reverts if any lacks headroom).
+ * @param limits aggregated window state
+ * @param amount amount to charge
+ */
+ function consumeLimit(WindowRateLimits storage limits, uint256 amount)
+ internal
+ {
+ uint256 n = limits.windows.length();
+ for (uint256 i = 0; i < n; ++i) {
+ uint256 window = limits.windows.at(i);
+ _consumeWindowLimit(limits.configs[window], amount);
+ }
+ }
+
+ /**
+ * @notice Decayed in-flight and remaining headroom for one window.
+ * @param cfg stored window config
+ * @return status window status snapshot
+ */
+ function _getWindowStatus(WindowRateLimitConfig storage cfg)
+ private
+ view
+ returns (WindowRateLimitStatus memory status)
+ {
+ (uint256 inFlight, uint256 remaining) = _availableCapacity(
+ cfg.amountInFlight,
+ cfg.lastUpdated,
+ cfg.limit,
+ cfg.window
+ );
+
+ status = WindowRateLimitStatus({
+ inFlight: inFlight,
+ remaining: remaining,
+ lastUpdated: cfg.lastUpdated,
+ window: cfg.window,
+ limit: cfg.limit
+ });
+ }
+
+ /**
+ * @notice Linear decay (`limit * elapsed / window`, floored) then headroom.
+ * @param amountInFlight stored in-flight amount
+ * @param lastUpdated last update timestamp
+ * @param limit max per window
+ * @param window window duration in seconds
+ * @return inFlight decayed in-flight
+ * @return remaining capacity still available under `limit`
+ */
+ function _availableCapacity(
+ uint256 amountInFlight,
+ uint256 lastUpdated,
+ uint256 limit,
+ uint256 window
+ ) private view returns (uint256 inFlight, uint256 remaining) {
+ uint256 elapsed = block.timestamp - lastUpdated;
+
+ uint256 decay = Math.mulDiv(limit, elapsed, window, Math.Rounding.Down);
+
+ inFlight = amountInFlight <= decay ? 0 : amountInFlight - decay;
+
+ remaining = limit <= inFlight ? 0 : limit - inFlight;
+ }
+
+ /**
+ * @notice Checks headroom, then adds `amount` to in-flight and refreshes timestamp.
+ * @param cfg stored window config
+ * @param amount amount to add to in-flight (may be zero to checkpoint)
+ */
+ function _consumeWindowLimit(
+ WindowRateLimitConfig storage cfg,
+ uint256 amount
+ ) private {
+ (
+ uint256 amountInFlight,
+ uint256 lastUpdated,
+ uint256 limit,
+ uint256 window
+ ) = (cfg.amountInFlight, cfg.lastUpdated, cfg.limit, cfg.window);
+
+ (uint256 inFlight, uint256 remaining) = _availableCapacity(
+ amountInFlight,
+ lastUpdated,
+ limit,
+ window
+ );
+
+ require(
+ amount <= remaining,
+ WindowLimitExceeded(window, remaining, amount)
+ );
+
+ cfg.amountInFlight = inFlight + amount;
+ cfg.lastUpdated = block.timestamp;
+ }
+}
diff --git a/contracts/libraries/RedemptionSwapperHelpersLibrary.sol b/contracts/libraries/RedemptionSwapperHelpersLibrary.sol
new file mode 100644
index 00000000..33da28e9
--- /dev/null
+++ b/contracts/libraries/RedemptionSwapperHelpersLibrary.sol
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
+
+import {IRedemptionVault} from "../interfaces/IRedemptionVault.sol";
+import {IERC20Upgradeable as IERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
+import {SafeERC20Upgradeable as SafeERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
+import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
+import {DecimalsCorrectionLibrary} from "./DecimalsCorrectionLibrary.sol";
+
+library RedemptionSwapperHelpersLibrary {
+ using SafeERC20 for IERC20;
+ using DecimalsCorrectionLibrary for uint256;
+
+ function getSwapperDetails(
+ IRedemptionVault _redemptionVault,
+ address _getBalanceOf
+ )
+ internal
+ view
+ returns (
+ uint256 mTokenARate,
+ IERC20 mTokenA,
+ uint256 mTokenABalance
+ )
+ {
+ mTokenARate = _redemptionVault.mTokenDataFeed().getDataInBase18();
+ mTokenA = IERC20(address(_redemptionVault.mToken()));
+ mTokenABalance = mTokenA.balanceOf(_getBalanceOf);
+ }
+
+ function redeemInstantSwapper(
+ IRedemptionVault _swapperVault,
+ IERC20 _mTokenA,
+ address _liquiditySource,
+ address _tokenOut,
+ uint256 _mTokenAAmount,
+ uint256 _tokenOutDecimals
+ ) internal returns (uint256) {
+ if (_liquiditySource != address(this)) {
+ _mTokenA.safeTransferFrom(
+ _liquiditySource,
+ address(this),
+ _mTokenAAmount
+ );
+ }
+
+ _mTokenA.safeIncreaseAllowance(address(_swapperVault), _mTokenAAmount);
+
+ return
+ _swapperVault
+ .redeemInstant(_tokenOut, _mTokenAAmount, 0)
+ .convertToBase18(_tokenOutDecimals);
+ }
+}
diff --git a/contracts/mToken.sol b/contracts/mToken.sol
index 4a6b8cfb..6961503d 100644
--- a/contracts/mToken.sol
+++ b/contracts/mToken.sol
@@ -1,35 +1,280 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
-import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PausableUpgradeable.sol";
+import {ERC20PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PausableUpgradeable.sol";
+import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
-import "./access/Blacklistable.sol";
-import "./interfaces/IMToken.sol";
+import {RateLimitLibrary} from "./libraries/RateLimitLibrary.sol";
+import {MidasAuthLibrary} from "./libraries/MidasAuthLibrary.sol";
+import {IMidasAccessControl} from "./interfaces/IMidasAccessControl.sol";
+import {PauseGuardsLibrary} from "./libraries/PauseGuardsLibrary.sol";
+import {MidasInitializable} from "./abstract/MidasInitializable.sol";
+import {WithMidasAccessControl} from "./access/WithMidasAccessControl.sol";
+import {Blacklistable} from "./access/Blacklistable.sol";
+import {IMToken} from "./interfaces/IMToken.sol";
/**
* @title mToken
* @author RedDuck Software
*/
//solhint-disable contract-name-camelcase
-abstract contract mToken is ERC20PausableUpgradeable, Blacklistable, IMToken {
+contract mToken is ERC20PausableUpgradeable, Blacklistable, IMToken {
+ using RateLimitLibrary for RateLimitLibrary.WindowRateLimits;
+ using MidasAuthLibrary for IMidasAccessControl;
+
+ /**
+ * @dev role that grants contract admin rights to the contract
+ * @custom:oz-upgrades-unsafe-allow state-variable-immutable
+ */
+ // solhint-disable-next-line var-name-mixedcase
+ bytes32 private immutable _CONTRACT_ADMIN_ROLE;
+
+ /**
+ * @dev role that grants minter rights to the contract
+ * @custom:oz-upgrades-unsafe-allow state-variable-immutable
+ */
+ // solhint-disable-next-line var-name-mixedcase
+ bytes32 private immutable _MINTER_ROLE;
+
+ /**
+ * @dev role that grants burner rights to the contract
+ * @custom:oz-upgrades-unsafe-allow state-variable-immutable
+ */
+ // solhint-disable-next-line var-name-mixedcase
+ bytes32 private immutable _BURNER_ROLE;
+
+ /**
+ * @dev role that grants greenlisted rights to the contract
+ * @custom:oz-upgrades-unsafe-allow state-variable-immutable
+ */
+ // solhint-disable-next-line var-name-mixedcase
+ bytes32 private immutable _GREENLISTED_ROLE;
+
+ /**
+ * @dev role that grants min balance exempt rights to the contract
+ * @custom:oz-upgrades-unsafe-allow state-variable-immutable
+ */
+ // solhint-disable-next-line var-name-mixedcase
+ bytes32 private immutable _MIN_BALANCE_EXEMPT_ROLE;
/**
* @notice metadata key => metadata value
*/
mapping(bytes32 => bytes) public metadata;
+ /**
+ * @notice mint rate limits state
+ */
+ RateLimitLibrary.WindowRateLimits private _mintRateLimits;
+
+ /**
+ * @notice address to which clawback tokens will be sent
+ */
+ address public clawbackReceiver;
+
+ /**
+ * @notice if true then current transfer is clawback operation
+ */
+ bool private _inClawback;
+
+ /**
+ * @notice name of the token
+ */
+ string private _name;
+
+ /**
+ * @notice symbol of the token
+ */
+ string private _symbol;
+
+ /**
+ * @notice if true then the token is permissioned
+ */
+ bool public isPermissioned;
+
+ /**
+ * @notice if true then the token has a minimum holding balance enforced
+ */
+ bool public isMinHoldingBalanceEnforced;
+
+ /**
+ * @notice maximum supply cap for the token
+ */
+ uint256 public override maxSupplyCap;
+
/**
* @dev leaving a storage gap for futures updates
*/
- uint256[50] private __gap;
+ uint256[42] private __gap;
+
+ /**
+ * @dev having a second gap here to match with the gap of previous implementations
+ */
+ uint256[50] private ___gap;
+
+ /**
+ * @dev havings a third gap here to match with the gap of previous implementations
+ */
+ uint256[50] private ____gap;
+
+ // TODO: can we remove 2nd and 3rd gaps somehow without disabling storage layout checks?
+
+ /**
+ * @notice constructor
+ * @param _contractAdminRole contract admin role
+ * @param _minterRole minter role
+ * @param _burnerRole burner role
+ * @param _greenlistedRole greenlisted role
+ * @param _minBalanceExemptRole min balance exempt role
+ * @custom:oz-upgrades-unsafe-allow constructor
+ */
+ constructor(
+ bytes32 _contractAdminRole,
+ bytes32 _minterRole,
+ bytes32 _burnerRole,
+ bytes32 _greenlistedRole,
+ bytes32 _minBalanceExemptRole
+ ) MidasInitializable() {
+ _CONTRACT_ADMIN_ROLE = _contractAdminRole;
+ _MINTER_ROLE = _minterRole;
+ _BURNER_ROLE = _burnerRole;
+ _GREENLISTED_ROLE = _greenlistedRole;
+ _MIN_BALANCE_EXEMPT_ROLE = _minBalanceExemptRole;
+ }
/**
* @notice upgradeable pattern contract`s initializer
* @param _accessControl address of MidasAccessControll contract
+ * @param _clawbackReceiver address to which clawback tokens will be sent
+ * @param name_ name of the token
+ * @param symbol_ symbol of the token
+ */
+ function initialize(
+ address _accessControl,
+ address _clawbackReceiver,
+ uint256 _maxSupplyCap,
+ bool _isPermissioned,
+ bool _isMinHoldingBalanceEnforced,
+ string memory name_,
+ string memory symbol_
+ ) external {
+ _initializeV1(_accessControl, name_, symbol_);
+ initializeV3(
+ _clawbackReceiver,
+ _maxSupplyCap,
+ _isPermissioned,
+ _isMinHoldingBalanceEnforced
+ );
+ }
+
+ /**
+ * @dev v1 initializer
+ * @param _accessControl address of MidasAccessControll contract
+ * @param name_ name of the token
+ * @param symbol_ symbol of the token
+ */
+ function _initializeV1(
+ address _accessControl,
+ string memory name_,
+ string memory symbol_
+ ) private initializer {
+ __WithMidasAccessControl_init(_accessControl);
+ __ERC20_init(name_, symbol_);
+ }
+
+ /**
+ * @notice v3 initializer
+ * @dev not v2 because some of the original product mTokens were upgraded to v2 already
+ * @param _clawbackReceiver address to which clawback tokens will be sent
+ * @param _maxSupplyCap maximum supply cap for the token
+ * @param _isPermissioned if true then the token is permissioned
+ * @param _isMinHoldingBalanceEnforced if true then the token has a minimum holding balance enforced
+ */
+ function initializeV3(
+ address _clawbackReceiver,
+ uint256 _maxSupplyCap,
+ bool _isPermissioned,
+ bool _isMinHoldingBalanceEnforced
+ ) public reinitializer(3) onlyProxyAdmin {
+ require(
+ _clawbackReceiver != address(0),
+ InvalidAddress(_clawbackReceiver)
+ );
+
+ maxSupplyCap = _maxSupplyCap;
+ clawbackReceiver = _clawbackReceiver;
+
+ // to make upgrades safer, we sync the name and symbol from the ERC20Upgradeable
+ _name = ERC20Upgradeable.name();
+ _symbol = ERC20Upgradeable.symbol();
+
+ isPermissioned = _isPermissioned;
+ isMinHoldingBalanceEnforced = _isMinHoldingBalanceEnforced;
+ }
+
+ /**
+ * @inheritdoc IMToken
+ */
+ function setNameSymbol(string memory name_, string memory symbol_)
+ external
+ onlyRoleDelayOverride(contractAdminRole(), 2 days, false)
+ {
+ _name = name_;
+ _symbol = symbol_;
+ }
+
+ /**
+ * @inheritdoc IMToken
+ */
+ function setIsPermissioned(bool _isPermissioned)
+ external
+ onlyRoleDelayOverride(contractAdminRole(), 2 days, false)
+ {
+ if (isPermissioned == _isPermissioned) {
+ return;
+ }
+
+ isPermissioned = _isPermissioned;
+
+ emit SetIsPermissioned(_isPermissioned);
+ }
+
+ /**
+ * @inheritdoc IMToken
+ */
+ function setMinHoldingBalanceEnforced(bool _isMinHoldingBalanceEnforced)
+ external
+ onlyRoleDelayOverride(contractAdminRole(), 2 days, false)
+ {
+ if (isMinHoldingBalanceEnforced == _isMinHoldingBalanceEnforced) {
+ return;
+ }
+
+ isMinHoldingBalanceEnforced = _isMinHoldingBalanceEnforced;
+
+ emit SetIsMinHoldingBalanceEnforced(_isMinHoldingBalanceEnforced);
+ }
+
+ /**
+ * @inheritdoc IMToken
*/
- function initialize(address _accessControl) external virtual initializer {
- __Blacklistable_init(_accessControl);
- (string memory _name, string memory _symbol) = _getNameSymbol();
- __ERC20_init(_name, _symbol);
+ function setMaxSupplyCap(uint256 _maxSupplyCap) external onlyContractAdmin {
+ maxSupplyCap = _maxSupplyCap;
+ emit SetMaxSupplyCap(_maxSupplyCap);
+ }
+
+ /**
+ * @inheritdoc IMToken
+ */
+ function setClawbackReceiver(address _clawbackReceiver)
+ external
+ onlyContractAdmin
+ {
+ require(
+ _clawbackReceiver != address(0),
+ InvalidAddress(_clawbackReceiver)
+ );
+ clawbackReceiver = _clawbackReceiver;
+ emit ClawbackReceiverSet(_clawbackReceiver);
}
/**
@@ -37,7 +282,7 @@ abstract contract mToken is ERC20PausableUpgradeable, Blacklistable, IMToken {
*/
function mint(address to, uint256 amount)
external
- onlyRole(_minterRole(), msg.sender)
+ onlyRoleNoTimelock(minterRole(), false)
{
_mint(to, amount);
}
@@ -45,11 +290,27 @@ abstract contract mToken is ERC20PausableUpgradeable, Blacklistable, IMToken {
/**
* @inheritdoc IMToken
*/
- function burn(address from, uint256 amount)
+ function mintGoverned(address to, uint256 amount)
external
- onlyRole(_burnerRole(), msg.sender)
+ onlyContractAdmin
+ {
+ _mint(to, amount);
+ }
+
+ /**
+ * @inheritdoc IMToken
+ */
+ function burn(address from, uint256 amount)
+ public
+ virtual
+ onlyRoleNoTimelock(burnerRole(), false)
{
_onlyNotBlacklisted(from);
+
+ if (isPermissioned) {
+ _onlyGreenlisted(from);
+ }
+
_burn(from, amount);
}
@@ -58,7 +319,7 @@ abstract contract mToken is ERC20PausableUpgradeable, Blacklistable, IMToken {
*/
function burnGoverned(address from, uint256 amount)
external
- onlyRole(_burnerRole(), msg.sender)
+ onlyContractAdmin
{
_burn(from, amount);
}
@@ -66,25 +327,146 @@ abstract contract mToken is ERC20PausableUpgradeable, Blacklistable, IMToken {
/**
* @inheritdoc IMToken
*/
- function pause() external override onlyRole(_pauserRole(), msg.sender) {
- _pause();
+ function clawback(uint256 amount, address from) external onlyContractAdmin {
+ address to = clawbackReceiver;
+ _inClawback = true;
+ _transfer(from, to, amount);
+ _inClawback = false;
+ emit Clawback(from, to, amount);
}
/**
* @inheritdoc IMToken
*/
- function unpause() external override onlyRole(_pauserRole(), msg.sender) {
- _unpause();
+ function setMetadata(bytes32 key, bytes memory data)
+ external
+ onlyContractAdmin
+ {
+ metadata[key] = data;
+ emit SetMetadata(key, data);
}
/**
* @inheritdoc IMToken
*/
- function setMetadata(bytes32 key, bytes memory data)
+ function increaseMintRateLimit(uint256 window, uint256 newLimit)
external
- onlyRole(DEFAULT_ADMIN_ROLE, msg.sender)
+ onlyContractAdmin
{
- metadata[key] = data;
+ _setMintRateLimitConfig(window, newLimit, true);
+ }
+
+ /**
+ * @inheritdoc IMToken
+ */
+ function decreaseMintRateLimit(uint256 window, uint256 newLimit)
+ external
+ onlyContractAdmin
+ {
+ _setMintRateLimitConfig(window, newLimit, false);
+ }
+
+ /**
+ * @inheritdoc IMToken
+ */
+ function removeMintRateLimitConfig(uint256 window)
+ external
+ onlyContractAdmin
+ {
+ _mintRateLimits.removeWindowLimit(window);
+ }
+
+ /**
+ * @notice returns array of mint rate limit configs
+ * @return statuses array of mint rate limit statuses
+ */
+ function getMintRateLimitStatuses()
+ external
+ view
+ returns (
+ RateLimitLibrary.WindowRateLimitStatus[] memory /* statuses */
+ )
+ {
+ return _mintRateLimits.getWindowStatuses();
+ }
+
+ /**
+ * @notice AC role, owner of which can mint mToken token
+ */
+ function minterRole() public view returns (bytes32) {
+ return _MINTER_ROLE;
+ }
+
+ /**
+ * @notice AC role, owner of which can burn mToken token
+ */
+ function burnerRole() public view returns (bytes32) {
+ return _BURNER_ROLE;
+ }
+
+ /**
+ * @inheritdoc WithMidasAccessControl
+ */
+ function contractAdminRole() public view override returns (bytes32) {
+ return _CONTRACT_ADMIN_ROLE;
+ }
+
+ /**
+ * @inheritdoc IMToken
+ */
+ function greenlistedRole() public view returns (bytes32) {
+ return _GREENLISTED_ROLE;
+ }
+
+ /**
+ * @inheritdoc IMToken
+ */
+ function minBalanceExemptRole() public view returns (bytes32) {
+ return _MIN_BALANCE_EXEMPT_ROLE;
+ }
+
+ /**
+ * @inheritdoc ERC20Upgradeable
+ */
+ function name() public view override returns (string memory) {
+ return _name;
+ }
+
+ /**
+ * @inheritdoc ERC20Upgradeable
+ */
+ function symbol() public view override returns (string memory) {
+ return _symbol;
+ }
+
+ /**
+ * @dev overrides _mint function to validate the maximum supply cap
+ * @param to address of the recipient
+ * @param amount amount of tokens to mint
+ */
+ function _mint(address to, uint256 amount) internal override {
+ super._mint(to, amount);
+ _validateMaxSupplyCap();
+ }
+
+ /**
+ * @dev set mint rate limit config
+ * @param window window duration in seconds
+ * @param limit limit amount per window
+ * @param increaseOnly if true - only increase the limit, if false - only decrease the limit
+ */
+ function _setMintRateLimitConfig(
+ uint256 window,
+ uint256 limit,
+ bool increaseOnly
+ ) private {
+ uint256 previousLimit = _mintRateLimits.setWindowLimit(window, limit);
+
+ bool isNewLimitValid = increaseOnly
+ ? limit > previousLimit
+ : limit < previousLimit;
+
+ require(isNewLimitValid, InvalidNewLimit(limit, previousLimit));
}
/**
@@ -95,37 +477,111 @@ abstract contract mToken is ERC20PausableUpgradeable, Blacklistable, IMToken {
address from,
address to,
uint256 amount
- ) internal virtual override(ERC20PausableUpgradeable) {
+ ) internal override(ERC20PausableUpgradeable) {
+ PauseGuardsLibrary.requireNotPaused(accessControl, msg.sig);
+ ERC20PausableUpgradeable._beforeTokenTransfer(from, to, amount);
+
if (to != address(0)) {
- _onlyNotBlacklisted(from);
+ if (!_inClawback && from != address(0)) {
+ _onlyNotBlacklisted(from);
+ }
_onlyNotBlacklisted(to);
}
- ERC20PausableUpgradeable._beforeTokenTransfer(from, to, amount);
+ _validatePermissioned(from, to);
+
+ // if minting, check and update mint rate limit
+ if (from == address(0)) {
+ _mintRateLimits.consumeLimit(amount);
+ }
}
/**
- * @dev returns name and symbol of the token
- * @return name of the token
- * @return symbol of the token
+ * @dev overrides _afterTokenTransfer function to run custom validations
+ * @param from address of the sender
+ * @param to address of the recipient
+ * @param amount amount of tokens transferred
*/
- function _getNameSymbol()
- internal
- virtual
- returns (string memory, string memory);
+ function _afterTokenTransfer(
+ address from,
+ address to,
+ uint256 amount
+ ) internal override {
+ _validateMinBalance(from, to);
+ super._afterTokenTransfer(from, to, amount);
+ }
+
+ /**
+ * @dev validates that the total supply is less than or equal to the maximum supply cap
+ */
+ function _validateMaxSupplyCap() private view {
+ require(totalSupply() <= maxSupplyCap, MaxSupplyCapExceeded());
+ }
+
+ /**
+ * @dev validates the minimum balance of a user
+ * @param from address of the sender
+ * @param to address of the recipient
+ */
+ function _validateMinBalance(address from, address to) private view {
+ if (!isMinHoldingBalanceEnforced) {
+ return;
+ }
+
+ if (from != address(0) && !_isMinBalanceExempt(from)) {
+ _validateUserMinBalance(from);
+ }
+
+ if (to != address(0) && !_isMinBalanceExempt(to)) {
+ _validateUserMinBalance(to);
+ }
+ }
/**
- * @dev AC role, owner of which can mint mToken token
+ * @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 _minterRole() internal pure virtual returns (bytes32);
+ function _isMinBalanceExempt(address user) private view returns (bool) {
+ return accessControl.hasRole(minBalanceExemptRole(), user);
+ }
/**
- * @dev AC role, owner of which can burn mToken token
+ * @dev validates the minimum balance of a user
+ * @param user address of the user
*/
- function _burnerRole() internal pure virtual returns (bytes32);
+ function _validateUserMinBalance(address user) private view {
+ uint256 balance = balanceOf(user);
+ require(balance == 0 || balance >= 1 ether, MinBalanceNotMet(balance));
+ }
/**
- * @dev AC role, owner of which can pause mToken token
+ * @dev validates that the sender and recipient are permissioned
+ * @param from address of the sender
+ * @param to address of the recipient
*/
- function _pauserRole() internal pure virtual returns (bytes32);
+ function _validatePermissioned(address from, address to) private {
+ if (!isPermissioned) {
+ return;
+ }
+
+ if (to != address(0)) {
+ if (!_inClawback && from != address(0)) {
+ _onlyGreenlisted(from);
+ }
+
+ _onlyGreenlisted(to);
+ }
+ }
+
+ /**
+ * @dev checks that a given `account` has `greenlistedRole()`
+ */
+ function _onlyGreenlisted(address account) private view {
+ MidasAuthLibrary.requireGreenlisted(
+ accessControl,
+ account,
+ greenlistedRole()
+ );
+ }
}
diff --git a/contracts/mTokenPermissioned.sol b/contracts/mTokenPermissioned.sol
deleted file mode 100644
index 88d9a5a8..00000000
--- a/contracts/mTokenPermissioned.sol
+++ /dev/null
@@ -1,52 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "./mToken.sol";
-
-/**
- * @title mTokenPermissioned
- * @notice mToken with fully permissioned transfers
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-abstract contract mTokenPermissioned is mToken {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @dev overrides _beforeTokenTransfer function to allow
- * greenlisted users to use the token transfers functions
- */
- function _beforeTokenTransfer(
- address from,
- address to,
- uint256 amount
- ) internal virtual override(mToken) {
- if (to != address(0)) {
- if (from != address(0)) {
- _onlyGreenlisted(from);
- }
- _onlyGreenlisted(to);
- }
-
- mToken._beforeTokenTransfer(from, to, amount);
- }
-
- /**
- * @notice AC role of a greenlist
- * @return role bytes32 role
- */
- function _greenlistedRole() internal pure virtual returns (bytes32);
-
- /**
- * @dev checks that a given `account`
- * have `greenlistedRole()`
- */
- function _onlyGreenlisted(address account)
- private
- view
- onlyRole(_greenlistedRole(), account)
- {}
-}
diff --git a/contracts/misc/acre/AcreAdapter.sol b/contracts/misc/acre/AcreAdapter.sol
index 36639187..6d5dbcf9 100644
--- a/contracts/misc/acre/AcreAdapter.sol
+++ b/contracts/misc/acre/AcreAdapter.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20Metadata, IERC20} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
@@ -109,9 +109,12 @@ contract AcreAdapter is IAcreAdapter {
IERC20(share()).safeTransferFrom(msg.sender, address(this), shares);
- requestId = IRedemptionVault(redemptionVault).redeemRequest(
+ (requestId, ) = IRedemptionVault(redemptionVault).redeemRequest(
asset(),
shares,
+ receiver,
+ 0,
+ 0,
receiver
);
diff --git a/contracts/misc/adapters/BandStdChailinkAdapter.sol b/contracts/misc/adapters/BandStdChailinkAdapter.sol
index 1af56d19..5beb65ee 100644
--- a/contracts/misc/adapters/BandStdChailinkAdapter.sol
+++ b/contracts/misc/adapters/BandStdChailinkAdapter.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "./ChainlinkAdapterBase.sol";
@@ -51,7 +51,7 @@ contract BandStdChailinkAdapter is ChainlinkAdapterBase {
}
function latestTimestamp() public view override returns (uint256) {
- return _getBandReferenceData().lastUpdatedBase;
+ return _getTimestamp(_getBandReferenceData());
}
function latestRoundData()
@@ -59,24 +59,31 @@ contract BandStdChailinkAdapter is ChainlinkAdapterBase {
view
override
returns (
- uint80 roundId,
- int256 answer,
- uint256 startedAt,
- uint256 updatedAt,
- uint80 answeredInRound
+ uint80, /* roundId */
+ int256, /* answer */
+ uint256, /* startedAt */
+ uint256, /* updatedAt */
+ uint80 /* answeredInRound */
)
{
IStdReference.ReferenceData memory value = _getBandReferenceData();
- roundId = uint80(value.lastUpdatedBase);
+ uint256 timestamp = _getTimestamp(value);
+ uint80 roundId = uint80(timestamp);
- return (
- roundId,
- int256(value.rate),
- value.lastUpdatedBase,
- value.lastUpdatedBase,
- roundId
- );
+ return (roundId, int256(value.rate), timestamp, timestamp, roundId);
+ }
+
+ function _getTimestamp(IStdReference.ReferenceData memory value)
+ private
+ view
+ returns (uint256)
+ {
+ // takes the minimum — stalest component determines freshness
+ return
+ value.lastUpdatedBase < value.lastUpdatedQuote
+ ? value.lastUpdatedBase
+ : value.lastUpdatedQuote;
}
function _getBandReferenceData()
diff --git a/contracts/misc/adapters/BeHypeChainlinkAdapter.sol b/contracts/misc/adapters/BeHypeChainlinkAdapter.sol
index f1fe9222..3eb1c74f 100644
--- a/contracts/misc/adapters/BeHypeChainlinkAdapter.sol
+++ b/contracts/misc/adapters/BeHypeChainlinkAdapter.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "./ChainlinkAdapterBase.sol";
diff --git a/contracts/misc/adapters/ChainlinkAdapterBase.sol b/contracts/misc/adapters/ChainlinkAdapterBase.sol
index 8ed7f942..9b7f1722 100644
--- a/contracts/misc/adapters/ChainlinkAdapterBase.sol
+++ b/contracts/misc/adapters/ChainlinkAdapterBase.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
diff --git a/contracts/misc/adapters/CompositeDataFeedToBandStdAdapter.sol b/contracts/misc/adapters/CompositeDataFeedToBandStdAdapter.sol
index c549296d..6f9a4ad6 100644
--- a/contracts/misc/adapters/CompositeDataFeedToBandStdAdapter.sol
+++ b/contracts/misc/adapters/CompositeDataFeedToBandStdAdapter.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "../../interfaces/IDataFeed.sol";
import "../../feeds/CompositeDataFeed.sol";
diff --git a/contracts/misc/adapters/DataFeedToBandStdAdapter.sol b/contracts/misc/adapters/DataFeedToBandStdAdapter.sol
index 9f3e1860..34aa0abb 100644
--- a/contracts/misc/adapters/DataFeedToBandStdAdapter.sol
+++ b/contracts/misc/adapters/DataFeedToBandStdAdapter.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import "../../interfaces/IDataFeed.sol";
diff --git a/contracts/misc/adapters/ERC4626ChainlinkAdapter.sol b/contracts/misc/adapters/ERC4626ChainlinkAdapter.sol
index c63ba72f..f151a20b 100644
--- a/contracts/misc/adapters/ERC4626ChainlinkAdapter.sol
+++ b/contracts/misc/adapters/ERC4626ChainlinkAdapter.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "@openzeppelin/contracts/interfaces/IERC4626.sol";
import "./ChainlinkAdapterBase.sol";
diff --git a/contracts/misc/adapters/MantleLspStakingChainlinkAdapter.sol b/contracts/misc/adapters/MantleLspStakingChainlinkAdapter.sol
index 0bace543..5655cc0b 100644
--- a/contracts/misc/adapters/MantleLspStakingChainlinkAdapter.sol
+++ b/contracts/misc/adapters/MantleLspStakingChainlinkAdapter.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "./ChainlinkAdapterBase.sol";
diff --git a/contracts/misc/adapters/PythChainlinkAdapter.sol b/contracts/misc/adapters/PythChainlinkAdapter.sol
index 69b423d5..2c601a76 100644
--- a/contracts/misc/adapters/PythChainlinkAdapter.sol
+++ b/contracts/misc/adapters/PythChainlinkAdapter.sol
@@ -1,6 +1,6 @@
-// SPDX-License-Identifier: Apache 2
+// SPDX-License-Identifier: BUSL-1.1
-pragma solidity 0.8.9;
+pragma solidity 0.8.34;
import "./ChainlinkAdapterBase.sol";
diff --git a/contracts/misc/adapters/RsEthChainlinkAdapter.sol b/contracts/misc/adapters/RsEthChainlinkAdapter.sol
index 873b1f65..6da95955 100644
--- a/contracts/misc/adapters/RsEthChainlinkAdapter.sol
+++ b/contracts/misc/adapters/RsEthChainlinkAdapter.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "./ChainlinkAdapterBase.sol";
diff --git a/contracts/misc/adapters/StorkChainlinkAdapter.sol b/contracts/misc/adapters/StorkChainlinkAdapter.sol
index 0ae67dfc..162f74c4 100644
--- a/contracts/misc/adapters/StorkChainlinkAdapter.sol
+++ b/contracts/misc/adapters/StorkChainlinkAdapter.sol
@@ -1,6 +1,6 @@
-// SPDX-License-Identifier: Apache 2
+// SPDX-License-Identifier: BUSL-1.1
-pragma solidity 0.8.9;
+pragma solidity 0.8.34;
import "./ChainlinkAdapterBase.sol";
diff --git a/contracts/misc/adapters/SyrupChainlinkAdapter.sol b/contracts/misc/adapters/SyrupChainlinkAdapter.sol
index 6bdc6b26..5047cfd3 100644
--- a/contracts/misc/adapters/SyrupChainlinkAdapter.sol
+++ b/contracts/misc/adapters/SyrupChainlinkAdapter.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "./ERC4626ChainlinkAdapter.sol";
diff --git a/contracts/misc/adapters/WrappedEEthChainlinkAdapter.sol b/contracts/misc/adapters/WrappedEEthChainlinkAdapter.sol
index 91d1baea..19e9b531 100644
--- a/contracts/misc/adapters/WrappedEEthChainlinkAdapter.sol
+++ b/contracts/misc/adapters/WrappedEEthChainlinkAdapter.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "./ChainlinkAdapterBase.sol";
diff --git a/contracts/misc/adapters/WstEthChainlinkAdapter.sol b/contracts/misc/adapters/WstEthChainlinkAdapter.sol
index bb48cc37..866b49e8 100644
--- a/contracts/misc/adapters/WstEthChainlinkAdapter.sol
+++ b/contracts/misc/adapters/WstEthChainlinkAdapter.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "./ChainlinkAdapterBase.sol";
diff --git a/contracts/misc/adapters/YInjChainlinkAdapter.sol b/contracts/misc/adapters/YInjChainlinkAdapter.sol
index 22fd5d9d..2f6d5a88 100644
--- a/contracts/misc/adapters/YInjChainlinkAdapter.sol
+++ b/contracts/misc/adapters/YInjChainlinkAdapter.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "./ChainlinkAdapterBase.sol";
diff --git a/contracts/misc/axelar/MidasAxelarVaultExecutable.sol b/contracts/misc/axelar/MidasAxelarVaultExecutable.sol
index 893ae6f0..34032673 100644
--- a/contracts/misc/axelar/MidasAxelarVaultExecutable.sol
+++ b/contracts/misc/axelar/MidasAxelarVaultExecutable.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.22;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import {InterchainTokenExecutable} from "@axelar-network/interchain-token-service/contracts/executable/InterchainTokenExecutable.sol";
import {IInterchainTokenService} from "@axelar-network/interchain-token-service/contracts/interfaces/IInterchainTokenService.sol";
diff --git a/contracts/misc/axelar/interfaces/IMidasAxelarVaultExecutable.sol b/contracts/misc/axelar/interfaces/IMidasAxelarVaultExecutable.sol
index 894c2208..af6663e3 100644
--- a/contracts/misc/axelar/interfaces/IMidasAxelarVaultExecutable.sol
+++ b/contracts/misc/axelar/interfaces/IMidasAxelarVaultExecutable.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.22;
+pragma solidity 0.8.34;
import {IDepositVault} from "../../../interfaces/IDepositVault.sol";
import {IRedemptionVault} from "../../../interfaces/IRedemptionVault.sol";
diff --git a/contracts/misc/imports.sol b/contracts/misc/imports.sol
index c2b373fc..70b0031c 100644
--- a/contracts/misc/imports.sol
+++ b/contracts/misc/imports.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
diff --git a/contracts/misc/layerzero/MidasLzMintBurnOFTAdapter.sol b/contracts/misc/layerzero/MidasLzMintBurnOFTAdapter.sol
index e1b8eee1..a287ca57 100644
--- a/contracts/misc/layerzero/MidasLzMintBurnOFTAdapter.sol
+++ b/contracts/misc/layerzero/MidasLzMintBurnOFTAdapter.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: UNLICENSED
-pragma solidity ^0.8.22;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {MintBurnOFTAdapter} from "@layerzerolabs/oft-evm/contracts/MintBurnOFTAdapter.sol";
diff --git a/contracts/misc/layerzero/MidasLzOFT.sol b/contracts/misc/layerzero/MidasLzOFT.sol
index 7ef9be6c..87308b4f 100644
--- a/contracts/misc/layerzero/MidasLzOFT.sol
+++ b/contracts/misc/layerzero/MidasLzOFT.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: UNLICENSED
-pragma solidity ^0.8.22;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {OFT} from "@layerzerolabs/oft-evm/contracts/OFT.sol";
diff --git a/contracts/misc/layerzero/MidasLzOFTAdapter.sol b/contracts/misc/layerzero/MidasLzOFTAdapter.sol
index 5e34f88f..f045052c 100644
--- a/contracts/misc/layerzero/MidasLzOFTAdapter.sol
+++ b/contracts/misc/layerzero/MidasLzOFTAdapter.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: UNLICENSED
-pragma solidity ^0.8.22;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {OFTAdapter} from "@layerzerolabs/oft-evm/contracts/OFTAdapter.sol";
diff --git a/contracts/misc/layerzero/MidasLzVaultComposerSync.sol b/contracts/misc/layerzero/MidasLzVaultComposerSync.sol
index ce47b699..b1638ea6 100644
--- a/contracts/misc/layerzero/MidasLzVaultComposerSync.sol
+++ b/contracts/misc/layerzero/MidasLzVaultComposerSync.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.22;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import {SafeERC20Upgradeable as SafeERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import {IERC20MetadataUpgradeable as IERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol";
diff --git a/contracts/misc/layerzero/interfaces/IMidasLzVaultComposerSync.sol b/contracts/misc/layerzero/interfaces/IMidasLzVaultComposerSync.sol
index e11c2439..602b9f17 100644
--- a/contracts/misc/layerzero/interfaces/IMidasLzVaultComposerSync.sol
+++ b/contracts/misc/layerzero/interfaces/IMidasLzVaultComposerSync.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.22;
+pragma solidity 0.8.34;
import {IOAppComposer} from "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppComposer.sol";
import {SendParam, MessagingFee} from "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol";
diff --git a/contracts/mocks/AaveV3PoolMock.sol b/contracts/mocks/AaveV3PoolMock.sol
index 9d962e7f..901836e2 100644
--- a/contracts/mocks/AaveV3PoolMock.sol
+++ b/contracts/mocks/AaveV3PoolMock.sol
@@ -1,6 +1,6 @@
// solhint-disable
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
diff --git a/contracts/mocks/AggregatorV3DeprecatedMock.sol b/contracts/mocks/AggregatorV3DeprecatedMock.sol
index dba4978e..e2d15e70 100644
--- a/contracts/mocks/AggregatorV3DeprecatedMock.sol
+++ b/contracts/mocks/AggregatorV3DeprecatedMock.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PausableUpgradeable.sol";
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
diff --git a/contracts/mocks/AggregatorV3Mock.sol b/contracts/mocks/AggregatorV3Mock.sol
index 15eecfbd..306d76eb 100644
--- a/contracts/mocks/AggregatorV3Mock.sol
+++ b/contracts/mocks/AggregatorV3Mock.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PausableUpgradeable.sol";
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
diff --git a/contracts/mocks/AggregatorV3UnhealthyMock.sol b/contracts/mocks/AggregatorV3UnhealthyMock.sol
index 02eb5436..f0751857 100644
--- a/contracts/mocks/AggregatorV3UnhealthyMock.sol
+++ b/contracts/mocks/AggregatorV3UnhealthyMock.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PausableUpgradeable.sol";
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
diff --git a/contracts/mocks/AxelarInterchainTokenServiceMock.sol b/contracts/mocks/AxelarInterchainTokenServiceMock.sol
index 1754df03..baf930e8 100644
--- a/contracts/mocks/AxelarInterchainTokenServiceMock.sol
+++ b/contracts/mocks/AxelarInterchainTokenServiceMock.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
diff --git a/contracts/mocks/ERC20Mock.sol b/contracts/mocks/ERC20Mock.sol
index 0d1f2175..4ad2f444 100644
--- a/contracts/mocks/ERC20Mock.sol
+++ b/contracts/mocks/ERC20Mock.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
diff --git a/contracts/mocks/ERC20MockWithName.sol b/contracts/mocks/ERC20MockWithName.sol
index 45f1c15e..1ee443ee 100644
--- a/contracts/mocks/ERC20MockWithName.sol
+++ b/contracts/mocks/ERC20MockWithName.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
diff --git a/contracts/mocks/LzEndpointV2Mock.sol b/contracts/mocks/LzEndpointV2Mock.sol
index bea86c22..a2c8f7d7 100644
--- a/contracts/mocks/LzEndpointV2Mock.sol
+++ b/contracts/mocks/LzEndpointV2Mock.sol
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: UNLICENSED
// solhint-disable
-pragma solidity ^0.8.22;
+pragma solidity 0.8.34;
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {ILayerZeroEndpointV2, MessagingParams, MessagingReceipt, MessagingFee, Origin} from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol";
diff --git a/contracts/mocks/MorphoVaultMock.sol b/contracts/mocks/MorphoVaultMock.sol
index 8c8f58d9..905d1326 100644
--- a/contracts/mocks/MorphoVaultMock.sol
+++ b/contracts/mocks/MorphoVaultMock.sol
@@ -1,6 +1,6 @@
// solhint-disable
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
@@ -69,11 +69,20 @@ contract MorphoVaultMock is ERC20 {
shares = (assets * RATE_PRECISION) / exchangeRateNumerator;
}
+ function redeem(
+ uint256 shares,
+ address receiver,
+ address owner
+ ) external returns (uint256 assets) {
+ assets = convertToAssets(shares);
+ withdraw(assets, receiver, owner);
+ }
+
function withdraw(
uint256 assets,
address receiver,
address owner
- ) external returns (uint256 shares) {
+ ) public returns (uint256 shares) {
shares = previewWithdraw(assets);
require(
@@ -105,7 +114,7 @@ contract MorphoVaultMock is ERC20 {
}
function convertToAssets(uint256 shares)
- external
+ public
view
returns (uint256 assets)
{
diff --git a/contracts/mocks/RedemptionTest.sol b/contracts/mocks/RedemptionTest.sol
deleted file mode 100644
index f493b5db..00000000
--- a/contracts/mocks/RedemptionTest.sol
+++ /dev/null
@@ -1,25 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import {IERC20Upgradeable as IERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
-import "../interfaces/buidl/IRedemption.sol";
-
-import "hardhat/console.sol";
-
-contract RedemptionTest is IRedemption {
- address public asset;
-
- address public liquidity;
-
- constructor(address _asset, address _liquidity) {
- asset = _asset;
- liquidity = _liquidity;
- }
-
- function settlement() external view returns (address) {}
-
- function redeem(uint256 amount) external {
- IERC20(asset).transferFrom(msg.sender, address(this), amount);
- IERC20(liquidity).transfer(msg.sender, amount);
- }
-}
diff --git a/contracts/mocks/SanctionsListTest.sol b/contracts/mocks/SanctionsListTest.sol
index 97747927..3f53eafb 100644
--- a/contracts/mocks/SanctionsListTest.sol
+++ b/contracts/mocks/SanctionsListTest.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "../interfaces/ISanctionsList.sol";
diff --git a/contracts/mocks/USTBMock.sol b/contracts/mocks/USTBMock.sol
index 337cc2d2..f545c591 100644
--- a/contracts/mocks/USTBMock.sol
+++ b/contracts/mocks/USTBMock.sol
@@ -1,11 +1,10 @@
// solhint-disable
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../interfaces/ustb/ISuperstateToken.sol";
-import "hardhat/console.sol";
contract USTBMock is ERC20, ISuperstateToken {
using SafeERC20 for IERC20;
diff --git a/contracts/mocks/USTBRedemptionMock.sol b/contracts/mocks/USTBRedemptionMock.sol
index fc186a11..9079f205 100644
--- a/contracts/mocks/USTBRedemptionMock.sol
+++ b/contracts/mocks/USTBRedemptionMock.sol
@@ -1,6 +1,6 @@
// solhint-disable
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
diff --git a/contracts/mocks/YInjOracleMock.sol b/contracts/mocks/YInjOracleMock.sol
index 90481c96..a97c7098 100644
--- a/contracts/mocks/YInjOracleMock.sol
+++ b/contracts/mocks/YInjOracleMock.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
contract YInjOracleMock {
uint256 private _rate;
diff --git a/contracts/products/JIV/JIV.sol b/contracts/products/JIV/JIV.sol
deleted file mode 100644
index 9d2e762c..00000000
--- a/contracts/products/JIV/JIV.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title JIV
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract JIV is mToken {
- /**
- * @notice actor that can mint JIV
- */
- bytes32 public constant JIV_MINT_OPERATOR_ROLE =
- keccak256("JIV_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn JIV
- */
- bytes32 public constant JIV_BURN_OPERATOR_ROLE =
- keccak256("JIV_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause JIV
- */
- bytes32 public constant JIV_PAUSE_OPERATOR_ROLE =
- keccak256("JIV_PAUSE_OPERATOR_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 ("Jaine Insurance Vault", "JIV");
- }
-
- /**
- * @dev AC role, owner of which can mint JIV token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return JIV_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn JIV token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return JIV_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause JIV token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return JIV_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/JIV/JivCustomAggregatorFeed.sol b/contracts/products/JIV/JivCustomAggregatorFeed.sol
deleted file mode 100644
index 1d12e9bb..00000000
--- a/contracts/products/JIV/JivCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./JivMidasAccessControlRoles.sol";
-
-/**
- * @title JivCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for JIV,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract JivCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- JivMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return JIV_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/JIV/JivDataFeed.sol b/contracts/products/JIV/JivDataFeed.sol
deleted file mode 100644
index 13f46cac..00000000
--- a/contracts/products/JIV/JivDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./JivMidasAccessControlRoles.sol";
-
-/**
- * @title JivDataFeed
- * @notice DataFeed for JIV product
- * @author RedDuck Software
- */
-contract JivDataFeed is DataFeed, JivMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return JIV_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/JIV/JivDepositVault.sol b/contracts/products/JIV/JivDepositVault.sol
deleted file mode 100644
index 07d2b2dc..00000000
--- a/contracts/products/JIV/JivDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./JivMidasAccessControlRoles.sol";
-
-/**
- * @title JivDepositVault
- * @notice Smart contract that handles JIV minting
- * @author RedDuck Software
- */
-contract JivDepositVault is DepositVault, JivMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return JIV_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/JIV/JivMidasAccessControlRoles.sol b/contracts/products/JIV/JivMidasAccessControlRoles.sol
deleted file mode 100644
index 66844eb2..00000000
--- a/contracts/products/JIV/JivMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title JivMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for JIV contracts
- * @author RedDuck Software
- */
-abstract contract JivMidasAccessControlRoles {
- /**
- * @notice actor that can manage JivDepositVault
- */
- bytes32 public constant JIV_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("JIV_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage JivRedemptionVault
- */
- bytes32 public constant JIV_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("JIV_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage JivCustomAggregatorFeed and JivDataFeed
- */
- bytes32 public constant JIV_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("JIV_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/JIV/JivRedemptionVaultWithSwapper.sol b/contracts/products/JIV/JivRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 6b71059b..00000000
--- a/contracts/products/JIV/JivRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./JivMidasAccessControlRoles.sol";
-
-/**
- * @title JivRedemptionVaultWithSwapper
- * @notice Smart contract that handles JIV redemptions
- * @author RedDuck Software
- */
-contract JivRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- JivMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return JIV_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/acremBTC1/AcreMBtc1CustomAggregatorFeed.sol b/contracts/products/acremBTC1/AcreMBtc1CustomAggregatorFeed.sol
deleted file mode 100644
index ff960243..00000000
--- a/contracts/products/acremBTC1/AcreMBtc1CustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./AcreMBtc1MidasAccessControlRoles.sol";
-
-/**
- * @title AcreMBtc1CustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for acremBTC1,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract AcreMBtc1CustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- AcreMBtc1MidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return ACRE_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/acremBTC1/AcreMBtc1DataFeed.sol b/contracts/products/acremBTC1/AcreMBtc1DataFeed.sol
deleted file mode 100644
index c830bf24..00000000
--- a/contracts/products/acremBTC1/AcreMBtc1DataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./AcreMBtc1MidasAccessControlRoles.sol";
-
-/**
- * @title AcreMBtc1DataFeed
- * @notice DataFeed for acremBTC1 product
- * @author RedDuck Software
- */
-contract AcreMBtc1DataFeed is DataFeed, AcreMBtc1MidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return ACRE_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/acremBTC1/AcreMBtc1DepositVault.sol b/contracts/products/acremBTC1/AcreMBtc1DepositVault.sol
deleted file mode 100644
index 5b2e2848..00000000
--- a/contracts/products/acremBTC1/AcreMBtc1DepositVault.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./AcreMBtc1MidasAccessControlRoles.sol";
-
-/**
- * @title AcreMBtc1DepositVault
- * @notice Smart contract that handles acremBTC1 minting
- * @author RedDuck Software
- */
-contract AcreMBtc1DepositVault is
- DepositVault,
- AcreMBtc1MidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return ACRE_BTC_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/acremBTC1/AcreMBtc1MidasAccessControlRoles.sol b/contracts/products/acremBTC1/AcreMBtc1MidasAccessControlRoles.sol
deleted file mode 100644
index a057bf05..00000000
--- a/contracts/products/acremBTC1/AcreMBtc1MidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title AcreMBtc1MidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for acremBTC1 contracts
- * @author RedDuck Software
- */
-abstract contract AcreMBtc1MidasAccessControlRoles {
- /**
- * @notice actor that can manage AcreMBtc1DepositVault
- */
- bytes32 public constant ACRE_BTC_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("ACRE_BTC_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage AcreMBtc1RedemptionVault
- */
- bytes32 public constant ACRE_BTC_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("ACRE_BTC_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage AcreMBtc1CustomAggregatorFeed and AcreMBtc1DataFeed
- */
- bytes32 public constant ACRE_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("ACRE_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/acremBTC1/AcreMBtc1RedemptionVaultWithSwapper.sol b/contracts/products/acremBTC1/AcreMBtc1RedemptionVaultWithSwapper.sol
deleted file mode 100644
index fd71a056..00000000
--- a/contracts/products/acremBTC1/AcreMBtc1RedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./AcreMBtc1MidasAccessControlRoles.sol";
-
-/**
- * @title AcreMBtc1RedemptionVaultWithSwapper
- * @notice Smart contract that handles acremBTC1 redemptions
- * @author RedDuck Software
- */
-contract AcreMBtc1RedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- AcreMBtc1MidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return ACRE_BTC_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/acremBTC1/acremBTC1.sol b/contracts/products/acremBTC1/acremBTC1.sol
deleted file mode 100644
index b51f4fdc..00000000
--- a/contracts/products/acremBTC1/acremBTC1.sol
+++ /dev/null
@@ -1,83 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title acremBTC1
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract acremBTC1 is mToken {
- /**
- * @notice actor that can mint acremBTC1
- */
- bytes32 public constant ACRE_BTC_MINT_OPERATOR_ROLE =
- keccak256("ACRE_BTC_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn acremBTC1
- */
- bytes32 public constant ACRE_BTC_BURN_OPERATOR_ROLE =
- keccak256("ACRE_BTC_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause acremBTC1
- */
- bytes32 public constant ACRE_BTC_PAUSE_OPERATOR_ROLE =
- keccak256("ACRE_BTC_PAUSE_OPERATOR_ROLE");
-
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ERC20Upgradeable
- * @dev override to return a new name (not the initial one)
- */
- function name() public pure override returns (string memory _name) {
- (_name, ) = _getNameSymbol();
- }
-
- /**
- * @inheritdoc ERC20Upgradeable
- * @dev override to return a new symbol (not the initial one)
- */
- function symbol() public pure override returns (string memory _symbol) {
- (, _symbol) = _getNameSymbol();
- }
-
- /**
- * @inheritdoc mToken
- */
- function _getNameSymbol()
- internal
- pure
- override
- returns (string memory, string memory)
- {
- return ("acremBTC1", "acremBTC1");
- }
-
- /**
- * @dev AC role, owner of which can mint acremBTC1 token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return ACRE_BTC_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn acremBTC1 token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return ACRE_BTC_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause acremBTC1 token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return ACRE_BTC_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/bondBTC/BondBtcCustomAggregatorFeed.sol b/contracts/products/bondBTC/BondBtcCustomAggregatorFeed.sol
deleted file mode 100644
index b582fc0f..00000000
--- a/contracts/products/bondBTC/BondBtcCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./BondBtcMidasAccessControlRoles.sol";
-
-/**
- * @title BondBtcCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for bondBTC,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract BondBtcCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- BondBtcMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return BOND_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/bondBTC/BondBtcDataFeed.sol b/contracts/products/bondBTC/BondBtcDataFeed.sol
deleted file mode 100644
index 063ae3f3..00000000
--- a/contracts/products/bondBTC/BondBtcDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./BondBtcMidasAccessControlRoles.sol";
-
-/**
- * @title BondBtcDataFeed
- * @notice DataFeed for bondBTC product
- * @author RedDuck Software
- */
-contract BondBtcDataFeed is DataFeed, BondBtcMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return BOND_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/bondBTC/BondBtcDepositVault.sol b/contracts/products/bondBTC/BondBtcDepositVault.sol
deleted file mode 100644
index 2f3ea71d..00000000
--- a/contracts/products/bondBTC/BondBtcDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./BondBtcMidasAccessControlRoles.sol";
-
-/**
- * @title BondBtcDepositVault
- * @notice Smart contract that handles bondBTC minting
- * @author RedDuck Software
- */
-contract BondBtcDepositVault is DepositVault, BondBtcMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return BOND_BTC_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/bondBTC/BondBtcMidasAccessControlRoles.sol b/contracts/products/bondBTC/BondBtcMidasAccessControlRoles.sol
deleted file mode 100644
index cb3da08d..00000000
--- a/contracts/products/bondBTC/BondBtcMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title BondBtcMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for bondBTC contracts
- * @author RedDuck Software
- */
-abstract contract BondBtcMidasAccessControlRoles {
- /**
- * @notice actor that can manage BondBtcDepositVault
- */
- bytes32 public constant BOND_BTC_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("BOND_BTC_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage BondBtcRedemptionVault
- */
- bytes32 public constant BOND_BTC_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("BOND_BTC_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage BondBtcCustomAggregatorFeed and BondBtcDataFeed
- */
- bytes32 public constant BOND_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("BOND_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/bondBTC/BondBtcRedemptionVaultWithSwapper.sol b/contracts/products/bondBTC/BondBtcRedemptionVaultWithSwapper.sol
deleted file mode 100644
index d149a88b..00000000
--- a/contracts/products/bondBTC/BondBtcRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./BondBtcMidasAccessControlRoles.sol";
-
-/**
- * @title BondBtcRedemptionVaultWithSwapper
- * @notice Smart contract that handles bondBTC redemptions
- * @author RedDuck Software
- */
-contract BondBtcRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- BondBtcMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return BOND_BTC_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/bondBTC/bondBTC.sol b/contracts/products/bondBTC/bondBTC.sol
deleted file mode 100644
index f364f790..00000000
--- a/contracts/products/bondBTC/bondBTC.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title bondBTC
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract bondBTC is mToken {
- /**
- * @notice actor that can mint bondBTC
- */
- bytes32 public constant BOND_BTC_MINT_OPERATOR_ROLE =
- keccak256("BOND_BTC_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn bondBTC
- */
- bytes32 public constant BOND_BTC_BURN_OPERATOR_ROLE =
- keccak256("BOND_BTC_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause bondBTC
- */
- bytes32 public constant BOND_BTC_PAUSE_OPERATOR_ROLE =
- keccak256("BOND_BTC_PAUSE_OPERATOR_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 ("Bond BTC", "bondBTC");
- }
-
- /**
- * @dev AC role, owner of which can mint bondBTC token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return BOND_BTC_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn bondBTC token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return BOND_BTC_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause bondBTC token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return BOND_BTC_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/bondETH/BondEthCustomAggregatorFeed.sol b/contracts/products/bondETH/BondEthCustomAggregatorFeed.sol
deleted file mode 100644
index 257ae81e..00000000
--- a/contracts/products/bondETH/BondEthCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./BondEthMidasAccessControlRoles.sol";
-
-/**
- * @title BondEthCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for bondETH,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract BondEthCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- BondEthMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return BOND_ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/bondETH/BondEthDataFeed.sol b/contracts/products/bondETH/BondEthDataFeed.sol
deleted file mode 100644
index 3342ab35..00000000
--- a/contracts/products/bondETH/BondEthDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./BondEthMidasAccessControlRoles.sol";
-
-/**
- * @title BondEthDataFeed
- * @notice DataFeed for bondETH product
- * @author RedDuck Software
- */
-contract BondEthDataFeed is DataFeed, BondEthMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return BOND_ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/bondETH/BondEthDepositVault.sol b/contracts/products/bondETH/BondEthDepositVault.sol
deleted file mode 100644
index d406e9bb..00000000
--- a/contracts/products/bondETH/BondEthDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./BondEthMidasAccessControlRoles.sol";
-
-/**
- * @title BondEthDepositVault
- * @notice Smart contract that handles bondETH minting
- * @author RedDuck Software
- */
-contract BondEthDepositVault is DepositVault, BondEthMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return BOND_ETH_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/bondETH/BondEthMidasAccessControlRoles.sol b/contracts/products/bondETH/BondEthMidasAccessControlRoles.sol
deleted file mode 100644
index 475b1e67..00000000
--- a/contracts/products/bondETH/BondEthMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title BondEthMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for bondETH contracts
- * @author RedDuck Software
- */
-abstract contract BondEthMidasAccessControlRoles {
- /**
- * @notice actor that can manage BondEthDepositVault
- */
- bytes32 public constant BOND_ETH_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("BOND_ETH_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage BondEthRedemptionVault
- */
- bytes32 public constant BOND_ETH_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("BOND_ETH_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage BondEthCustomAggregatorFeed and BondEthDataFeed
- */
- bytes32 public constant BOND_ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("BOND_ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/bondETH/BondEthRedemptionVaultWithSwapper.sol b/contracts/products/bondETH/BondEthRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 27887087..00000000
--- a/contracts/products/bondETH/BondEthRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./BondEthMidasAccessControlRoles.sol";
-
-/**
- * @title BondEthRedemptionVaultWithSwapper
- * @notice Smart contract that handles bondETH redemptions
- * @author RedDuck Software
- */
-contract BondEthRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- BondEthMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return BOND_ETH_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/bondETH/bondETH.sol b/contracts/products/bondETH/bondETH.sol
deleted file mode 100644
index d55df784..00000000
--- a/contracts/products/bondETH/bondETH.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title bondETH
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract bondETH is mToken {
- /**
- * @notice actor that can mint bondETH
- */
- bytes32 public constant BOND_ETH_MINT_OPERATOR_ROLE =
- keccak256("BOND_ETH_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn bondETH
- */
- bytes32 public constant BOND_ETH_BURN_OPERATOR_ROLE =
- keccak256("BOND_ETH_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause bondETH
- */
- bytes32 public constant BOND_ETH_PAUSE_OPERATOR_ROLE =
- keccak256("BOND_ETH_PAUSE_OPERATOR_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 ("Bond ETH", "bondETH");
- }
-
- /**
- * @dev AC role, owner of which can mint bondETH token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return BOND_ETH_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn bondETH token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return BOND_ETH_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause bondETH token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return BOND_ETH_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/bondUSD/BondUsdCustomAggregatorFeed.sol b/contracts/products/bondUSD/BondUsdCustomAggregatorFeed.sol
deleted file mode 100644
index 781a6d5b..00000000
--- a/contracts/products/bondUSD/BondUsdCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./BondUsdMidasAccessControlRoles.sol";
-
-/**
- * @title BondUsdCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for bondUSD,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract BondUsdCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- BondUsdMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return BOND_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/bondUSD/BondUsdDataFeed.sol b/contracts/products/bondUSD/BondUsdDataFeed.sol
deleted file mode 100644
index aa5ca561..00000000
--- a/contracts/products/bondUSD/BondUsdDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./BondUsdMidasAccessControlRoles.sol";
-
-/**
- * @title BondUsdDataFeed
- * @notice DataFeed for bondUSD product
- * @author RedDuck Software
- */
-contract BondUsdDataFeed is DataFeed, BondUsdMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return BOND_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/bondUSD/BondUsdDepositVault.sol b/contracts/products/bondUSD/BondUsdDepositVault.sol
deleted file mode 100644
index 89d65b55..00000000
--- a/contracts/products/bondUSD/BondUsdDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./BondUsdMidasAccessControlRoles.sol";
-
-/**
- * @title BondUsdDepositVault
- * @notice Smart contract that handles bondUSD minting
- * @author RedDuck Software
- */
-contract BondUsdDepositVault is DepositVault, BondUsdMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return BOND_USD_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/bondUSD/BondUsdMidasAccessControlRoles.sol b/contracts/products/bondUSD/BondUsdMidasAccessControlRoles.sol
deleted file mode 100644
index 6d39dad2..00000000
--- a/contracts/products/bondUSD/BondUsdMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title BondUsdMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for bondUSD contracts
- * @author RedDuck Software
- */
-abstract contract BondUsdMidasAccessControlRoles {
- /**
- * @notice actor that can manage BondUsdDepositVault
- */
- bytes32 public constant BOND_USD_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("BOND_USD_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage BondUsdRedemptionVault
- */
- bytes32 public constant BOND_USD_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("BOND_USD_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage BondUsdCustomAggregatorFeed and BondUsdDataFeed
- */
- bytes32 public constant BOND_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("BOND_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/bondUSD/BondUsdRedemptionVaultWithSwapper.sol b/contracts/products/bondUSD/BondUsdRedemptionVaultWithSwapper.sol
deleted file mode 100644
index e175561e..00000000
--- a/contracts/products/bondUSD/BondUsdRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./BondUsdMidasAccessControlRoles.sol";
-
-/**
- * @title BondUsdRedemptionVaultWithSwapper
- * @notice Smart contract that handles bondUSD redemptions
- * @author RedDuck Software
- */
-contract BondUsdRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- BondUsdMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return BOND_USD_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/bondUSD/bondUSD.sol b/contracts/products/bondUSD/bondUSD.sol
deleted file mode 100644
index af26b94f..00000000
--- a/contracts/products/bondUSD/bondUSD.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title bondUSD
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract bondUSD is mToken {
- /**
- * @notice actor that can mint bondUSD
- */
- bytes32 public constant BOND_USD_MINT_OPERATOR_ROLE =
- keccak256("BOND_USD_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn bondUSD
- */
- bytes32 public constant BOND_USD_BURN_OPERATOR_ROLE =
- keccak256("BOND_USD_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause bondUSD
- */
- bytes32 public constant BOND_USD_PAUSE_OPERATOR_ROLE =
- keccak256("BOND_USD_PAUSE_OPERATOR_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 ("Bond USD", "bondUSD");
- }
-
- /**
- * @dev AC role, owner of which can mint bondUSD token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return BOND_USD_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn bondUSD token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return BOND_USD_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause bondUSD token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return BOND_USD_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/cUSDO/CUsdoCustomAggregatorFeed.sol b/contracts/products/cUSDO/CUsdoCustomAggregatorFeed.sol
deleted file mode 100644
index 95f1b30f..00000000
--- a/contracts/products/cUSDO/CUsdoCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./CUsdoMidasAccessControlRoles.sol";
-
-/**
- * @title CUsdoCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for cUSDO,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract CUsdoCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- CUsdoMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return C_USDO_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/cUSDO/CUsdoDataFeed.sol b/contracts/products/cUSDO/CUsdoDataFeed.sol
deleted file mode 100644
index 6ea35ed3..00000000
--- a/contracts/products/cUSDO/CUsdoDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./CUsdoMidasAccessControlRoles.sol";
-
-/**
- * @title CUsdoDataFeed
- * @notice DataFeed for cUSDO product
- * @author RedDuck Software
- */
-contract CUsdoDataFeed is DataFeed, CUsdoMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return C_USDO_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/cUSDO/CUsdoDepositVault.sol b/contracts/products/cUSDO/CUsdoDepositVault.sol
deleted file mode 100644
index 464d797c..00000000
--- a/contracts/products/cUSDO/CUsdoDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./CUsdoMidasAccessControlRoles.sol";
-
-/**
- * @title CUsdoDepositVault
- * @notice Smart contract that handles cUSDO minting
- * @author RedDuck Software
- */
-contract CUsdoDepositVault is DepositVault, CUsdoMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return C_USDO_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/cUSDO/CUsdoMidasAccessControlRoles.sol b/contracts/products/cUSDO/CUsdoMidasAccessControlRoles.sol
deleted file mode 100644
index 4b01a8c9..00000000
--- a/contracts/products/cUSDO/CUsdoMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title CUsdoMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for cUSDO contracts
- * @author RedDuck Software
- */
-abstract contract CUsdoMidasAccessControlRoles {
- /**
- * @notice actor that can manage CUsdoDepositVault
- */
- bytes32 public constant C_USDO_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("C_USDO_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage CUsdoRedemptionVault
- */
- bytes32 public constant C_USDO_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("C_USDO_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage CUsdoCustomAggregatorFeed and CUsdoDataFeed
- */
- bytes32 public constant C_USDO_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("C_USDO_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/cUSDO/CUsdoRedemptionVaultWithSwapper.sol b/contracts/products/cUSDO/CUsdoRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 7f7093c3..00000000
--- a/contracts/products/cUSDO/CUsdoRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./CUsdoMidasAccessControlRoles.sol";
-
-/**
- * @title CUsdoRedemptionVaultWithSwapper
- * @notice Smart contract that handles cUSDO redemptions
- * @author RedDuck Software
- */
-contract CUsdoRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- CUsdoMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return C_USDO_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/cUSDO/cUSDO.sol b/contracts/products/cUSDO/cUSDO.sol
deleted file mode 100644
index fd18219f..00000000
--- a/contracts/products/cUSDO/cUSDO.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title cUSDO
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract cUSDO is mToken {
- /**
- * @notice actor that can mint cUSDO
- */
- bytes32 public constant C_USDO_MINT_OPERATOR_ROLE =
- keccak256("C_USDO_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn cUSDO
- */
- bytes32 public constant C_USDO_BURN_OPERATOR_ROLE =
- keccak256("C_USDO_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause cUSDO
- */
- bytes32 public constant C_USDO_PAUSE_OPERATOR_ROLE =
- keccak256("C_USDO_PAUSE_OPERATOR_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 ("cUSDO BNB Midas Vault", "cUSDO");
- }
-
- /**
- * @dev AC role, owner of which can mint cUSDO token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return C_USDO_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn cUSDO token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return C_USDO_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause cUSDO token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return C_USDO_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/carryTradeUSDTRYLeverage/CarryTradeUsdTryLeverageCustomAggregatorFeed.sol b/contracts/products/carryTradeUSDTRYLeverage/CarryTradeUsdTryLeverageCustomAggregatorFeed.sol
deleted file mode 100644
index b1fd50a0..00000000
--- a/contracts/products/carryTradeUSDTRYLeverage/CarryTradeUsdTryLeverageCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./CarryTradeUsdTryLeverageMidasAccessControlRoles.sol";
-
-/**
- * @title CarryTradeUsdTryLeverageCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for carryTradeUSDTRYLeverage,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract CarryTradeUsdTryLeverageCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- CarryTradeUsdTryLeverageMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return CARRY_TRADE_USD_TRY_LEVERAGE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/carryTradeUSDTRYLeverage/CarryTradeUsdTryLeverageDataFeed.sol b/contracts/products/carryTradeUSDTRYLeverage/CarryTradeUsdTryLeverageDataFeed.sol
deleted file mode 100644
index 2f265c08..00000000
--- a/contracts/products/carryTradeUSDTRYLeverage/CarryTradeUsdTryLeverageDataFeed.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./CarryTradeUsdTryLeverageMidasAccessControlRoles.sol";
-
-/**
- * @title CarryTradeUsdTryLeverageDataFeed
- * @notice DataFeed for carryTradeUSDTRYLeverage product
- * @author RedDuck Software
- */
-contract CarryTradeUsdTryLeverageDataFeed is
- DataFeed,
- CarryTradeUsdTryLeverageMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return CARRY_TRADE_USD_TRY_LEVERAGE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/carryTradeUSDTRYLeverage/CarryTradeUsdTryLeverageDepositVault.sol b/contracts/products/carryTradeUSDTRYLeverage/CarryTradeUsdTryLeverageDepositVault.sol
deleted file mode 100644
index b2b7b4e4..00000000
--- a/contracts/products/carryTradeUSDTRYLeverage/CarryTradeUsdTryLeverageDepositVault.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./CarryTradeUsdTryLeverageMidasAccessControlRoles.sol";
-
-/**
- * @title CarryTradeUsdTryLeverageDepositVault
- * @notice Smart contract that handles carryTradeUSDTRYLeverage minting
- * @author RedDuck Software
- */
-contract CarryTradeUsdTryLeverageDepositVault is
- DepositVault,
- CarryTradeUsdTryLeverageMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return CARRY_TRADE_USD_TRY_LEVERAGE_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/carryTradeUSDTRYLeverage/CarryTradeUsdTryLeverageMidasAccessControlRoles.sol b/contracts/products/carryTradeUSDTRYLeverage/CarryTradeUsdTryLeverageMidasAccessControlRoles.sol
deleted file mode 100644
index 65befb2d..00000000
--- a/contracts/products/carryTradeUSDTRYLeverage/CarryTradeUsdTryLeverageMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,32 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title CarryTradeUsdTryLeverageMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for carryTradeUSDTRYLeverage contracts
- * @author RedDuck Software
- */
-abstract contract CarryTradeUsdTryLeverageMidasAccessControlRoles {
- /**
- * @notice actor that can manage CarryTradeUsdTryLeverageDepositVault
- */
- bytes32
- public constant CARRY_TRADE_USD_TRY_LEVERAGE_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("CARRY_TRADE_USD_TRY_LEVERAGE_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage CarryTradeUsdTryLeverageRedemptionVault
- */
- bytes32
- public constant CARRY_TRADE_USD_TRY_LEVERAGE_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("CARRY_TRADE_USD_TRY_LEVERAGE_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage CarryTradeUsdTryLeverageCustomAggregatorFeed and CarryTradeUsdTryLeverageDataFeed
- */
- bytes32
- public constant CARRY_TRADE_USD_TRY_LEVERAGE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256(
- "CARRY_TRADE_USD_TRY_LEVERAGE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE"
- );
-}
diff --git a/contracts/products/carryTradeUSDTRYLeverage/CarryTradeUsdTryLeverageRedemptionVaultWithSwapper.sol b/contracts/products/carryTradeUSDTRYLeverage/CarryTradeUsdTryLeverageRedemptionVaultWithSwapper.sol
deleted file mode 100644
index fe025f9f..00000000
--- a/contracts/products/carryTradeUSDTRYLeverage/CarryTradeUsdTryLeverageRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./CarryTradeUsdTryLeverageMidasAccessControlRoles.sol";
-
-/**
- * @title CarryTradeUsdTryLeverageRedemptionVaultWithSwapper
- * @notice Smart contract that handles carryTradeUSDTRYLeverage redemptions
- * @author RedDuck Software
- */
-contract CarryTradeUsdTryLeverageRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- CarryTradeUsdTryLeverageMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return CARRY_TRADE_USD_TRY_LEVERAGE_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/carryTradeUSDTRYLeverage/carryTradeUSDTRYLeverage.sol b/contracts/products/carryTradeUSDTRYLeverage/carryTradeUSDTRYLeverage.sol
deleted file mode 100644
index 3a916f92..00000000
--- a/contracts/products/carryTradeUSDTRYLeverage/carryTradeUSDTRYLeverage.sol
+++ /dev/null
@@ -1,70 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title carryTradeUSDTRYLeverage
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract carryTradeUSDTRYLeverage is mToken {
- /**
- * @notice actor that can mint carryTradeUSDTRYLeverage
- */
- bytes32 public constant CARRY_TRADE_USD_TRY_LEVERAGE_MINT_OPERATOR_ROLE =
- keccak256("CARRY_TRADE_USD_TRY_LEVERAGE_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn carryTradeUSDTRYLeverage
- */
- bytes32 public constant CARRY_TRADE_USD_TRY_LEVERAGE_BURN_OPERATOR_ROLE =
- keccak256("CARRY_TRADE_USD_TRY_LEVERAGE_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause carryTradeUSDTRYLeverage
- */
- bytes32 public constant CARRY_TRADE_USD_TRY_LEVERAGE_PAUSE_OPERATOR_ROLE =
- keccak256("CARRY_TRADE_USD_TRY_LEVERAGE_PAUSE_OPERATOR_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 (
- "Morini CarryTradeUSDTRYLeverage Vault",
- "CarryTradeUSDTRYLeverage"
- );
- }
-
- /**
- * @dev AC role, owner of which can mint carryTradeUSDTRYLeverage token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return CARRY_TRADE_USD_TRY_LEVERAGE_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn carryTradeUSDTRYLeverage token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return CARRY_TRADE_USD_TRY_LEVERAGE_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause carryTradeUSDTRYLeverage token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return CARRY_TRADE_USD_TRY_LEVERAGE_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/dnETH/DnEthCustomAggregatorFeed.sol b/contracts/products/dnETH/DnEthCustomAggregatorFeed.sol
deleted file mode 100644
index a56cb3a0..00000000
--- a/contracts/products/dnETH/DnEthCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./DnEthMidasAccessControlRoles.sol";
-
-/**
- * @title DnEthCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for dnETH,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract DnEthCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- DnEthMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return DN_ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/dnETH/DnEthDataFeed.sol b/contracts/products/dnETH/DnEthDataFeed.sol
deleted file mode 100644
index ef345574..00000000
--- a/contracts/products/dnETH/DnEthDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./DnEthMidasAccessControlRoles.sol";
-
-/**
- * @title DnEthDataFeed
- * @notice DataFeed for dnETH product
- * @author RedDuck Software
- */
-contract DnEthDataFeed is DataFeed, DnEthMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return DN_ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/dnETH/DnEthDepositVault.sol b/contracts/products/dnETH/DnEthDepositVault.sol
deleted file mode 100644
index 27aeda91..00000000
--- a/contracts/products/dnETH/DnEthDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./DnEthMidasAccessControlRoles.sol";
-
-/**
- * @title DnEthDepositVault
- * @notice Smart contract that handles dnETH minting
- * @author RedDuck Software
- */
-contract DnEthDepositVault is DepositVault, DnEthMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return DN_ETH_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/dnETH/DnEthMidasAccessControlRoles.sol b/contracts/products/dnETH/DnEthMidasAccessControlRoles.sol
deleted file mode 100644
index 49c870cd..00000000
--- a/contracts/products/dnETH/DnEthMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title DnEthMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for dnETH contracts
- * @author RedDuck Software
- */
-abstract contract DnEthMidasAccessControlRoles {
- /**
- * @notice actor that can manage DnEthDepositVault
- */
- bytes32 public constant DN_ETH_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("DN_ETH_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage DnEthRedemptionVault
- */
- bytes32 public constant DN_ETH_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("DN_ETH_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage DnEthCustomAggregatorFeed and DnEthDataFeed
- */
- bytes32 public constant DN_ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("DN_ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/dnETH/DnEthRedemptionVaultWithSwapper.sol b/contracts/products/dnETH/DnEthRedemptionVaultWithSwapper.sol
deleted file mode 100644
index d697772e..00000000
--- a/contracts/products/dnETH/DnEthRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./DnEthMidasAccessControlRoles.sol";
-
-/**
- * @title DnEthRedemptionVaultWithSwapper
- * @notice Smart contract that handles dnETH redemptions
- * @author RedDuck Software
- */
-contract DnEthRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- DnEthMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return DN_ETH_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/dnETH/dnETH.sol b/contracts/products/dnETH/dnETH.sol
deleted file mode 100644
index a2fb1f76..00000000
--- a/contracts/products/dnETH/dnETH.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title dnETH
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract dnETH is mToken {
- /**
- * @notice actor that can mint dnETH
- */
- bytes32 public constant DN_ETH_MINT_OPERATOR_ROLE =
- keccak256("DN_ETH_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn dnETH
- */
- bytes32 public constant DN_ETH_BURN_OPERATOR_ROLE =
- keccak256("DN_ETH_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause dnETH
- */
- bytes32 public constant DN_ETH_PAUSE_OPERATOR_ROLE =
- keccak256("DN_ETH_PAUSE_OPERATOR_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 ("Delta Neutral ETH", "dnETH");
- }
-
- /**
- * @dev AC role, owner of which can mint dnETH token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return DN_ETH_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn dnETH token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return DN_ETH_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause dnETH token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return DN_ETH_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/dnFART/DnFartCustomAggregatorFeed.sol b/contracts/products/dnFART/DnFartCustomAggregatorFeed.sol
deleted file mode 100644
index 32a337af..00000000
--- a/contracts/products/dnFART/DnFartCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./DnFartMidasAccessControlRoles.sol";
-
-/**
- * @title DnFartCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for dnFART,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract DnFartCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- DnFartMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return DN_FART_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/dnFART/DnFartDataFeed.sol b/contracts/products/dnFART/DnFartDataFeed.sol
deleted file mode 100644
index 9cb7220d..00000000
--- a/contracts/products/dnFART/DnFartDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./DnFartMidasAccessControlRoles.sol";
-
-/**
- * @title DnFartDataFeed
- * @notice DataFeed for dnFART product
- * @author RedDuck Software
- */
-contract DnFartDataFeed is DataFeed, DnFartMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return DN_FART_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/dnFART/DnFartDepositVault.sol b/contracts/products/dnFART/DnFartDepositVault.sol
deleted file mode 100644
index dc34cff9..00000000
--- a/contracts/products/dnFART/DnFartDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./DnFartMidasAccessControlRoles.sol";
-
-/**
- * @title DnFartDepositVault
- * @notice Smart contract that handles dnFART minting
- * @author RedDuck Software
- */
-contract DnFartDepositVault is DepositVault, DnFartMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return DN_FART_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/dnFART/DnFartMidasAccessControlRoles.sol b/contracts/products/dnFART/DnFartMidasAccessControlRoles.sol
deleted file mode 100644
index ef1a1c9e..00000000
--- a/contracts/products/dnFART/DnFartMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title DnFartMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for dnFART contracts
- * @author RedDuck Software
- */
-abstract contract DnFartMidasAccessControlRoles {
- /**
- * @notice actor that can manage DnFartDepositVault
- */
- bytes32 public constant DN_FART_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("DN_FART_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage DnFartRedemptionVault
- */
- bytes32 public constant DN_FART_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("DN_FART_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage DnFartCustomAggregatorFeed and DnFartDataFeed
- */
- bytes32 public constant DN_FART_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("DN_FART_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/dnFART/DnFartRedemptionVaultWithSwapper.sol b/contracts/products/dnFART/DnFartRedemptionVaultWithSwapper.sol
deleted file mode 100644
index c7646182..00000000
--- a/contracts/products/dnFART/DnFartRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./DnFartMidasAccessControlRoles.sol";
-
-/**
- * @title DnFartRedemptionVaultWithSwapper
- * @notice Smart contract that handles dnFART redemptions
- * @author RedDuck Software
- */
-contract DnFartRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- DnFartMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return DN_FART_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/dnFART/dnFART.sol b/contracts/products/dnFART/dnFART.sol
deleted file mode 100644
index ebddd908..00000000
--- a/contracts/products/dnFART/dnFART.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title dnFART
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract dnFART is mToken {
- /**
- * @notice actor that can mint dnFART
- */
- bytes32 public constant DN_FART_MINT_OPERATOR_ROLE =
- keccak256("DN_FART_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn dnFART
- */
- bytes32 public constant DN_FART_BURN_OPERATOR_ROLE =
- keccak256("DN_FART_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause dnFART
- */
- bytes32 public constant DN_FART_PAUSE_OPERATOR_ROLE =
- keccak256("DN_FART_PAUSE_OPERATOR_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 ("Delta Neutral FART", "dnFART");
- }
-
- /**
- * @dev AC role, owner of which can mint dnFART token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return DN_FART_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn dnFART token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return DN_FART_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause dnFART token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return DN_FART_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/dnHYPE/DnHypeCustomAggregatorFeed.sol b/contracts/products/dnHYPE/DnHypeCustomAggregatorFeed.sol
deleted file mode 100644
index 214db6ba..00000000
--- a/contracts/products/dnHYPE/DnHypeCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./DnHypeMidasAccessControlRoles.sol";
-
-/**
- * @title DnHypeCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for dnHYPE,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract DnHypeCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- DnHypeMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return DN_HYPE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/dnHYPE/DnHypeDataFeed.sol b/contracts/products/dnHYPE/DnHypeDataFeed.sol
deleted file mode 100644
index 074cb258..00000000
--- a/contracts/products/dnHYPE/DnHypeDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./DnHypeMidasAccessControlRoles.sol";
-
-/**
- * @title DnHypeDataFeed
- * @notice DataFeed for dnHYPE product
- * @author RedDuck Software
- */
-contract DnHypeDataFeed is DataFeed, DnHypeMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return DN_HYPE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/dnHYPE/DnHypeDepositVault.sol b/contracts/products/dnHYPE/DnHypeDepositVault.sol
deleted file mode 100644
index ea269523..00000000
--- a/contracts/products/dnHYPE/DnHypeDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./DnHypeMidasAccessControlRoles.sol";
-
-/**
- * @title DnHypeDepositVault
- * @notice Smart contract that handles dnHYPE minting
- * @author RedDuck Software
- */
-contract DnHypeDepositVault is DepositVault, DnHypeMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return DN_HYPE_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/dnHYPE/DnHypeMidasAccessControlRoles.sol b/contracts/products/dnHYPE/DnHypeMidasAccessControlRoles.sol
deleted file mode 100644
index 25fc4138..00000000
--- a/contracts/products/dnHYPE/DnHypeMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title DnHypeMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for dnHYPE contracts
- * @author RedDuck Software
- */
-abstract contract DnHypeMidasAccessControlRoles {
- /**
- * @notice actor that can manage DnHypeDepositVault
- */
- bytes32 public constant DN_HYPE_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("DN_HYPE_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage DnHypeRedemptionVault
- */
- bytes32 public constant DN_HYPE_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("DN_HYPE_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage DnHypeCustomAggregatorFeed and DnHypeDataFeed
- */
- bytes32 public constant DN_HYPE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("DN_HYPE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/dnHYPE/DnHypeRedemptionVaultWithSwapper.sol b/contracts/products/dnHYPE/DnHypeRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 03112a5d..00000000
--- a/contracts/products/dnHYPE/DnHypeRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./DnHypeMidasAccessControlRoles.sol";
-
-/**
- * @title DnHypeRedemptionVaultWithSwapper
- * @notice Smart contract that handles dnHYPE redemptions
- * @author RedDuck Software
- */
-contract DnHypeRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- DnHypeMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return DN_HYPE_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/dnHYPE/dnHYPE.sol b/contracts/products/dnHYPE/dnHYPE.sol
deleted file mode 100644
index e33ecdf4..00000000
--- a/contracts/products/dnHYPE/dnHYPE.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title dnHYPE
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract dnHYPE is mToken {
- /**
- * @notice actor that can mint dnHYPE
- */
- bytes32 public constant DN_HYPE_MINT_OPERATOR_ROLE =
- keccak256("DN_HYPE_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn dnHYPE
- */
- bytes32 public constant DN_HYPE_BURN_OPERATOR_ROLE =
- keccak256("DN_HYPE_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause dnHYPE
- */
- bytes32 public constant DN_HYPE_PAUSE_OPERATOR_ROLE =
- keccak256("DN_HYPE_PAUSE_OPERATOR_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 ("Delta Neutral HYPE", "dnHYPE");
- }
-
- /**
- * @dev AC role, owner of which can mint dnHYPE token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return DN_HYPE_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn dnHYPE token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return DN_HYPE_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause dnHYPE token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return DN_HYPE_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/dnPUMP/DnPumpCustomAggregatorFeed.sol b/contracts/products/dnPUMP/DnPumpCustomAggregatorFeed.sol
deleted file mode 100644
index fd827045..00000000
--- a/contracts/products/dnPUMP/DnPumpCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./DnPumpMidasAccessControlRoles.sol";
-
-/**
- * @title DnPumpCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for dnPUMP,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract DnPumpCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- DnPumpMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return DN_PUMP_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/dnPUMP/DnPumpDataFeed.sol b/contracts/products/dnPUMP/DnPumpDataFeed.sol
deleted file mode 100644
index e87b12cf..00000000
--- a/contracts/products/dnPUMP/DnPumpDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./DnPumpMidasAccessControlRoles.sol";
-
-/**
- * @title DnPumpDataFeed
- * @notice DataFeed for dnPUMP product
- * @author RedDuck Software
- */
-contract DnPumpDataFeed is DataFeed, DnPumpMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return DN_PUMP_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/dnPUMP/DnPumpDepositVault.sol b/contracts/products/dnPUMP/DnPumpDepositVault.sol
deleted file mode 100644
index b1e9fd57..00000000
--- a/contracts/products/dnPUMP/DnPumpDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./DnPumpMidasAccessControlRoles.sol";
-
-/**
- * @title DnPumpDepositVault
- * @notice Smart contract that handles dnPUMP minting
- * @author RedDuck Software
- */
-contract DnPumpDepositVault is DepositVault, DnPumpMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return DN_PUMP_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/dnPUMP/DnPumpMidasAccessControlRoles.sol b/contracts/products/dnPUMP/DnPumpMidasAccessControlRoles.sol
deleted file mode 100644
index 20f32775..00000000
--- a/contracts/products/dnPUMP/DnPumpMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title DnPumpMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for dnPUMP contracts
- * @author RedDuck Software
- */
-abstract contract DnPumpMidasAccessControlRoles {
- /**
- * @notice actor that can manage DnPumpDepositVault
- */
- bytes32 public constant DN_PUMP_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("DN_PUMP_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage DnPumpRedemptionVault
- */
- bytes32 public constant DN_PUMP_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("DN_PUMP_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage DnPumpCustomAggregatorFeed and DnPumpDataFeed
- */
- bytes32 public constant DN_PUMP_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("DN_PUMP_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/dnPUMP/DnPumpRedemptionVaultWithSwapper.sol b/contracts/products/dnPUMP/DnPumpRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 23d14c31..00000000
--- a/contracts/products/dnPUMP/DnPumpRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./DnPumpMidasAccessControlRoles.sol";
-
-/**
- * @title DnPumpRedemptionVaultWithSwapper
- * @notice Smart contract that handles dnPUMP redemptions
- * @author RedDuck Software
- */
-contract DnPumpRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- DnPumpMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return DN_PUMP_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/dnPUMP/dnPUMP.sol b/contracts/products/dnPUMP/dnPUMP.sol
deleted file mode 100644
index 21b8c2e2..00000000
--- a/contracts/products/dnPUMP/dnPUMP.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title dnPUMP
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract dnPUMP is mToken {
- /**
- * @notice actor that can mint dnPUMP
- */
- bytes32 public constant DN_PUMP_MINT_OPERATOR_ROLE =
- keccak256("DN_PUMP_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn dnPUMP
- */
- bytes32 public constant DN_PUMP_BURN_OPERATOR_ROLE =
- keccak256("DN_PUMP_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause dnPUMP
- */
- bytes32 public constant DN_PUMP_PAUSE_OPERATOR_ROLE =
- keccak256("DN_PUMP_PAUSE_OPERATOR_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 ("Delta Neutral PUMP", "dnPUMP");
- }
-
- /**
- * @dev AC role, owner of which can mint dnPUMP token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return DN_PUMP_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn dnPUMP token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return DN_PUMP_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause dnPUMP token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return DN_PUMP_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/dnTEST/DnTestCustomAggregatorFeedGrowth.sol b/contracts/products/dnTEST/DnTestCustomAggregatorFeedGrowth.sol
deleted file mode 100644
index a2956648..00000000
--- a/contracts/products/dnTEST/DnTestCustomAggregatorFeedGrowth.sol
+++ /dev/null
@@ -1,29 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeedGrowth.sol";
-import "./DnTestMidasAccessControlRoles.sol";
-
-/**
- * @title DnTestCustomAggregatorFeedGrowth
- * @notice AggregatorV3 compatible feed for dnTEST,
- * where price is submitted manually by feed admins,
- * and growth apr applies to the answer.
- * @author RedDuck Software
- */
-contract DnTestCustomAggregatorFeedGrowth is
- CustomAggregatorV3CompatibleFeedGrowth,
- DnTestMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeedGrowth
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return DN_TEST_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/dnTEST/DnTestDataFeed.sol b/contracts/products/dnTEST/DnTestDataFeed.sol
deleted file mode 100644
index 4c7cb45d..00000000
--- a/contracts/products/dnTEST/DnTestDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./DnTestMidasAccessControlRoles.sol";
-
-/**
- * @title DnTestDataFeed
- * @notice DataFeed for dnTEST product
- * @author RedDuck Software
- */
-contract DnTestDataFeed is DataFeed, DnTestMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return DN_TEST_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/dnTEST/DnTestDepositVault.sol b/contracts/products/dnTEST/DnTestDepositVault.sol
deleted file mode 100644
index 8326973c..00000000
--- a/contracts/products/dnTEST/DnTestDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./DnTestMidasAccessControlRoles.sol";
-
-/**
- * @title DnTestDepositVault
- * @notice Smart contract that handles dnTEST minting
- * @author RedDuck Software
- */
-contract DnTestDepositVault is DepositVault, DnTestMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return DN_TEST_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/dnTEST/DnTestMidasAccessControlRoles.sol b/contracts/products/dnTEST/DnTestMidasAccessControlRoles.sol
deleted file mode 100644
index 272269af..00000000
--- a/contracts/products/dnTEST/DnTestMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title DnTestMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for dnTEST contracts
- * @author RedDuck Software
- */
-abstract contract DnTestMidasAccessControlRoles {
- /**
- * @notice actor that can manage DnTestDepositVault
- */
- bytes32 public constant DN_TEST_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("DN_TEST_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage DnTestRedemptionVault
- */
- bytes32 public constant DN_TEST_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("DN_TEST_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage DnTestCustomAggregatorFeed and DnTestDataFeed
- */
- bytes32 public constant DN_TEST_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("DN_TEST_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/dnTEST/DnTestRedemptionVaultWithSwapper.sol b/contracts/products/dnTEST/DnTestRedemptionVaultWithSwapper.sol
deleted file mode 100644
index ca7e2722..00000000
--- a/contracts/products/dnTEST/DnTestRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./DnTestMidasAccessControlRoles.sol";
-
-/**
- * @title DnTestRedemptionVaultWithSwapper
- * @notice Smart contract that handles dnTEST redemptions
- * @author RedDuck Software
- */
-contract DnTestRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- DnTestMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return DN_TEST_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/dnTEST/dnTEST.sol b/contracts/products/dnTEST/dnTEST.sol
deleted file mode 100644
index 13478685..00000000
--- a/contracts/products/dnTEST/dnTEST.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title dnTEST
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract dnTEST is mToken {
- /**
- * @notice actor that can mint dnTEST
- */
- bytes32 public constant DN_TEST_MINT_OPERATOR_ROLE =
- keccak256("DN_TEST_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn dnTEST
- */
- bytes32 public constant DN_TEST_BURN_OPERATOR_ROLE =
- keccak256("DN_TEST_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause dnTEST
- */
- bytes32 public constant DN_TEST_PAUSE_OPERATOR_ROLE =
- keccak256("DN_TEST_PAUSE_OPERATOR_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 ("Delta Neutral TEST", "dnTEST");
- }
-
- /**
- * @dev AC role, owner of which can mint dnTEST token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return DN_TEST_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn dnTEST token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return DN_TEST_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause dnTEST token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return DN_TEST_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/eUSD/EUsdDepositVault.sol b/contracts/products/eUSD/EUsdDepositVault.sol
deleted file mode 100644
index 2a9a76c6..00000000
--- a/contracts/products/eUSD/EUsdDepositVault.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "./EUsdMidasAccessControlRoles.sol";
-import "../../DepositVault.sol";
-
-/**
- * @title EUsdDepositVault
- * @notice Smart contract that handles eUSD minting
- * @author RedDuck Software
- */
-contract EUsdDepositVault is DepositVault, EUsdMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return E_USD_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-
- function greenlistedRole() public pure override returns (bytes32) {
- return E_USD_GREENLISTED_ROLE;
- }
-}
diff --git a/contracts/products/eUSD/EUsdMidasAccessControlRoles.sol b/contracts/products/eUSD/EUsdMidasAccessControlRoles.sol
deleted file mode 100644
index bbb28a76..00000000
--- a/contracts/products/eUSD/EUsdMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,39 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title EUsdMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for eUSD contracts
- * @author RedDuck Software
- */
-abstract contract EUsdMidasAccessControlRoles {
- /**
- * @notice actor that can manage vault admin roles
- */
- bytes32 public constant E_USD_VAULT_ROLES_OPERATOR =
- keccak256("E_USD_VAULT_ROLES_OPERATOR");
-
- /**
- * @notice actor that can manage EUsdDepositVault
- */
- bytes32 public constant E_USD_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("E_USD_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage EUsdRedemptionVault
- */
- bytes32 public constant E_USD_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("E_USD_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can change eUSD green list statuses of addresses
- */
- bytes32 public constant E_USD_GREENLIST_OPERATOR_ROLE =
- keccak256("E_USD_GREENLIST_OPERATOR_ROLE");
-
- /**
- * @notice actor that is greenlisted in eUSD
- */
- bytes32 public constant E_USD_GREENLISTED_ROLE =
- keccak256("E_USD_GREENLISTED_ROLE");
-}
diff --git a/contracts/products/eUSD/EUsdRedemptionVault.sol b/contracts/products/eUSD/EUsdRedemptionVault.sol
deleted file mode 100644
index 918178ba..00000000
--- a/contracts/products/eUSD/EUsdRedemptionVault.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "./EUsdMidasAccessControlRoles.sol";
-import "../../RedemptionVault.sol";
-
-/**
- * @title EUsdRedemptionVault
- * @notice Smart contract that handles eUSD redeeming
- * @author RedDuck Software
- */
-contract EUsdRedemptionVault is RedemptionVault, EUsdMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return E_USD_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-
- function greenlistedRole() public pure override returns (bytes32) {
- return E_USD_GREENLISTED_ROLE;
- }
-}
diff --git a/contracts/products/eUSD/EUsdRedemptionVaultWithBUIDL.sol b/contracts/products/eUSD/EUsdRedemptionVaultWithBUIDL.sol
deleted file mode 100644
index 4c1047f4..00000000
--- a/contracts/products/eUSD/EUsdRedemptionVaultWithBUIDL.sol
+++ /dev/null
@@ -1,31 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "./EUsdMidasAccessControlRoles.sol";
-import "../../RedemptionVaultWithBUIDL.sol";
-
-/**
- * @title EUsdRedemptionVaultWithBUIDL
- * @notice Smart contract that handles eUSD redeeming
- * @author RedDuck Software
- */
-contract EUsdRedemptionVaultWithBUIDL is
- RedemptionVaultWIthBUIDL,
- EUsdMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return E_USD_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-
- function greenlistedRole() public pure override returns (bytes32) {
- return E_USD_GREENLISTED_ROLE;
- }
-}
diff --git a/contracts/products/eUSD/eUSD.sol b/contracts/products/eUSD/eUSD.sol
deleted file mode 100644
index f92800d8..00000000
--- a/contracts/products/eUSD/eUSD.sol
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-import "../../mToken.sol";
-
-/**
- * @title eUSD
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract eUSD is mToken {
- /**
- * @notice actor that can mint eUSD
- */
- bytes32 public constant E_USD_MINT_OPERATOR_ROLE =
- keccak256("E_USD_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn eUSD
- */
- bytes32 public constant E_USD_BURN_OPERATOR_ROLE =
- keccak256("E_USD_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause eUSD
- */
- bytes32 public constant E_USD_PAUSE_OPERATOR_ROLE =
- keccak256("E_USD_PAUSE_OPERATOR_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 Eternal USD", "eUSD");
- }
-
- /**
- * @dev AC role, owner of which can mint eUSD token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return E_USD_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn eUSD token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return E_USD_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause eUSD token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return E_USD_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/hbUSDC/HBUsdcCustomAggregatorFeed.sol b/contracts/products/hbUSDC/HBUsdcCustomAggregatorFeed.sol
deleted file mode 100644
index b38931d7..00000000
--- a/contracts/products/hbUSDC/HBUsdcCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./HBUsdcMidasAccessControlRoles.sol";
-
-/**
- * @title HBUsdcCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for hbUSDC,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract HBUsdcCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- HBUsdcMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return HB_USDC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/hbUSDC/HBUsdcDataFeed.sol b/contracts/products/hbUSDC/HBUsdcDataFeed.sol
deleted file mode 100644
index fc1b25e9..00000000
--- a/contracts/products/hbUSDC/HBUsdcDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./HBUsdcMidasAccessControlRoles.sol";
-
-/**
- * @title HBUsdcDataFeed
- * @notice DataFeed for hbUSDC product
- * @author RedDuck Software
- */
-contract HBUsdcDataFeed is DataFeed, HBUsdcMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return HB_USDC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/hbUSDC/HBUsdcDepositVault.sol b/contracts/products/hbUSDC/HBUsdcDepositVault.sol
deleted file mode 100644
index 2f7bc8e1..00000000
--- a/contracts/products/hbUSDC/HBUsdcDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./HBUsdcMidasAccessControlRoles.sol";
-
-/**
- * @title HBUsdcDepositVault
- * @notice Smart contract that handles hbUSDC minting
- * @author RedDuck Software
- */
-contract HBUsdcDepositVault is DepositVault, HBUsdcMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return HB_USDC_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/hbUSDC/HBUsdcMidasAccessControlRoles.sol b/contracts/products/hbUSDC/HBUsdcMidasAccessControlRoles.sol
deleted file mode 100644
index 13ef3707..00000000
--- a/contracts/products/hbUSDC/HBUsdcMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title HBUsdcMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for hbUSDC contracts
- * @author RedDuck Software
- */
-abstract contract HBUsdcMidasAccessControlRoles {
- /**
- * @notice actor that can manage HBUsdcDepositVault
- */
- bytes32 public constant HB_USDC_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("HB_USDC_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage HBUsdcRedemptionVault
- */
- bytes32 public constant HB_USDC_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("HB_USDC_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage HBUsdcCustomAggregatorFeed and HBUsdcDataFeed
- */
- bytes32 public constant HB_USDC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("HB_USDC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/hbUSDC/HBUsdcRedemptionVaultWithSwapper.sol b/contracts/products/hbUSDC/HBUsdcRedemptionVaultWithSwapper.sol
deleted file mode 100644
index db38d856..00000000
--- a/contracts/products/hbUSDC/HBUsdcRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./HBUsdcMidasAccessControlRoles.sol";
-
-/**
- * @title HBUsdcRedemptionVaultWithSwapper
- * @notice Smart contract that handles hbUSDC redemptions
- * @author RedDuck Software
- */
-contract HBUsdcRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- HBUsdcMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return HB_USDC_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/hbUSDC/hbUSDC.sol b/contracts/products/hbUSDC/hbUSDC.sol
deleted file mode 100644
index 4e73862d..00000000
--- a/contracts/products/hbUSDC/hbUSDC.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title hbUSDC
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract hbUSDC is mToken {
- /**
- * @notice actor that can mint hbUSDC
- */
- bytes32 public constant HB_USDC_MINT_OPERATOR_ROLE =
- keccak256("HB_USDC_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn hbUSDC
- */
- bytes32 public constant HB_USDC_BURN_OPERATOR_ROLE =
- keccak256("HB_USDC_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause hbUSDC
- */
- bytes32 public constant HB_USDC_PAUSE_OPERATOR_ROLE =
- keccak256("HB_USDC_PAUSE_OPERATOR_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 ("Hyperbeat USDC", "hbUSDC");
- }
-
- /**
- * @dev AC role, owner of which can mint hbUSDC token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return HB_USDC_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn hbUSDC token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return HB_USDC_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause hbUSDC token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return HB_USDC_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/hbUSDT/HBUsdtCustomAggregatorFeed.sol b/contracts/products/hbUSDT/HBUsdtCustomAggregatorFeed.sol
deleted file mode 100644
index 1daea88c..00000000
--- a/contracts/products/hbUSDT/HBUsdtCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./HBUsdtMidasAccessControlRoles.sol";
-
-/**
- * @title HBUsdtCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for hbUSDT,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract HBUsdtCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- HBUsdtMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return HB_USDT_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/hbUSDT/HBUsdtDataFeed.sol b/contracts/products/hbUSDT/HBUsdtDataFeed.sol
deleted file mode 100644
index 54a1124c..00000000
--- a/contracts/products/hbUSDT/HBUsdtDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./HBUsdtMidasAccessControlRoles.sol";
-
-/**
- * @title HBUsdtDataFeed
- * @notice DataFeed for hbUSDT product
- * @author RedDuck Software
- */
-contract HBUsdtDataFeed is DataFeed, HBUsdtMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return HB_USDT_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/hbUSDT/HBUsdtDepositVault.sol b/contracts/products/hbUSDT/HBUsdtDepositVault.sol
deleted file mode 100644
index e00a3dcd..00000000
--- a/contracts/products/hbUSDT/HBUsdtDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./HBUsdtMidasAccessControlRoles.sol";
-
-/**
- * @title HBUsdtDepositVault
- * @notice Smart contract that handles hbUSDT minting
- * @author RedDuck Software
- */
-contract HBUsdtDepositVault is DepositVault, HBUsdtMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return HB_USDT_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/hbUSDT/HBUsdtMidasAccessControlRoles.sol b/contracts/products/hbUSDT/HBUsdtMidasAccessControlRoles.sol
deleted file mode 100644
index a6934fba..00000000
--- a/contracts/products/hbUSDT/HBUsdtMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title HBUsdtMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for hbUSDT contracts
- * @author RedDuck Software
- */
-abstract contract HBUsdtMidasAccessControlRoles {
- /**
- * @notice actor that can manage HBUsdtDepositVault
- */
- bytes32 public constant HB_USDT_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("HB_USDT_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage HBUsdtRedemptionVault
- */
- bytes32 public constant HB_USDT_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("HB_USDT_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage HBUsdtCustomAggregatorFeed and HBUsdtDataFeed
- */
- bytes32 public constant HB_USDT_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("HB_USDT_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/hbUSDT/HBUsdtRedemptionVaultWithSwapper.sol b/contracts/products/hbUSDT/HBUsdtRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 8ada6a0b..00000000
--- a/contracts/products/hbUSDT/HBUsdtRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./HBUsdtMidasAccessControlRoles.sol";
-
-/**
- * @title HBUsdtRedemptionVaultWithSwapper
- * @notice Smart contract that handles hbUSDT redemptions
- * @author RedDuck Software
- */
-contract HBUsdtRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- HBUsdtMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return HB_USDT_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/hbUSDT/hbUSDT.sol b/contracts/products/hbUSDT/hbUSDT.sol
deleted file mode 100644
index 77956ed8..00000000
--- a/contracts/products/hbUSDT/hbUSDT.sol
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-import "../../mToken.sol";
-
-/**
- * @title hbUSDT
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract hbUSDT is mToken {
- /**
- * @notice actor that can mint hbUSDT
- */
- bytes32 public constant HB_USDT_MINT_OPERATOR_ROLE =
- keccak256("HB_USDT_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn hbUSDT
- */
- bytes32 public constant HB_USDT_BURN_OPERATOR_ROLE =
- keccak256("HB_USDT_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause hbUSDT
- */
- bytes32 public constant HB_USDT_PAUSE_OPERATOR_ROLE =
- keccak256("HB_USDT_PAUSE_OPERATOR_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 ("Hyperbeat USDT", "hbUSDT");
- }
-
- /**
- * @dev AC role, owner of which can mint hbUSDT token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return HB_USDT_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn hbUSDT token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return HB_USDT_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause hbUSDT token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return HB_USDT_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/hbXAUt/HBXautCustomAggregatorFeed.sol b/contracts/products/hbXAUt/HBXautCustomAggregatorFeed.sol
deleted file mode 100644
index 19f4ce29..00000000
--- a/contracts/products/hbXAUt/HBXautCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./HBXautMidasAccessControlRoles.sol";
-
-/**
- * @title HBXautCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for hbXAUt,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract HBXautCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- HBXautMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return HB_XAUT_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/hbXAUt/HBXautDataFeed.sol b/contracts/products/hbXAUt/HBXautDataFeed.sol
deleted file mode 100644
index 7e7af9df..00000000
--- a/contracts/products/hbXAUt/HBXautDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./HBXautMidasAccessControlRoles.sol";
-
-/**
- * @title HBXautDataFeed
- * @notice DataFeed for hbXAUt product
- * @author RedDuck Software
- */
-contract HBXautDataFeed is DataFeed, HBXautMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return HB_XAUT_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/hbXAUt/HBXautDepositVault.sol b/contracts/products/hbXAUt/HBXautDepositVault.sol
deleted file mode 100644
index e6588bd2..00000000
--- a/contracts/products/hbXAUt/HBXautDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./HBXautMidasAccessControlRoles.sol";
-
-/**
- * @title HBXautDepositVault
- * @notice Smart contract that handles hbXAUt minting
- * @author RedDuck Software
- */
-contract HBXautDepositVault is DepositVault, HBXautMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return HB_XAUT_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/hbXAUt/HBXautMidasAccessControlRoles.sol b/contracts/products/hbXAUt/HBXautMidasAccessControlRoles.sol
deleted file mode 100644
index 6a70338f..00000000
--- a/contracts/products/hbXAUt/HBXautMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title HBXautMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for hbXAUt contracts
- * @author RedDuck Software
- */
-abstract contract HBXautMidasAccessControlRoles {
- /**
- * @notice actor that can manage HBXautDepositVault
- */
- bytes32 public constant HB_XAUT_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("HB_XAUT_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage HBXautRedemptionVault
- */
- bytes32 public constant HB_XAUT_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("HB_XAUT_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage HBXautCustomAggregatorFeed and HBXautDataFeed
- */
- bytes32 public constant HB_XAUT_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("HB_XAUT_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/hbXAUt/HBXautRedemptionVaultWithSwapper.sol b/contracts/products/hbXAUt/HBXautRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 66c1c944..00000000
--- a/contracts/products/hbXAUt/HBXautRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./HBXautMidasAccessControlRoles.sol";
-
-/**
- * @title HBXautRedemptionVaultWithSwapper
- * @notice Smart contract that handles hbXAUt redemptions
- * @author RedDuck Software
- */
-contract HBXautRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- HBXautMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return HB_XAUT_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/hbXAUt/hbXAUt.sol b/contracts/products/hbXAUt/hbXAUt.sol
deleted file mode 100644
index 2cb7ee3b..00000000
--- a/contracts/products/hbXAUt/hbXAUt.sol
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-import "../../mToken.sol";
-
-/**
- * @title hbXAUt
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract hbXAUt is mToken {
- /**
- * @notice actor that can mint hbXAUt
- */
- bytes32 public constant HB_XAUT_MINT_OPERATOR_ROLE =
- keccak256("HB_XAUT_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn hbXAUt
- */
- bytes32 public constant HB_XAUT_BURN_OPERATOR_ROLE =
- keccak256("HB_XAUT_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause hbXAUt
- */
- bytes32 public constant HB_XAUT_PAUSE_OPERATOR_ROLE =
- keccak256("HB_XAUT_PAUSE_OPERATOR_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 ("Hyperbeat XAUt", "hbXAUt");
- }
-
- /**
- * @dev AC role, owner of which can mint hbXAUt token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return HB_XAUT_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn hbXAUt token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return HB_XAUT_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause hbXAUt token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return HB_XAUT_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/hypeBTC/HypeBtcCustomAggregatorFeed.sol b/contracts/products/hypeBTC/HypeBtcCustomAggregatorFeed.sol
deleted file mode 100644
index b689cc9d..00000000
--- a/contracts/products/hypeBTC/HypeBtcCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./HypeBtcMidasAccessControlRoles.sol";
-
-/**
- * @title HypeBtcCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for hypeBTC,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract HypeBtcCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- HypeBtcMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return HYPE_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/hypeBTC/HypeBtcDataFeed.sol b/contracts/products/hypeBTC/HypeBtcDataFeed.sol
deleted file mode 100644
index 825d236a..00000000
--- a/contracts/products/hypeBTC/HypeBtcDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./HypeBtcMidasAccessControlRoles.sol";
-
-/**
- * @title HypeBtcDataFeed
- * @notice DataFeed for hypeBTC product
- * @author RedDuck Software
- */
-contract HypeBtcDataFeed is DataFeed, HypeBtcMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return HYPE_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/hypeBTC/HypeBtcDepositVault.sol b/contracts/products/hypeBTC/HypeBtcDepositVault.sol
deleted file mode 100644
index 4c378b7b..00000000
--- a/contracts/products/hypeBTC/HypeBtcDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./HypeBtcMidasAccessControlRoles.sol";
-
-/**
- * @title HypeBtcDepositVault
- * @notice Smart contract that handles hypeBTC minting
- * @author RedDuck Software
- */
-contract HypeBtcDepositVault is DepositVault, HypeBtcMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return HYPE_BTC_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/hypeBTC/HypeBtcMidasAccessControlRoles.sol b/contracts/products/hypeBTC/HypeBtcMidasAccessControlRoles.sol
deleted file mode 100644
index 848360ed..00000000
--- a/contracts/products/hypeBTC/HypeBtcMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title HypeBtcMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for hypeBTC contracts
- * @author RedDuck Software
- */
-abstract contract HypeBtcMidasAccessControlRoles {
- /**
- * @notice actor that can manage HypeBtcDepositVault
- */
- bytes32 public constant HYPE_BTC_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("HYPE_BTC_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage HypeBtcRedemptionVault
- */
- bytes32 public constant HYPE_BTC_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("HYPE_BTC_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage HypeBtcCustomAggregatorFeed and HypeBtcDataFeed
- */
- bytes32 public constant HYPE_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("HYPE_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/hypeBTC/HypeBtcRedemptionVaultWithSwapper.sol b/contracts/products/hypeBTC/HypeBtcRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 642e78c8..00000000
--- a/contracts/products/hypeBTC/HypeBtcRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./HypeBtcMidasAccessControlRoles.sol";
-
-/**
- * @title HypeBtcRedemptionVaultWithSwapper
- * @notice Smart contract that handles hypeBTC redemptions
- * @author RedDuck Software
- */
-contract HypeBtcRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- HypeBtcMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return HYPE_BTC_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/hypeBTC/hypeBTC.sol b/contracts/products/hypeBTC/hypeBTC.sol
deleted file mode 100644
index cdb3a9d7..00000000
--- a/contracts/products/hypeBTC/hypeBTC.sol
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-import "../../mToken.sol";
-
-/**
- * @title hypeBTC
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract hypeBTC is mToken {
- /**
- * @notice actor that can mint hypeBTC
- */
- bytes32 public constant HYPE_BTC_MINT_OPERATOR_ROLE =
- keccak256("HYPE_BTC_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn hypeBTC
- */
- bytes32 public constant HYPE_BTC_BURN_OPERATOR_ROLE =
- keccak256("HYPE_BTC_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause hypeBTC
- */
- bytes32 public constant HYPE_BTC_PAUSE_OPERATOR_ROLE =
- keccak256("HYPE_BTC_PAUSE_OPERATOR_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 ("HyperBTC Vault", "hypeBTC");
- }
-
- /**
- * @dev AC role, owner of which can mint hypeBTC token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return HYPE_BTC_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn hypeBTC token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return HYPE_BTC_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause hypeBTC token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return HYPE_BTC_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/hypeETH/HypeEthCustomAggregatorFeed.sol b/contracts/products/hypeETH/HypeEthCustomAggregatorFeed.sol
deleted file mode 100644
index 1e633afb..00000000
--- a/contracts/products/hypeETH/HypeEthCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./HypeEthMidasAccessControlRoles.sol";
-
-/**
- * @title HypeEthCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for hypeETH,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract HypeEthCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- HypeEthMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return HYPE_ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/hypeETH/HypeEthDataFeed.sol b/contracts/products/hypeETH/HypeEthDataFeed.sol
deleted file mode 100644
index 875eb699..00000000
--- a/contracts/products/hypeETH/HypeEthDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./HypeEthMidasAccessControlRoles.sol";
-
-/**
- * @title HypeEthDataFeed
- * @notice DataFeed for hypeETH product
- * @author RedDuck Software
- */
-contract HypeEthDataFeed is DataFeed, HypeEthMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return HYPE_ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/hypeETH/HypeEthDepositVault.sol b/contracts/products/hypeETH/HypeEthDepositVault.sol
deleted file mode 100644
index 63192c82..00000000
--- a/contracts/products/hypeETH/HypeEthDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./HypeEthMidasAccessControlRoles.sol";
-
-/**
- * @title HypeEthDepositVault
- * @notice Smart contract that handles hypeETH minting
- * @author RedDuck Software
- */
-contract HypeEthDepositVault is DepositVault, HypeEthMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return HYPE_ETH_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/hypeETH/HypeEthMidasAccessControlRoles.sol b/contracts/products/hypeETH/HypeEthMidasAccessControlRoles.sol
deleted file mode 100644
index 2af6d10c..00000000
--- a/contracts/products/hypeETH/HypeEthMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title HypeEthMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for hypeETH contracts
- * @author RedDuck Software
- */
-abstract contract HypeEthMidasAccessControlRoles {
- /**
- * @notice actor that can manage HypeEthDepositVault
- */
- bytes32 public constant HYPE_ETH_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("HYPE_ETH_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage HypeEthRedemptionVault
- */
- bytes32 public constant HYPE_ETH_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("HYPE_ETH_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage HypeEthCustomAggregatorFeed and HypeEthDataFeed
- */
- bytes32 public constant HYPE_ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("HYPE_ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/hypeETH/HypeEthRedemptionVaultWithSwapper.sol b/contracts/products/hypeETH/HypeEthRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 9b711a6b..00000000
--- a/contracts/products/hypeETH/HypeEthRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./HypeEthMidasAccessControlRoles.sol";
-
-/**
- * @title HypeEthRedemptionVaultWithSwapper
- * @notice Smart contract that handles hypeETH redemptions
- * @author RedDuck Software
- */
-contract HypeEthRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- HypeEthMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return HYPE_ETH_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/hypeETH/hypeETH.sol b/contracts/products/hypeETH/hypeETH.sol
deleted file mode 100644
index 21866ca9..00000000
--- a/contracts/products/hypeETH/hypeETH.sol
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-import "../../mToken.sol";
-
-/**
- * @title hypeETH
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract hypeETH is mToken {
- /**
- * @notice actor that can mint hypeETH
- */
- bytes32 public constant HYPE_ETH_MINT_OPERATOR_ROLE =
- keccak256("HYPE_ETH_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn hypeETH
- */
- bytes32 public constant HYPE_ETH_BURN_OPERATOR_ROLE =
- keccak256("HYPE_ETH_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause hypeETH
- */
- bytes32 public constant HYPE_ETH_PAUSE_OPERATOR_ROLE =
- keccak256("HYPE_ETH_PAUSE_OPERATOR_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 ("HyperETH Vault", "hypeETH");
- }
-
- /**
- * @dev AC role, owner of which can mint hypeETH token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return HYPE_ETH_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn hypeETH token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return HYPE_ETH_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause hypeETH token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return HYPE_ETH_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/hypeUSD/HypeUsdCustomAggregatorFeed.sol b/contracts/products/hypeUSD/HypeUsdCustomAggregatorFeed.sol
deleted file mode 100644
index 1339cd2a..00000000
--- a/contracts/products/hypeUSD/HypeUsdCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./HypeUsdMidasAccessControlRoles.sol";
-
-/**
- * @title HypeUsdCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for hypeUSD,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract HypeUsdCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- HypeUsdMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return HYPE_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/hypeUSD/HypeUsdDataFeed.sol b/contracts/products/hypeUSD/HypeUsdDataFeed.sol
deleted file mode 100644
index 67767723..00000000
--- a/contracts/products/hypeUSD/HypeUsdDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./HypeUsdMidasAccessControlRoles.sol";
-
-/**
- * @title HypeUsdDataFeed
- * @notice DataFeed for hypeUSD product
- * @author RedDuck Software
- */
-contract HypeUsdDataFeed is DataFeed, HypeUsdMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return HYPE_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/hypeUSD/HypeUsdDepositVault.sol b/contracts/products/hypeUSD/HypeUsdDepositVault.sol
deleted file mode 100644
index 44ed47b2..00000000
--- a/contracts/products/hypeUSD/HypeUsdDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./HypeUsdMidasAccessControlRoles.sol";
-
-/**
- * @title HypeUsdDepositVault
- * @notice Smart contract that handles hypeUSD minting
- * @author RedDuck Software
- */
-contract HypeUsdDepositVault is DepositVault, HypeUsdMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return HYPE_USD_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/hypeUSD/HypeUsdMidasAccessControlRoles.sol b/contracts/products/hypeUSD/HypeUsdMidasAccessControlRoles.sol
deleted file mode 100644
index babcc9b6..00000000
--- a/contracts/products/hypeUSD/HypeUsdMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title HypeUsdMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for hypeUSD contracts
- * @author RedDuck Software
- */
-abstract contract HypeUsdMidasAccessControlRoles {
- /**
- * @notice actor that can manage HypeUsdDepositVault
- */
- bytes32 public constant HYPE_USD_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("HYPE_USD_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage HypeUsdRedemptionVault
- */
- bytes32 public constant HYPE_USD_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("HYPE_USD_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage HypeUsdCustomAggregatorFeed and HypeUsdDataFeed
- */
- bytes32 public constant HYPE_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("HYPE_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/hypeUSD/HypeUsdRedemptionVaultWithSwapper.sol b/contracts/products/hypeUSD/HypeUsdRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 27a40fed..00000000
--- a/contracts/products/hypeUSD/HypeUsdRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./HypeUsdMidasAccessControlRoles.sol";
-
-/**
- * @title HypeUsdRedemptionVaultWithSwapper
- * @notice Smart contract that handles hypeUSD redemptions
- * @author RedDuck Software
- */
-contract HypeUsdRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- HypeUsdMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return HYPE_USD_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/hypeUSD/hypeUSD.sol b/contracts/products/hypeUSD/hypeUSD.sol
deleted file mode 100644
index 70ef48d9..00000000
--- a/contracts/products/hypeUSD/hypeUSD.sol
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-import "../../mToken.sol";
-
-/**
- * @title hypeUSD
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract hypeUSD is mToken {
- /**
- * @notice actor that can mint hypeUSD
- */
- bytes32 public constant HYPE_USD_MINT_OPERATOR_ROLE =
- keccak256("HYPE_USD_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn hypeUSD
- */
- bytes32 public constant HYPE_USD_BURN_OPERATOR_ROLE =
- keccak256("HYPE_USD_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause hypeUSD
- */
- bytes32 public constant HYPE_USD_PAUSE_OPERATOR_ROLE =
- keccak256("HYPE_USD_PAUSE_OPERATOR_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 ("HyperUSD Vault", "hypeUSD");
- }
-
- /**
- * @dev AC role, owner of which can mint hypeUSD token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return HYPE_USD_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn hypeUSD token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return HYPE_USD_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause hypeUSD token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return HYPE_USD_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/kitBTC/KitBtcCustomAggregatorFeed.sol b/contracts/products/kitBTC/KitBtcCustomAggregatorFeed.sol
deleted file mode 100644
index 9316309a..00000000
--- a/contracts/products/kitBTC/KitBtcCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./KitBtcMidasAccessControlRoles.sol";
-
-/**
- * @title KitBtcCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for kitBTC,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract KitBtcCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- KitBtcMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return KIT_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/kitBTC/KitBtcDataFeed.sol b/contracts/products/kitBTC/KitBtcDataFeed.sol
deleted file mode 100644
index 7a2c396d..00000000
--- a/contracts/products/kitBTC/KitBtcDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./KitBtcMidasAccessControlRoles.sol";
-
-/**
- * @title KitBtcDataFeed
- * @notice DataFeed for kitBTC product
- * @author RedDuck Software
- */
-contract KitBtcDataFeed is DataFeed, KitBtcMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return KIT_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/kitBTC/KitBtcDepositVault.sol b/contracts/products/kitBTC/KitBtcDepositVault.sol
deleted file mode 100644
index 0d330030..00000000
--- a/contracts/products/kitBTC/KitBtcDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./KitBtcMidasAccessControlRoles.sol";
-
-/**
- * @title KitBtcDepositVault
- * @notice Smart contract that handles kitBTC minting
- * @author RedDuck Software
- */
-contract KitBtcDepositVault is DepositVault, KitBtcMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return KIT_BTC_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/kitBTC/KitBtcMidasAccessControlRoles.sol b/contracts/products/kitBTC/KitBtcMidasAccessControlRoles.sol
deleted file mode 100644
index 6230e6ac..00000000
--- a/contracts/products/kitBTC/KitBtcMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title KitBtcMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for kitBTC contracts
- * @author RedDuck Software
- */
-abstract contract KitBtcMidasAccessControlRoles {
- /**
- * @notice actor that can manage KitBtcDepositVault
- */
- bytes32 public constant KIT_BTC_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("KIT_BTC_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage KitBtcRedemptionVault
- */
- bytes32 public constant KIT_BTC_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("KIT_BTC_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage KitBtcCustomAggregatorFeed and KitBtcDataFeed
- */
- bytes32 public constant KIT_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("KIT_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/kitBTC/KitBtcRedemptionVaultWithSwapper.sol b/contracts/products/kitBTC/KitBtcRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 2eaebcd3..00000000
--- a/contracts/products/kitBTC/KitBtcRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./KitBtcMidasAccessControlRoles.sol";
-
-/**
- * @title KitBtcRedemptionVaultWithSwapper
- * @notice Smart contract that handles kitBTC redemptions
- * @author RedDuck Software
- */
-contract KitBtcRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- KitBtcMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return KIT_BTC_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/kitBTC/kitBTC.sol b/contracts/products/kitBTC/kitBTC.sol
deleted file mode 100644
index 0e0db27a..00000000
--- a/contracts/products/kitBTC/kitBTC.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title kitBTC
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract kitBTC is mToken {
- /**
- * @notice actor that can mint kitBTC
- */
- bytes32 public constant KIT_BTC_MINT_OPERATOR_ROLE =
- keccak256("KIT_BTC_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn kitBTC
- */
- bytes32 public constant KIT_BTC_BURN_OPERATOR_ROLE =
- keccak256("KIT_BTC_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause kitBTC
- */
- bytes32 public constant KIT_BTC_PAUSE_OPERATOR_ROLE =
- keccak256("KIT_BTC_PAUSE_OPERATOR_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 ("Kitchen Pre-deposit $kitBTC", "$kitBTC");
- }
-
- /**
- * @dev AC role, owner of which can mint kitBTC token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return KIT_BTC_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn kitBTC token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return KIT_BTC_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause kitBTC token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return KIT_BTC_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/kitHYPE/KitHypeCustomAggregatorFeed.sol b/contracts/products/kitHYPE/KitHypeCustomAggregatorFeed.sol
deleted file mode 100644
index a71e6d04..00000000
--- a/contracts/products/kitHYPE/KitHypeCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./KitHypeMidasAccessControlRoles.sol";
-
-/**
- * @title KitHypeCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for kitHYPE,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract KitHypeCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- KitHypeMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return KIT_HYPE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/kitHYPE/KitHypeDataFeed.sol b/contracts/products/kitHYPE/KitHypeDataFeed.sol
deleted file mode 100644
index 13eb7c5c..00000000
--- a/contracts/products/kitHYPE/KitHypeDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./KitHypeMidasAccessControlRoles.sol";
-
-/**
- * @title KitHypeDataFeed
- * @notice DataFeed for kitHYPE product
- * @author RedDuck Software
- */
-contract KitHypeDataFeed is DataFeed, KitHypeMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return KIT_HYPE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/kitHYPE/KitHypeDepositVault.sol b/contracts/products/kitHYPE/KitHypeDepositVault.sol
deleted file mode 100644
index d2faa1c4..00000000
--- a/contracts/products/kitHYPE/KitHypeDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./KitHypeMidasAccessControlRoles.sol";
-
-/**
- * @title KitHypeDepositVault
- * @notice Smart contract that handles kitHYPE minting
- * @author RedDuck Software
- */
-contract KitHypeDepositVault is DepositVault, KitHypeMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return KIT_HYPE_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/kitHYPE/KitHypeMidasAccessControlRoles.sol b/contracts/products/kitHYPE/KitHypeMidasAccessControlRoles.sol
deleted file mode 100644
index 629e026e..00000000
--- a/contracts/products/kitHYPE/KitHypeMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title KitHypeMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for kitHYPE contracts
- * @author RedDuck Software
- */
-abstract contract KitHypeMidasAccessControlRoles {
- /**
- * @notice actor that can manage KitHypeDepositVault
- */
- bytes32 public constant KIT_HYPE_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("KIT_HYPE_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage KitHypeRedemptionVault
- */
- bytes32 public constant KIT_HYPE_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("KIT_HYPE_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage KitHypeCustomAggregatorFeed and KitHypeDataFeed
- */
- bytes32 public constant KIT_HYPE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("KIT_HYPE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/kitHYPE/KitHypeRedemptionVaultWithSwapper.sol b/contracts/products/kitHYPE/KitHypeRedemptionVaultWithSwapper.sol
deleted file mode 100644
index fe528166..00000000
--- a/contracts/products/kitHYPE/KitHypeRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./KitHypeMidasAccessControlRoles.sol";
-
-/**
- * @title KitHypeRedemptionVaultWithSwapper
- * @notice Smart contract that handles kitHYPE redemptions
- * @author RedDuck Software
- */
-contract KitHypeRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- KitHypeMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return KIT_HYPE_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/kitHYPE/kitHYPE.sol b/contracts/products/kitHYPE/kitHYPE.sol
deleted file mode 100644
index d5dd83b1..00000000
--- a/contracts/products/kitHYPE/kitHYPE.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title kitHYPE
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract kitHYPE is mToken {
- /**
- * @notice actor that can mint kitHYPE
- */
- bytes32 public constant KIT_HYPE_MINT_OPERATOR_ROLE =
- keccak256("KIT_HYPE_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn kitHYPE
- */
- bytes32 public constant KIT_HYPE_BURN_OPERATOR_ROLE =
- keccak256("KIT_HYPE_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause kitHYPE
- */
- bytes32 public constant KIT_HYPE_PAUSE_OPERATOR_ROLE =
- keccak256("KIT_HYPE_PAUSE_OPERATOR_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 ("Kitchen Pre-deposit $kitHYPE", "$kitHYPE");
- }
-
- /**
- * @dev AC role, owner of which can mint kitHYPE token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return KIT_HYPE_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn kitHYPE token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return KIT_HYPE_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause kitHYPE token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return KIT_HYPE_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/kitUSD/KitUsdCustomAggregatorFeed.sol b/contracts/products/kitUSD/KitUsdCustomAggregatorFeed.sol
deleted file mode 100644
index b078a929..00000000
--- a/contracts/products/kitUSD/KitUsdCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./KitUsdMidasAccessControlRoles.sol";
-
-/**
- * @title KitUsdCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for kitUSD,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract KitUsdCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- KitUsdMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return KIT_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/kitUSD/KitUsdDataFeed.sol b/contracts/products/kitUSD/KitUsdDataFeed.sol
deleted file mode 100644
index d6e4874d..00000000
--- a/contracts/products/kitUSD/KitUsdDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./KitUsdMidasAccessControlRoles.sol";
-
-/**
- * @title KitUsdDataFeed
- * @notice DataFeed for kitUSD product
- * @author RedDuck Software
- */
-contract KitUsdDataFeed is DataFeed, KitUsdMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return KIT_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/kitUSD/KitUsdDepositVault.sol b/contracts/products/kitUSD/KitUsdDepositVault.sol
deleted file mode 100644
index 6f0267b1..00000000
--- a/contracts/products/kitUSD/KitUsdDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./KitUsdMidasAccessControlRoles.sol";
-
-/**
- * @title KitUsdDepositVault
- * @notice Smart contract that handles kitUSD minting
- * @author RedDuck Software
- */
-contract KitUsdDepositVault is DepositVault, KitUsdMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return KIT_USD_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/kitUSD/KitUsdMidasAccessControlRoles.sol b/contracts/products/kitUSD/KitUsdMidasAccessControlRoles.sol
deleted file mode 100644
index 202b19a7..00000000
--- a/contracts/products/kitUSD/KitUsdMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title KitUsdMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for kitUSD contracts
- * @author RedDuck Software
- */
-abstract contract KitUsdMidasAccessControlRoles {
- /**
- * @notice actor that can manage KitUsdDepositVault
- */
- bytes32 public constant KIT_USD_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("KIT_USD_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage KitUsdRedemptionVault
- */
- bytes32 public constant KIT_USD_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("KIT_USD_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage KitUsdCustomAggregatorFeed and KitUsdDataFeed
- */
- bytes32 public constant KIT_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("KIT_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/kitUSD/KitUsdRedemptionVaultWithSwapper.sol b/contracts/products/kitUSD/KitUsdRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 99f48932..00000000
--- a/contracts/products/kitUSD/KitUsdRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./KitUsdMidasAccessControlRoles.sol";
-
-/**
- * @title KitUsdRedemptionVaultWithSwapper
- * @notice Smart contract that handles kitUSD redemptions
- * @author RedDuck Software
- */
-contract KitUsdRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- KitUsdMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return KIT_USD_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/kitUSD/kitUSD.sol b/contracts/products/kitUSD/kitUSD.sol
deleted file mode 100644
index 07f42997..00000000
--- a/contracts/products/kitUSD/kitUSD.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title kitUSD
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract kitUSD is mToken {
- /**
- * @notice actor that can mint kitUSD
- */
- bytes32 public constant KIT_USD_MINT_OPERATOR_ROLE =
- keccak256("KIT_USD_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn kitUSD
- */
- bytes32 public constant KIT_USD_BURN_OPERATOR_ROLE =
- keccak256("KIT_USD_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause kitUSD
- */
- bytes32 public constant KIT_USD_PAUSE_OPERATOR_ROLE =
- keccak256("KIT_USD_PAUSE_OPERATOR_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 ("Kitchen Pre-deposit $kitUSD", "$kitUSD");
- }
-
- /**
- * @dev AC role, owner of which can mint kitUSD token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return KIT_USD_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn kitUSD token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return KIT_USD_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause kitUSD token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return KIT_USD_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/kmiUSD/KmiUsdCustomAggregatorFeed.sol b/contracts/products/kmiUSD/KmiUsdCustomAggregatorFeed.sol
deleted file mode 100644
index 298bef64..00000000
--- a/contracts/products/kmiUSD/KmiUsdCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./KmiUsdMidasAccessControlRoles.sol";
-
-/**
- * @title KmiUsdCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for kmiUSD,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract KmiUsdCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- KmiUsdMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return KMI_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/kmiUSD/KmiUsdDataFeed.sol b/contracts/products/kmiUSD/KmiUsdDataFeed.sol
deleted file mode 100644
index 6065073b..00000000
--- a/contracts/products/kmiUSD/KmiUsdDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./KmiUsdMidasAccessControlRoles.sol";
-
-/**
- * @title KmiUsdDataFeed
- * @notice DataFeed for kmiUSD product
- * @author RedDuck Software
- */
-contract KmiUsdDataFeed is DataFeed, KmiUsdMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return KMI_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/kmiUSD/KmiUsdDepositVault.sol b/contracts/products/kmiUSD/KmiUsdDepositVault.sol
deleted file mode 100644
index 25af1dc5..00000000
--- a/contracts/products/kmiUSD/KmiUsdDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./KmiUsdMidasAccessControlRoles.sol";
-
-/**
- * @title KmiUsdDepositVault
- * @notice Smart contract that handles kmiUSD minting
- * @author RedDuck Software
- */
-contract KmiUsdDepositVault is DepositVault, KmiUsdMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return KMI_USD_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/kmiUSD/KmiUsdMidasAccessControlRoles.sol b/contracts/products/kmiUSD/KmiUsdMidasAccessControlRoles.sol
deleted file mode 100644
index 05c73d8e..00000000
--- a/contracts/products/kmiUSD/KmiUsdMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title KmiUsdMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for kmiUSD contracts
- * @author RedDuck Software
- */
-abstract contract KmiUsdMidasAccessControlRoles {
- /**
- * @notice actor that can manage KmiUsdDepositVault
- */
- bytes32 public constant KMI_USD_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("KMI_USD_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage KmiUsdRedemptionVault
- */
- bytes32 public constant KMI_USD_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("KMI_USD_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage KmiUsdCustomAggregatorFeed and KmiUsdDataFeed
- */
- bytes32 public constant KMI_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("KMI_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/kmiUSD/KmiUsdRedemptionVaultWithSwapper.sol b/contracts/products/kmiUSD/KmiUsdRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 4219902f..00000000
--- a/contracts/products/kmiUSD/KmiUsdRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./KmiUsdMidasAccessControlRoles.sol";
-
-/**
- * @title KmiUsdRedemptionVaultWithSwapper
- * @notice Smart contract that handles kmiUSD redemptions
- * @author RedDuck Software
- */
-contract KmiUsdRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- KmiUsdMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return KMI_USD_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/kmiUSD/kmiUSD.sol b/contracts/products/kmiUSD/kmiUSD.sol
deleted file mode 100644
index 9c4d5b31..00000000
--- a/contracts/products/kmiUSD/kmiUSD.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title kmiUSD
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract kmiUSD is mToken {
- /**
- * @notice actor that can mint kmiUSD
- */
- bytes32 public constant KMI_USD_MINT_OPERATOR_ROLE =
- keccak256("KMI_USD_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn kmiUSD
- */
- bytes32 public constant KMI_USD_BURN_OPERATOR_ROLE =
- keccak256("KMI_USD_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause kmiUSD
- */
- bytes32 public constant KMI_USD_PAUSE_OPERATOR_ROLE =
- keccak256("KMI_USD_PAUSE_OPERATOR_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 ("Katana miUSD", "kmiUSD");
- }
-
- /**
- * @dev AC role, owner of which can mint kmiUSD token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return KMI_USD_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn kmiUSD token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return KMI_USD_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause kmiUSD token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return KMI_USD_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/liquidHYPE/LiquidHypeCustomAggregatorFeed.sol b/contracts/products/liquidHYPE/LiquidHypeCustomAggregatorFeed.sol
deleted file mode 100644
index dcb5882d..00000000
--- a/contracts/products/liquidHYPE/LiquidHypeCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./LiquidHypeMidasAccessControlRoles.sol";
-
-/**
- * @title LiquidHypeCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for liquidHYPE,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract LiquidHypeCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- LiquidHypeMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return LIQUID_HYPE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/liquidHYPE/LiquidHypeDataFeed.sol b/contracts/products/liquidHYPE/LiquidHypeDataFeed.sol
deleted file mode 100644
index 19ea3986..00000000
--- a/contracts/products/liquidHYPE/LiquidHypeDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./LiquidHypeMidasAccessControlRoles.sol";
-
-/**
- * @title LiquidHypeDataFeed
- * @notice DataFeed for liquidHYPE product
- * @author RedDuck Software
- */
-contract LiquidHypeDataFeed is DataFeed, LiquidHypeMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return LIQUID_HYPE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/liquidHYPE/LiquidHypeDepositVault.sol b/contracts/products/liquidHYPE/LiquidHypeDepositVault.sol
deleted file mode 100644
index 78400e70..00000000
--- a/contracts/products/liquidHYPE/LiquidHypeDepositVault.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./LiquidHypeMidasAccessControlRoles.sol";
-
-/**
- * @title LiquidHypeDepositVault
- * @notice Smart contract that handles liquidHYPE minting
- * @author RedDuck Software
- */
-contract LiquidHypeDepositVault is
- DepositVault,
- LiquidHypeMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return LIQUID_HYPE_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/liquidHYPE/LiquidHypeMidasAccessControlRoles.sol b/contracts/products/liquidHYPE/LiquidHypeMidasAccessControlRoles.sol
deleted file mode 100644
index da2f066a..00000000
--- a/contracts/products/liquidHYPE/LiquidHypeMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title LiquidHypeMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for liquidHYPE contracts
- * @author RedDuck Software
- */
-abstract contract LiquidHypeMidasAccessControlRoles {
- /**
- * @notice actor that can manage LiquidHypeDepositVault
- */
- bytes32 public constant LIQUID_HYPE_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("LIQUID_HYPE_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage LiquidHypeRedemptionVault
- */
- bytes32 public constant LIQUID_HYPE_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("LIQUID_HYPE_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage LiquidHypeCustomAggregatorFeed and LiquidHypeDataFeed
- */
- bytes32 public constant LIQUID_HYPE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("LIQUID_HYPE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/liquidHYPE/LiquidHypeRedemptionVaultWithSwapper.sol b/contracts/products/liquidHYPE/LiquidHypeRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 65666573..00000000
--- a/contracts/products/liquidHYPE/LiquidHypeRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./LiquidHypeMidasAccessControlRoles.sol";
-
-/**
- * @title LiquidHypeRedemptionVaultWithSwapper
- * @notice Smart contract that handles liquidHYPE redemptions
- * @author RedDuck Software
- */
-contract LiquidHypeRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- LiquidHypeMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return LIQUID_HYPE_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/liquidHYPE/liquidHYPE.sol b/contracts/products/liquidHYPE/liquidHYPE.sol
deleted file mode 100644
index 2e1007b3..00000000
--- a/contracts/products/liquidHYPE/liquidHYPE.sol
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-import "../../mToken.sol";
-
-/**
- * @title liquidHYPE
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract liquidHYPE is mToken {
- /**
- * @notice actor that can mint liquidHYPE
- */
- bytes32 public constant LIQUID_HYPE_MINT_OPERATOR_ROLE =
- keccak256("LIQUID_HYPE_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn liquidHYPE
- */
- bytes32 public constant LIQUID_HYPE_BURN_OPERATOR_ROLE =
- keccak256("LIQUID_HYPE_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause liquidHYPE
- */
- bytes32 public constant LIQUID_HYPE_PAUSE_OPERATOR_ROLE =
- keccak256("LIQUID_HYPE_PAUSE_OPERATOR_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 ("Liquid HYPE Yield", "liquidHYPE");
- }
-
- /**
- * @dev AC role, owner of which can mint liquidHYPE token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return LIQUID_HYPE_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn liquidHYPE token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return LIQUID_HYPE_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause liquidHYPE token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return LIQUID_HYPE_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/liquidRESERVE/LiquidReserveCustomAggregatorFeed.sol b/contracts/products/liquidRESERVE/LiquidReserveCustomAggregatorFeed.sol
deleted file mode 100644
index 5e316eaf..00000000
--- a/contracts/products/liquidRESERVE/LiquidReserveCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./LiquidReserveMidasAccessControlRoles.sol";
-
-/**
- * @title LiquidReserveCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for liquidRESERVE,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract LiquidReserveCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- LiquidReserveMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return LIQUID_RESERVE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/liquidRESERVE/LiquidReserveDataFeed.sol b/contracts/products/liquidRESERVE/LiquidReserveDataFeed.sol
deleted file mode 100644
index e3b297c0..00000000
--- a/contracts/products/liquidRESERVE/LiquidReserveDataFeed.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./LiquidReserveMidasAccessControlRoles.sol";
-
-/**
- * @title LiquidReserveDataFeed
- * @notice DataFeed for liquidRESERVE product
- * @author RedDuck Software
- */
-contract LiquidReserveDataFeed is
- DataFeed,
- LiquidReserveMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return LIQUID_RESERVE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/liquidRESERVE/LiquidReserveDepositVault.sol b/contracts/products/liquidRESERVE/LiquidReserveDepositVault.sol
deleted file mode 100644
index b3538d93..00000000
--- a/contracts/products/liquidRESERVE/LiquidReserveDepositVault.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./LiquidReserveMidasAccessControlRoles.sol";
-
-/**
- * @title LiquidReserveDepositVault
- * @notice Smart contract that handles liquidRESERVE minting
- * @author RedDuck Software
- */
-contract LiquidReserveDepositVault is
- DepositVault,
- LiquidReserveMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return LIQUID_RESERVE_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/liquidRESERVE/LiquidReserveMidasAccessControlRoles.sol b/contracts/products/liquidRESERVE/LiquidReserveMidasAccessControlRoles.sol
deleted file mode 100644
index 6e2245ed..00000000
--- a/contracts/products/liquidRESERVE/LiquidReserveMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title LiquidReserveMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for liquidRESERVE contracts
- * @author RedDuck Software
- */
-abstract contract LiquidReserveMidasAccessControlRoles {
- /**
- * @notice actor that can manage LiquidReserveDepositVault
- */
- bytes32 public constant LIQUID_RESERVE_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("LIQUID_RESERVE_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage LiquidReserveRedemptionVault
- */
- bytes32 public constant LIQUID_RESERVE_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("LIQUID_RESERVE_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage LiquidReserveCustomAggregatorFeed and LiquidReserveDataFeed
- */
- bytes32 public constant LIQUID_RESERVE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("LIQUID_RESERVE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/liquidRESERVE/LiquidReserveRedemptionVaultWithSwapper.sol b/contracts/products/liquidRESERVE/LiquidReserveRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 84ac0446..00000000
--- a/contracts/products/liquidRESERVE/LiquidReserveRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./LiquidReserveMidasAccessControlRoles.sol";
-
-/**
- * @title LiquidReserveRedemptionVaultWithSwapper
- * @notice Smart contract that handles liquidRESERVE redemptions
- * @author RedDuck Software
- */
-contract LiquidReserveRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- LiquidReserveMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return LIQUID_RESERVE_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/liquidRESERVE/liquidRESERVE.sol b/contracts/products/liquidRESERVE/liquidRESERVE.sol
deleted file mode 100644
index 81fdb6b2..00000000
--- a/contracts/products/liquidRESERVE/liquidRESERVE.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title liquidRESERVE
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract liquidRESERVE is mToken {
- /**
- * @notice actor that can mint liquidRESERVE
- */
- bytes32 public constant LIQUID_RESERVE_MINT_OPERATOR_ROLE =
- keccak256("LIQUID_RESERVE_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn liquidRESERVE
- */
- bytes32 public constant LIQUID_RESERVE_BURN_OPERATOR_ROLE =
- keccak256("LIQUID_RESERVE_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause liquidRESERVE
- */
- bytes32 public constant LIQUID_RESERVE_PAUSE_OPERATOR_ROLE =
- keccak256("LIQUID_RESERVE_PAUSE_OPERATOR_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 ("Ether.Fi Liquid Reserve", "liquidRESERVE");
- }
-
- /**
- * @dev AC role, owner of which can mint liquidRESERVE token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return LIQUID_RESERVE_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn liquidRESERVE token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return LIQUID_RESERVE_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause liquidRESERVE token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return LIQUID_RESERVE_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/liquidRWA/LiquidRwaCustomAggregatorFeed.sol b/contracts/products/liquidRWA/LiquidRwaCustomAggregatorFeed.sol
deleted file mode 100644
index db1d9bc3..00000000
--- a/contracts/products/liquidRWA/LiquidRwaCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./LiquidRwaMidasAccessControlRoles.sol";
-
-/**
- * @title LiquidRwaCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for liquidRWA,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract LiquidRwaCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- LiquidRwaMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return LIQUID_RWA_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/liquidRWA/LiquidRwaDataFeed.sol b/contracts/products/liquidRWA/LiquidRwaDataFeed.sol
deleted file mode 100644
index 4a6213c2..00000000
--- a/contracts/products/liquidRWA/LiquidRwaDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./LiquidRwaMidasAccessControlRoles.sol";
-
-/**
- * @title LiquidRwaDataFeed
- * @notice DataFeed for liquidRWA product
- * @author RedDuck Software
- */
-contract LiquidRwaDataFeed is DataFeed, LiquidRwaMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return LIQUID_RWA_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/liquidRWA/LiquidRwaDepositVault.sol b/contracts/products/liquidRWA/LiquidRwaDepositVault.sol
deleted file mode 100644
index 2e2658fb..00000000
--- a/contracts/products/liquidRWA/LiquidRwaDepositVault.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./LiquidRwaMidasAccessControlRoles.sol";
-
-/**
- * @title LiquidRwaDepositVault
- * @notice Smart contract that handles liquidRWA minting
- * @author RedDuck Software
- */
-contract LiquidRwaDepositVault is
- DepositVault,
- LiquidRwaMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return LIQUID_RWA_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/liquidRWA/LiquidRwaMidasAccessControlRoles.sol b/contracts/products/liquidRWA/LiquidRwaMidasAccessControlRoles.sol
deleted file mode 100644
index 2d0c4d71..00000000
--- a/contracts/products/liquidRWA/LiquidRwaMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title LiquidRwaMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for liquidRWA contracts
- * @author RedDuck Software
- */
-abstract contract LiquidRwaMidasAccessControlRoles {
- /**
- * @notice actor that can manage LiquidRwaDepositVault
- */
- bytes32 public constant LIQUID_RWA_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("LIQUID_RWA_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage LiquidRwaRedemptionVault
- */
- bytes32 public constant LIQUID_RWA_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("LIQUID_RWA_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage LiquidRwaCustomAggregatorFeed and LiquidRwaDataFeed
- */
- bytes32 public constant LIQUID_RWA_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("LIQUID_RWA_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/liquidRWA/LiquidRwaRedemptionVaultWithSwapper.sol b/contracts/products/liquidRWA/LiquidRwaRedemptionVaultWithSwapper.sol
deleted file mode 100644
index f21416ed..00000000
--- a/contracts/products/liquidRWA/LiquidRwaRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./LiquidRwaMidasAccessControlRoles.sol";
-
-/**
- * @title LiquidRwaRedemptionVaultWithSwapper
- * @notice Smart contract that handles liquidRWA redemptions
- * @author RedDuck Software
- */
-contract LiquidRwaRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- LiquidRwaMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return LIQUID_RWA_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/liquidRWA/liquidRWA.sol b/contracts/products/liquidRWA/liquidRWA.sol
deleted file mode 100644
index 8446ce84..00000000
--- a/contracts/products/liquidRWA/liquidRWA.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title liquidRWA
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract liquidRWA is mToken {
- /**
- * @notice actor that can mint liquidRWA
- */
- bytes32 public constant LIQUID_RWA_MINT_OPERATOR_ROLE =
- keccak256("LIQUID_RWA_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn liquidRWA
- */
- bytes32 public constant LIQUID_RWA_BURN_OPERATOR_ROLE =
- keccak256("LIQUID_RWA_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause liquidRWA
- */
- bytes32 public constant LIQUID_RWA_PAUSE_OPERATOR_ROLE =
- keccak256("LIQUID_RWA_PAUSE_OPERATOR_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 ("Ether.fi Liquid RWA", "liquidRWA");
- }
-
- /**
- * @dev AC role, owner of which can mint liquidRWA token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return LIQUID_RWA_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn liquidRWA token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return LIQUID_RWA_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause liquidRWA token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return LIQUID_RWA_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/lstHYPE/LstHypeCustomAggregatorFeed.sol b/contracts/products/lstHYPE/LstHypeCustomAggregatorFeed.sol
deleted file mode 100644
index c95fc906..00000000
--- a/contracts/products/lstHYPE/LstHypeCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./LstHypeMidasAccessControlRoles.sol";
-
-/**
- * @title LstHypeCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for lstHYPE,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract LstHypeCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- LstHypeMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return LST_HYPE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/lstHYPE/LstHypeDataFeed.sol b/contracts/products/lstHYPE/LstHypeDataFeed.sol
deleted file mode 100644
index ae42a67b..00000000
--- a/contracts/products/lstHYPE/LstHypeDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./LstHypeMidasAccessControlRoles.sol";
-
-/**
- * @title LstHypeDataFeed
- * @notice DataFeed for lstHYPE product
- * @author RedDuck Software
- */
-contract LstHypeDataFeed is DataFeed, LstHypeMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return LST_HYPE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/lstHYPE/LstHypeDepositVault.sol b/contracts/products/lstHYPE/LstHypeDepositVault.sol
deleted file mode 100644
index b83b6a6f..00000000
--- a/contracts/products/lstHYPE/LstHypeDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./LstHypeMidasAccessControlRoles.sol";
-
-/**
- * @title LstHypeDepositVault
- * @notice Smart contract that handles LstHype minting
- * @author RedDuck Software
- */
-contract LstHypeDepositVault is DepositVault, LstHypeMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return LST_HYPE_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/lstHYPE/LstHypeMidasAccessControlRoles.sol b/contracts/products/lstHYPE/LstHypeMidasAccessControlRoles.sol
deleted file mode 100644
index 628dc227..00000000
--- a/contracts/products/lstHYPE/LstHypeMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title LstHypeMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for lstHYPE contracts
- * @author RedDuck Software
- */
-abstract contract LstHypeMidasAccessControlRoles {
- /**
- * @notice actor that can manage LstHypeDepositVault
- */
- bytes32 public constant LST_HYPE_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("LST_HYPE_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage LstHypeRedemptionVault
- */
- bytes32 public constant LST_HYPE_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("LST_HYPE_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage LstHypeCustomAggregatorFeed and LstHypeDataFeed
- */
- bytes32 public constant LST_HYPE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("LST_HYPE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/lstHYPE/LstHypeRedemptionVaultWithSwapper.sol b/contracts/products/lstHYPE/LstHypeRedemptionVaultWithSwapper.sol
deleted file mode 100644
index d13e4c10..00000000
--- a/contracts/products/lstHYPE/LstHypeRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./LstHypeMidasAccessControlRoles.sol";
-
-/**
- * @title LstHypeRedemptionVaultWithSwapper
- * @notice Smart contract that handles lstHYPE redemptions
- * @author RedDuck Software
- */
-contract LstHypeRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- LstHypeMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return LST_HYPE_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/lstHYPE/lstHYPE.sol b/contracts/products/lstHYPE/lstHYPE.sol
deleted file mode 100644
index b43c7fc0..00000000
--- a/contracts/products/lstHYPE/lstHYPE.sol
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-import "../../mToken.sol";
-
-/**
- * @title lstHYPE
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract lstHYPE is mToken {
- /**
- * @notice actor that can mint lstHYPE
- */
- bytes32 public constant LST_HYPE_MINT_OPERATOR_ROLE =
- keccak256("LST_HYPE_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn lstHYPE
- */
- bytes32 public constant LST_HYPE_BURN_OPERATOR_ROLE =
- keccak256("LST_HYPE_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause lstHYPE
- */
- bytes32 public constant LST_HYPE_PAUSE_OPERATOR_ROLE =
- keccak256("LST_HYPE_PAUSE_OPERATOR_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 ("Hyperbeat LST Vault", "lstHYPE");
- }
-
- /**
- * @dev AC role, owner of which can mint lstHYPE token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return LST_HYPE_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn lstHYPE token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return LST_HYPE_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause lstHYPE token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return LST_HYPE_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mAPOLLO/MApolloCustomAggregatorFeed.sol b/contracts/products/mAPOLLO/MApolloCustomAggregatorFeed.sol
deleted file mode 100644
index d26a3852..00000000
--- a/contracts/products/mAPOLLO/MApolloCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MApolloMidasAccessControlRoles.sol";
-
-/**
- * @title MApolloCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mAPOLLO,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MApolloCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MApolloMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_APOLLO_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mAPOLLO/MApolloDataFeed.sol b/contracts/products/mAPOLLO/MApolloDataFeed.sol
deleted file mode 100644
index f4f8363d..00000000
--- a/contracts/products/mAPOLLO/MApolloDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MApolloMidasAccessControlRoles.sol";
-
-/**
- * @title MApolloDataFeed
- * @notice DataFeed for mAPOLLO product
- * @author RedDuck Software
- */
-contract MApolloDataFeed is DataFeed, MApolloMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_APOLLO_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mAPOLLO/MApolloDepositVault.sol b/contracts/products/mAPOLLO/MApolloDepositVault.sol
deleted file mode 100644
index 48710f7a..00000000
--- a/contracts/products/mAPOLLO/MApolloDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MApolloMidasAccessControlRoles.sol";
-
-/**
- * @title MApolloDepositVault
- * @notice Smart contract that handles mAPOLLO minting
- * @author RedDuck Software
- */
-contract MApolloDepositVault is DepositVault, MApolloMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_APOLLO_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mAPOLLO/MApolloMidasAccessControlRoles.sol b/contracts/products/mAPOLLO/MApolloMidasAccessControlRoles.sol
deleted file mode 100644
index 346a661c..00000000
--- a/contracts/products/mAPOLLO/MApolloMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MApolloMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mAPOLLO contracts
- * @author RedDuck Software
- */
-abstract contract MApolloMidasAccessControlRoles {
- /**
- * @notice actor that can manage MApolloDepositVault
- */
- bytes32 public constant M_APOLLO_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_APOLLO_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MApolloRedemptionVault
- */
- bytes32 public constant M_APOLLO_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_APOLLO_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MApolloCustomAggregatorFeed and MApolloDataFeed
- */
- bytes32 public constant M_APOLLO_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_APOLLO_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/mAPOLLO/MApolloRedemptionVaultWithSwapper.sol b/contracts/products/mAPOLLO/MApolloRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 02f51d57..00000000
--- a/contracts/products/mAPOLLO/MApolloRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MApolloMidasAccessControlRoles.sol";
-
-/**
- * @title MApolloRedemptionVaultWithSwapper
- * @notice Smart contract that handles mAPOLLO redemptions
- * @author RedDuck Software
- */
-contract MApolloRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MApolloMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_APOLLO_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mAPOLLO/mAPOLLO.sol b/contracts/products/mAPOLLO/mAPOLLO.sol
deleted file mode 100644
index 266c3251..00000000
--- a/contracts/products/mAPOLLO/mAPOLLO.sol
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-import "../../mToken.sol";
-
-/**
- * @title mAPOLLO
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mAPOLLO is mToken {
- /**
- * @notice actor that can mint mAPOLLO
- */
- bytes32 public constant M_APOLLO_MINT_OPERATOR_ROLE =
- keccak256("M_APOLLO_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mAPOLLO
- */
- bytes32 public constant M_APOLLO_BURN_OPERATOR_ROLE =
- keccak256("M_APOLLO_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mAPOLLO
- */
- bytes32 public constant M_APOLLO_PAUSE_OPERATOR_ROLE =
- keccak256("M_APOLLO_PAUSE_OPERATOR_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 Apollo Crypto", "mAPOLLO");
- }
-
- /**
- * @dev AC role, owner of which can mint mAPOLLO token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_APOLLO_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mAPOLLO token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_APOLLO_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mAPOLLO token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_APOLLO_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mBASIS/MBasisCustomAggregatorFeed.sol b/contracts/products/mBASIS/MBasisCustomAggregatorFeed.sol
deleted file mode 100644
index c46ad9db..00000000
--- a/contracts/products/mBASIS/MBasisCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,23 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MBasisMidasAccessControlRoles.sol";
-
-/**
- * @title MBasisCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mBASIS,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MBasisCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MBasisMidasAccessControlRoles
-{
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_BASIS_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mBASIS/MBasisDataFeed.sol b/contracts/products/mBASIS/MBasisDataFeed.sol
deleted file mode 100644
index bdeeea50..00000000
--- a/contracts/products/mBASIS/MBasisDataFeed.sol
+++ /dev/null
@@ -1,19 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MBasisMidasAccessControlRoles.sol";
-
-/**
- * @title MBasisDataFeed
- * @notice DataFeed for mBASIS product
- * @author RedDuck Software
- */
-contract MBasisDataFeed is DataFeed, MBasisMidasAccessControlRoles {
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_BASIS_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mBASIS/MBasisDepositVault.sol b/contracts/products/mBASIS/MBasisDepositVault.sol
deleted file mode 100644
index e752a7f8..00000000
--- a/contracts/products/mBASIS/MBasisDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MBasisMidasAccessControlRoles.sol";
-
-/**
- * @title MBasisDepositVault
- * @notice Smart contract that handles mBASIS minting
- * @author RedDuck Software
- */
-contract MBasisDepositVault is DepositVault, MBasisMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_BASIS_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mBASIS/MBasisMidasAccessControlRoles.sol b/contracts/products/mBASIS/MBasisMidasAccessControlRoles.sol
deleted file mode 100644
index c95dfe89..00000000
--- a/contracts/products/mBASIS/MBasisMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MBasisMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mBASIS contracts
- * @author RedDuck Software
- */
-abstract contract MBasisMidasAccessControlRoles {
- /**
- * @notice actor that can manage MBasisDepositVault
- */
- bytes32 public constant M_BASIS_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_BASIS_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MBasisRedemptionVault
- */
- bytes32 public constant M_BASIS_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_BASIS_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MBasisCustomAggregatorFeed and MBasisDataFeed
- */
- bytes32 public constant M_BASIS_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_BASIS_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/mBASIS/MBasisRedemptionVault.sol b/contracts/products/mBASIS/MBasisRedemptionVault.sol
deleted file mode 100644
index 8537f0e9..00000000
--- a/contracts/products/mBASIS/MBasisRedemptionVault.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVault.sol";
-import "./MBasisMidasAccessControlRoles.sol";
-
-/**
- * @title MBasisRedemptionVault
- * @notice Smart contract that handles mBASIS minting
- * @author RedDuck Software
- */
-contract MBasisRedemptionVault is
- RedemptionVault,
- MBasisMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_BASIS_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mBASIS/MBasisRedemptionVaultWithBUIDL.sol b/contracts/products/mBASIS/MBasisRedemptionVaultWithBUIDL.sol
deleted file mode 100644
index f24a266c..00000000
--- a/contracts/products/mBASIS/MBasisRedemptionVaultWithBUIDL.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithBUIDL.sol";
-import "./MBasisMidasAccessControlRoles.sol";
-
-/**
- * @title MBasisRedemptionVaultWithBUIDL
- * @notice Smart contract that handles mBASIS minting
- * @author RedDuck Software
- */
-contract MBasisRedemptionVaultWithBUIDL is
- RedemptionVaultWIthBUIDL,
- MBasisMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_BASIS_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mBASIS/MBasisRedemptionVaultWithSwapper.sol b/contracts/products/mBASIS/MBasisRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 0642d2ce..00000000
--- a/contracts/products/mBASIS/MBasisRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,30 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import {IERC20Upgradeable as IERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
-import {SafeERC20Upgradeable as SafeERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MBasisMidasAccessControlRoles.sol";
-
-/**
- * @title MBasisRedemptionVault
- * @notice Smart contract that handles mBASIS redemptions
- * @author RedDuck Software
- */
-contract MBasisRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MBasisMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_BASIS_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mBASIS/mBASIS.sol b/contracts/products/mBASIS/mBASIS.sol
deleted file mode 100644
index 6211c8e5..00000000
--- a/contracts/products/mBASIS/mBASIS.sol
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-import "../../mToken.sol";
-
-/**
- * @title mBASIS
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mBASIS is mToken {
- /**
- * @notice actor that can mint mBASIS
- */
- bytes32 public constant M_BASIS_MINT_OPERATOR_ROLE =
- keccak256("M_BASIS_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mBASIS
- */
- bytes32 public constant M_BASIS_BURN_OPERATOR_ROLE =
- keccak256("M_BASIS_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mBASIS
- */
- bytes32 public constant M_BASIS_PAUSE_OPERATOR_ROLE =
- keccak256("M_BASIS_PAUSE_OPERATOR_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 Basis Trading Token", "mBASIS");
- }
-
- /**
- * @dev AC role, owner of which can mint mBASIS token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_BASIS_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mBASIS token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_BASIS_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mBASIS token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_BASIS_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mBTC/MBtcCustomAggregatorFeed.sol b/contracts/products/mBTC/MBtcCustomAggregatorFeed.sol
deleted file mode 100644
index a42ca317..00000000
--- a/contracts/products/mBTC/MBtcCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,23 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MBtcMidasAccessControlRoles.sol";
-
-/**
- * @title MBtcCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mBTC,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MBtcCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MBtcMidasAccessControlRoles
-{
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mBTC/MBtcDataFeed.sol b/contracts/products/mBTC/MBtcDataFeed.sol
deleted file mode 100644
index ce7fbdef..00000000
--- a/contracts/products/mBTC/MBtcDataFeed.sol
+++ /dev/null
@@ -1,19 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MBtcMidasAccessControlRoles.sol";
-
-/**
- * @title MBtcDataFeed
- * @notice DataFeed for mBTC product
- * @author RedDuck Software
- */
-contract MBtcDataFeed is DataFeed, MBtcMidasAccessControlRoles {
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mBTC/MBtcDepositVault.sol b/contracts/products/mBTC/MBtcDepositVault.sol
deleted file mode 100644
index 858a5948..00000000
--- a/contracts/products/mBTC/MBtcDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MBtcMidasAccessControlRoles.sol";
-
-/**
- * @title MBtcDepositVault
- * @notice Smart contract that handles mBTC minting
- * @author RedDuck Software
- */
-contract MBtcDepositVault is DepositVault, MBtcMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_BTC_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mBTC/MBtcMidasAccessControlRoles.sol b/contracts/products/mBTC/MBtcMidasAccessControlRoles.sol
deleted file mode 100644
index 4f809aff..00000000
--- a/contracts/products/mBTC/MBtcMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MBtcMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mBTC contracts
- * @author RedDuck Software
- */
-abstract contract MBtcMidasAccessControlRoles {
- /**
- * @notice actor that can manage MBtcDepositVault
- */
- bytes32 public constant M_BTC_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_BTC_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MBtcRedemptionVault
- */
- bytes32 public constant M_BTC_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_BTC_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MBtcCustomAggregatorFeed and MBtcDataFeed
- */
- bytes32 public constant M_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/mBTC/MBtcRedemptionVault.sol b/contracts/products/mBTC/MBtcRedemptionVault.sol
deleted file mode 100644
index 33af194b..00000000
--- a/contracts/products/mBTC/MBtcRedemptionVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVault.sol";
-import "./MBtcMidasAccessControlRoles.sol";
-
-/**
- * @title MBtcRedemptionVault
- * @notice Smart contract that handles mBTC redemption
- * @author RedDuck Software
- */
-contract MBtcRedemptionVault is RedemptionVault, MBtcMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_BTC_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mBTC/mBTC.sol b/contracts/products/mBTC/mBTC.sol
deleted file mode 100644
index 3eed97b9..00000000
--- a/contracts/products/mBTC/mBTC.sol
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-import "../../mToken.sol";
-
-/**
- * @title mBTC
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mBTC is mToken {
- /**
- * @notice actor that can mint mBTC
- */
- bytes32 public constant M_BTC_MINT_OPERATOR_ROLE =
- keccak256("M_BTC_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mBTC
- */
- bytes32 public constant M_BTC_BURN_OPERATOR_ROLE =
- keccak256("M_BTC_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mBTC
- */
- bytes32 public constant M_BTC_PAUSE_OPERATOR_ROLE =
- keccak256("M_BTC_PAUSE_OPERATOR_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 BTC Yield Token", "mBTC");
- }
-
- /**
- * @dev AC role, owner of which can mint mBTC token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_BTC_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mBTC token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_BTC_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mBTC token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_BTC_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mBTC/tac/TACmBTC.sol b/contracts/products/mBTC/tac/TACmBTC.sol
deleted file mode 100644
index 94437d9d..00000000
--- a/contracts/products/mBTC/tac/TACmBTC.sol
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-import "../../../mToken.sol";
-
-/**
- * @title TACmBTC
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract TACmBTC is mToken {
- /**
- * @notice actor that can mint TACmBTC
- */
- bytes32 public constant TAC_M_BTC_MINT_OPERATOR_ROLE =
- keccak256("TAC_M_BTC_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn TACmBTC
- */
- bytes32 public constant TAC_M_BTC_BURN_OPERATOR_ROLE =
- keccak256("TAC_M_BTC_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause TACmBTC
- */
- bytes32 public constant TAC_M_BTC_PAUSE_OPERATOR_ROLE =
- keccak256("TAC_M_BTC_PAUSE_OPERATOR_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 TACmBTC Token", "TACmBTC");
- }
-
- /**
- * @dev AC role, owner of which can mint TACmBTC token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return TAC_M_BTC_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn TACmBTC token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return TAC_M_BTC_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause TACmBTC token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return TAC_M_BTC_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mBTC/tac/TACmBtcDepositVault.sol b/contracts/products/mBTC/tac/TACmBtcDepositVault.sol
deleted file mode 100644
index 835975cb..00000000
--- a/contracts/products/mBTC/tac/TACmBtcDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../../DepositVault.sol";
-import "./TACmBtcMidasAccessControlRoles.sol";
-
-/**
- * @title TACmBtcDepositVault
- * @notice Smart contract that handles TACmBTC minting
- * @author RedDuck Software
- */
-contract TACmBtcDepositVault is DepositVault, TACmBtcMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return TAC_M_BTC_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mBTC/tac/TACmBtcMidasAccessControlRoles.sol b/contracts/products/mBTC/tac/TACmBtcMidasAccessControlRoles.sol
deleted file mode 100644
index 1e7ac3bd..00000000
--- a/contracts/products/mBTC/tac/TACmBtcMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,21 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title TACmBtcMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for TACmBTC contracts
- * @author RedDuck Software
- */
-abstract contract TACmBtcMidasAccessControlRoles {
- /**
- * @notice actor that can manage TACmBtcDepositVault
- */
- bytes32 public constant TAC_M_BTC_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("TAC_M_BTC_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage TACmBtcRedemptionVault
- */
- bytes32 public constant TAC_M_BTC_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("TAC_M_BTC_REDEMPTION_VAULT_ADMIN_ROLE");
-}
diff --git a/contracts/products/mBTC/tac/TACmBtcRedemptionVault.sol b/contracts/products/mBTC/tac/TACmBtcRedemptionVault.sol
deleted file mode 100644
index 047f5dfa..00000000
--- a/contracts/products/mBTC/tac/TACmBtcRedemptionVault.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../../RedemptionVault.sol";
-import "./TACmBtcMidasAccessControlRoles.sol";
-
-/**
- * @title TACmBtcRedemptionVault
- * @notice Smart contract that handles TACmBTC redemption
- * @author RedDuck Software
- */
-contract TACmBtcRedemptionVault is
- RedemptionVault,
- TACmBtcMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return TAC_M_BTC_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mEDGE/MEdgeCustomAggregatorFeed.sol b/contracts/products/mEDGE/MEdgeCustomAggregatorFeed.sol
deleted file mode 100644
index beb6be38..00000000
--- a/contracts/products/mEDGE/MEdgeCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,23 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MEdgeMidasAccessControlRoles.sol";
-
-/**
- * @title MEdgeCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mEDGE,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MEdgeCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MEdgeMidasAccessControlRoles
-{
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_EDGE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mEDGE/MEdgeDataFeed.sol b/contracts/products/mEDGE/MEdgeDataFeed.sol
deleted file mode 100644
index 8e25ad90..00000000
--- a/contracts/products/mEDGE/MEdgeDataFeed.sol
+++ /dev/null
@@ -1,19 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MEdgeMidasAccessControlRoles.sol";
-
-/**
- * @title MEdgeDataFeed
- * @notice DataFeed for mEDGE product
- * @author RedDuck Software
- */
-contract MEdgeDataFeed is DataFeed, MEdgeMidasAccessControlRoles {
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_EDGE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mEDGE/MEdgeDepositVault.sol b/contracts/products/mEDGE/MEdgeDepositVault.sol
deleted file mode 100644
index 99a0dd04..00000000
--- a/contracts/products/mEDGE/MEdgeDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MEdgeMidasAccessControlRoles.sol";
-
-/**
- * @title MEdgeDepositVault
- * @notice Smart contract that handles mEDGE minting
- * @author RedDuck Software
- */
-contract MEdgeDepositVault is DepositVault, MEdgeMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_EDGE_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mEDGE/MEdgeMidasAccessControlRoles.sol b/contracts/products/mEDGE/MEdgeMidasAccessControlRoles.sol
deleted file mode 100644
index f9c51220..00000000
--- a/contracts/products/mEDGE/MEdgeMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MEdgeMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mEDGE contracts
- * @author RedDuck Software
- */
-abstract contract MEdgeMidasAccessControlRoles {
- /**
- * @notice actor that can manage MEdgeDepositVault
- */
- bytes32 public constant M_EDGE_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_EDGE_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MEdgeRedemptionVault
- */
- bytes32 public constant M_EDGE_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_EDGE_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MEdgeCustomAggregatorFeed and MEdgeDataFeed
- */
- bytes32 public constant M_EDGE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_EDGE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/mEDGE/MEdgeRedemptionVaultWithSwapper.sol b/contracts/products/mEDGE/MEdgeRedemptionVaultWithSwapper.sol
deleted file mode 100644
index c3c2c66e..00000000
--- a/contracts/products/mEDGE/MEdgeRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MEdgeMidasAccessControlRoles.sol";
-
-/**
- * @title MEdgeRedemptionVaultWithSwapper
- * @notice Smart contract that handles mEDGE redemptions
- * @author RedDuck Software
- */
-contract MEdgeRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MEdgeMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_EDGE_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mEDGE/mEDGE.sol b/contracts/products/mEDGE/mEDGE.sol
deleted file mode 100644
index cab4a5b1..00000000
--- a/contracts/products/mEDGE/mEDGE.sol
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-import "../../mToken.sol";
-
-/**
- * @title mEDGE
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mEDGE is mToken {
- /**
- * @notice actor that can mint mEDGE
- */
- bytes32 public constant M_EDGE_MINT_OPERATOR_ROLE =
- keccak256("M_EDGE_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mEDGE
- */
- bytes32 public constant M_EDGE_BURN_OPERATOR_ROLE =
- keccak256("M_EDGE_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mEDGE
- */
- bytes32 public constant M_EDGE_PAUSE_OPERATOR_ROLE =
- keccak256("M_EDGE_PAUSE_OPERATOR_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 mEDGE", "mEDGE");
- }
-
- /**
- * @dev AC role, owner of which can mint mEDGE token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_EDGE_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mEDGE token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_EDGE_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mEDGE token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_EDGE_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mEDGE/tac/TACmEDGE.sol b/contracts/products/mEDGE/tac/TACmEDGE.sol
deleted file mode 100644
index 38c9cd3b..00000000
--- a/contracts/products/mEDGE/tac/TACmEDGE.sol
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-import "../../../mToken.sol";
-
-/**
- * @title TACmEDGE
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract TACmEDGE is mToken {
- /**
- * @notice actor that can mint TACmEDGE
- */
- bytes32 public constant TAC_M_EDGE_MINT_OPERATOR_ROLE =
- keccak256("TAC_M_EDGE_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn TACmEDGE
- */
- bytes32 public constant TAC_M_EDGE_BURN_OPERATOR_ROLE =
- keccak256("TAC_M_EDGE_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause TACmEDGE
- */
- bytes32 public constant TAC_M_EDGE_PAUSE_OPERATOR_ROLE =
- keccak256("TAC_M_EDGE_PAUSE_OPERATOR_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 TACmEDGE Token", "TACmEDGE");
- }
-
- /**
- * @dev AC role, owner of which can mint TACmEDGE token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return TAC_M_EDGE_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn TACmEDGE token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return TAC_M_EDGE_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause TACmEDGE token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return TAC_M_EDGE_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mEDGE/tac/TACmEdgeDepositVault.sol b/contracts/products/mEDGE/tac/TACmEdgeDepositVault.sol
deleted file mode 100644
index 2ecbee10..00000000
--- a/contracts/products/mEDGE/tac/TACmEdgeDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../../DepositVault.sol";
-import "./TACmEdgeMidasAccessControlRoles.sol";
-
-/**
- * @title TACmEdgeDepositVault
- * @notice Smart contract that handles TACmEdge minting
- * @author RedDuck Software
- */
-contract TACmEdgeDepositVault is DepositVault, TACmEdgeMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return TAC_M_EDGE_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mEDGE/tac/TACmEdgeMidasAccessControlRoles.sol b/contracts/products/mEDGE/tac/TACmEdgeMidasAccessControlRoles.sol
deleted file mode 100644
index c394a542..00000000
--- a/contracts/products/mEDGE/tac/TACmEdgeMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,21 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title TACmEdgeMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for TACmEdge contracts
- * @author RedDuck Software
- */
-abstract contract TACmEdgeMidasAccessControlRoles {
- /**
- * @notice actor that can manage TACmEdgeDepositVault
- */
- bytes32 public constant TAC_M_EDGE_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("TAC_M_EDGE_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage TACmEdgeRedemptionVault
- */
- bytes32 public constant TAC_M_EDGE_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("TAC_M_EDGE_REDEMPTION_VAULT_ADMIN_ROLE");
-}
diff --git a/contracts/products/mEDGE/tac/TACmEdgeRedemptionVault.sol b/contracts/products/mEDGE/tac/TACmEdgeRedemptionVault.sol
deleted file mode 100644
index 38d838b2..00000000
--- a/contracts/products/mEDGE/tac/TACmEdgeRedemptionVault.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../../RedemptionVault.sol";
-import "./TACmEdgeMidasAccessControlRoles.sol";
-
-/**
- * @title TACmEdgeRedemptionVault
- * @notice Smart contract that handles TACmEDGE redemption
- * @author RedDuck Software
- */
-contract TACmEdgeRedemptionVault is
- RedemptionVault,
- TACmEdgeMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return TAC_M_EDGE_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mEVETH/MEvEthCustomAggregatorFeed.sol b/contracts/products/mEVETH/MEvEthCustomAggregatorFeed.sol
deleted file mode 100644
index 62cc268e..00000000
--- a/contracts/products/mEVETH/MEvEthCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MEvEthMidasAccessControlRoles.sol";
-
-/**
- * @title MEvEthCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mEVETH,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MEvEthCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MEvEthMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_EV_ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mEVETH/MEvEthDataFeed.sol b/contracts/products/mEVETH/MEvEthDataFeed.sol
deleted file mode 100644
index 8e196f2f..00000000
--- a/contracts/products/mEVETH/MEvEthDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MEvEthMidasAccessControlRoles.sol";
-
-/**
- * @title MEvEthDataFeed
- * @notice DataFeed for mEVETH product
- * @author RedDuck Software
- */
-contract MEvEthDataFeed is DataFeed, MEvEthMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_EV_ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mEVETH/MEvEthDepositVault.sol b/contracts/products/mEVETH/MEvEthDepositVault.sol
deleted file mode 100644
index bf0a8ae0..00000000
--- a/contracts/products/mEVETH/MEvEthDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MEvEthMidasAccessControlRoles.sol";
-
-/**
- * @title MEvEthDepositVault
- * @notice Smart contract that handles mEVETH minting
- * @author RedDuck Software
- */
-contract MEvEthDepositVault is DepositVault, MEvEthMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_EV_ETH_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mEVETH/MEvEthMidasAccessControlRoles.sol b/contracts/products/mEVETH/MEvEthMidasAccessControlRoles.sol
deleted file mode 100644
index 91d5a002..00000000
--- a/contracts/products/mEVETH/MEvEthMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MEvEthMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mEVETH contracts
- * @author RedDuck Software
- */
-abstract contract MEvEthMidasAccessControlRoles {
- /**
- * @notice actor that can manage MEvEthDepositVault
- */
- bytes32 public constant M_EV_ETH_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_EV_ETH_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MEvEthRedemptionVault
- */
- bytes32 public constant M_EV_ETH_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_EV_ETH_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MEvEthCustomAggregatorFeed and MEvEthDataFeed
- */
- bytes32 public constant M_EV_ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_EV_ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/mEVETH/MEvEthRedemptionVaultWithSwapper.sol b/contracts/products/mEVETH/MEvEthRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 8cbe367c..00000000
--- a/contracts/products/mEVETH/MEvEthRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MEvEthMidasAccessControlRoles.sol";
-
-/**
- * @title MEvEthRedemptionVaultWithSwapper
- * @notice Smart contract that handles mEVETH redemptions
- * @author RedDuck Software
- */
-contract MEvEthRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MEvEthMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_EV_ETH_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mEVETH/mEVETH.sol b/contracts/products/mEVETH/mEVETH.sol
deleted file mode 100644
index 42843369..00000000
--- a/contracts/products/mEVETH/mEVETH.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title mEVETH
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mEVETH is mToken {
- /**
- * @notice actor that can mint mEVETH
- */
- bytes32 public constant M_EV_ETH_MINT_OPERATOR_ROLE =
- keccak256("M_EV_ETH_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mEVETH
- */
- bytes32 public constant M_EV_ETH_BURN_OPERATOR_ROLE =
- keccak256("M_EV_ETH_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mEVETH
- */
- bytes32 public constant M_EV_ETH_PAUSE_OPERATOR_ROLE =
- keccak256("M_EV_ETH_PAUSE_OPERATOR_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 Everstake ETH", "mEVETH");
- }
-
- /**
- * @dev AC role, owner of which can mint mEVETH token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_EV_ETH_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mEVETH token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_EV_ETH_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mEVETH token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_EV_ETH_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mEVUSD/MEvUsdCustomAggregatorFeed.sol b/contracts/products/mEVUSD/MEvUsdCustomAggregatorFeed.sol
deleted file mode 100644
index d7355aec..00000000
--- a/contracts/products/mEVUSD/MEvUsdCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MEvUsdMidasAccessControlRoles.sol";
-
-/**
- * @title MEvUsdCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mEVUSD,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MEvUsdCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MEvUsdMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_EV_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mEVUSD/MEvUsdDataFeed.sol b/contracts/products/mEVUSD/MEvUsdDataFeed.sol
deleted file mode 100644
index f20b9dd4..00000000
--- a/contracts/products/mEVUSD/MEvUsdDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MEvUsdMidasAccessControlRoles.sol";
-
-/**
- * @title MEvUsdDataFeed
- * @notice DataFeed for mEVUSD product
- * @author RedDuck Software
- */
-contract MEvUsdDataFeed is DataFeed, MEvUsdMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_EV_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mEVUSD/MEvUsdDepositVault.sol b/contracts/products/mEVUSD/MEvUsdDepositVault.sol
deleted file mode 100644
index ab7d84fb..00000000
--- a/contracts/products/mEVUSD/MEvUsdDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MEvUsdMidasAccessControlRoles.sol";
-
-/**
- * @title MEvUsdDepositVault
- * @notice Smart contract that handles mEVUSD minting
- * @author RedDuck Software
- */
-contract MEvUsdDepositVault is DepositVault, MEvUsdMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_EV_USD_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mEVUSD/MEvUsdMidasAccessControlRoles.sol b/contracts/products/mEVUSD/MEvUsdMidasAccessControlRoles.sol
deleted file mode 100644
index 128f44fe..00000000
--- a/contracts/products/mEVUSD/MEvUsdMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MEvUsdMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mEVUSD contracts
- * @author RedDuck Software
- */
-abstract contract MEvUsdMidasAccessControlRoles {
- /**
- * @notice actor that can manage MEvUsdDepositVault
- */
- bytes32 public constant M_EV_USD_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_EV_USD_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MEvUsdRedemptionVault
- */
- bytes32 public constant M_EV_USD_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_EV_USD_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MEvUsdCustomAggregatorFeed and MEvUsdDataFeed
- */
- bytes32 public constant M_EV_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_EV_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/mEVUSD/MEvUsdRedemptionVaultWithSwapper.sol b/contracts/products/mEVUSD/MEvUsdRedemptionVaultWithSwapper.sol
deleted file mode 100644
index fe193c3c..00000000
--- a/contracts/products/mEVUSD/MEvUsdRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MEvUsdMidasAccessControlRoles.sol";
-
-/**
- * @title MEvUsdRedemptionVaultWithSwapper
- * @notice Smart contract that handles mEVUSD redemptions
- * @author RedDuck Software
- */
-contract MEvUsdRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MEvUsdMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_EV_USD_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mEVUSD/mEVUSD.sol b/contracts/products/mEVUSD/mEVUSD.sol
deleted file mode 100644
index e347176d..00000000
--- a/contracts/products/mEVUSD/mEVUSD.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title mEVUSD
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mEVUSD is mToken {
- /**
- * @notice actor that can mint mEVUSD
- */
- bytes32 public constant M_EV_USD_MINT_OPERATOR_ROLE =
- keccak256("M_EV_USD_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mEVUSD
- */
- bytes32 public constant M_EV_USD_BURN_OPERATOR_ROLE =
- keccak256("M_EV_USD_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mEVUSD
- */
- bytes32 public constant M_EV_USD_PAUSE_OPERATOR_ROLE =
- keccak256("M_EV_USD_PAUSE_OPERATOR_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 Everstake USD", "mEVUSD");
- }
-
- /**
- * @dev AC role, owner of which can mint mEVUSD token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_EV_USD_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mEVUSD token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_EV_USD_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mEVUSD token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_EV_USD_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mFARM/MFarmCustomAggregatorFeed.sol b/contracts/products/mFARM/MFarmCustomAggregatorFeed.sol
deleted file mode 100644
index 69827c7a..00000000
--- a/contracts/products/mFARM/MFarmCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MFarmMidasAccessControlRoles.sol";
-
-/**
- * @title MFarmCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mFARM,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MFarmCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MFarmMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_FARM_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mFARM/MFarmDataFeed.sol b/contracts/products/mFARM/MFarmDataFeed.sol
deleted file mode 100644
index 4133b343..00000000
--- a/contracts/products/mFARM/MFarmDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MFarmMidasAccessControlRoles.sol";
-
-/**
- * @title MFarmDataFeed
- * @notice DataFeed for mFARM product
- * @author RedDuck Software
- */
-contract MFarmDataFeed is DataFeed, MFarmMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_FARM_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mFARM/MFarmDepositVault.sol b/contracts/products/mFARM/MFarmDepositVault.sol
deleted file mode 100644
index 41ac65bf..00000000
--- a/contracts/products/mFARM/MFarmDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MFarmMidasAccessControlRoles.sol";
-
-/**
- * @title MFarmDepositVault
- * @notice Smart contract that handles mFARM minting
- * @author RedDuck Software
- */
-contract MFarmDepositVault is DepositVault, MFarmMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_FARM_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mFARM/MFarmMidasAccessControlRoles.sol b/contracts/products/mFARM/MFarmMidasAccessControlRoles.sol
deleted file mode 100644
index 96c758cf..00000000
--- a/contracts/products/mFARM/MFarmMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MFarmMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mFARM contracts
- * @author RedDuck Software
- */
-abstract contract MFarmMidasAccessControlRoles {
- /**
- * @notice actor that can manage MFarmDepositVault
- */
- bytes32 public constant M_FARM_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_FARM_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MFarmRedemptionVault
- */
- bytes32 public constant M_FARM_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_FARM_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MFarmCustomAggregatorFeed and MFarmDataFeed
- */
- bytes32 public constant M_FARM_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_FARM_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/mFARM/MFarmRedemptionVaultWithSwapper.sol b/contracts/products/mFARM/MFarmRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 7e5d4c53..00000000
--- a/contracts/products/mFARM/MFarmRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MFarmMidasAccessControlRoles.sol";
-
-/**
- * @title MFarmRedemptionVaultWithSwapper
- * @notice Smart contract that handles mFARM redemptions
- * @author RedDuck Software
- */
-contract MFarmRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MFarmMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_FARM_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mFARM/mFARM.sol b/contracts/products/mFARM/mFARM.sol
deleted file mode 100644
index a0d9ec09..00000000
--- a/contracts/products/mFARM/mFARM.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title mFARM
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mFARM is mToken {
- /**
- * @notice actor that can mint mFARM
- */
- bytes32 public constant M_FARM_MINT_OPERATOR_ROLE =
- keccak256("M_FARM_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mFARM
- */
- bytes32 public constant M_FARM_BURN_OPERATOR_ROLE =
- keccak256("M_FARM_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mFARM
- */
- bytes32 public constant M_FARM_PAUSE_OPERATOR_ROLE =
- keccak256("M_FARM_PAUSE_OPERATOR_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 Farm Capital", "mFARM");
- }
-
- /**
- * @dev AC role, owner of which can mint mFARM token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_FARM_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mFARM token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_FARM_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mFARM token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_FARM_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mFONE/MFOneCustomAggregatorFeed.sol b/contracts/products/mFONE/MFOneCustomAggregatorFeed.sol
deleted file mode 100644
index bdf7535c..00000000
--- a/contracts/products/mFONE/MFOneCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MFOneMidasAccessControlRoles.sol";
-
-/**
- * @title MFOneCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mF-ONE,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MFOneCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MFOneMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_FONE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mFONE/MFOneDataFeed.sol b/contracts/products/mFONE/MFOneDataFeed.sol
deleted file mode 100644
index e6775650..00000000
--- a/contracts/products/mFONE/MFOneDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MFOneMidasAccessControlRoles.sol";
-
-/**
- * @title MFOneDataFeed
- * @notice DataFeed for mF-ONE product
- * @author RedDuck Software
- */
-contract MFOneDataFeed is DataFeed, MFOneMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_FONE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mFONE/MFOneDepositVault.sol b/contracts/products/mFONE/MFOneDepositVault.sol
deleted file mode 100644
index d97b6eb6..00000000
--- a/contracts/products/mFONE/MFOneDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MFOneMidasAccessControlRoles.sol";
-
-/**
- * @title MFOneDepositVault
- * @notice Smart contract that handles mF-ONE minting
- * @author RedDuck Software
- */
-contract MFOneDepositVault is DepositVault, MFOneMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_FONE_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mFONE/MFOneMidasAccessControlRoles.sol b/contracts/products/mFONE/MFOneMidasAccessControlRoles.sol
deleted file mode 100644
index 8d4d2396..00000000
--- a/contracts/products/mFONE/MFOneMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MFOneMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mF-ONE contracts
- * @author RedDuck Software
- */
-abstract contract MFOneMidasAccessControlRoles {
- /**
- * @notice actor that can manage MFOneDepositVault
- */
- bytes32 public constant M_FONE_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_FONE_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MFOneRedemptionVault
- */
- bytes32 public constant M_FONE_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_FONE_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MFOneCustomAggregatorFeed and MFOneDataFeed
- */
- bytes32 public constant M_FONE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_FONE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/mFONE/MFOneRedemptionVaultWithMToken.sol b/contracts/products/mFONE/MFOneRedemptionVaultWithMToken.sol
deleted file mode 100644
index 943efbbf..00000000
--- a/contracts/products/mFONE/MFOneRedemptionVaultWithMToken.sol
+++ /dev/null
@@ -1,29 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithMToken.sol";
-import "./MFOneMidasAccessControlRoles.sol";
-
-/**
- * @title MFOneRedemptionVaultWithMToken
- * @notice Smart contract that handles mF-ONE redemptions using mToken
- * liquid strategy. Upgrade-compatible replacement for
- * MFOneRedemptionVaultWithSwapper.
- * @author RedDuck Software
- */
-contract MFOneRedemptionVaultWithMToken is
- RedemptionVaultWithMToken,
- MFOneMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_FONE_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mFONE/MFOneRedemptionVaultWithSwapper.sol b/contracts/products/mFONE/MFOneRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 4e9cf402..00000000
--- a/contracts/products/mFONE/MFOneRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MFOneMidasAccessControlRoles.sol";
-
-/**
- * @title MFOneRedemptionVaultWithSwapper
- * @notice Smart contract that handles mF-ONE redemptions
- * @author RedDuck Software
- */
-contract MFOneRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MFOneMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_FONE_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mFONE/mFONE.sol b/contracts/products/mFONE/mFONE.sol
deleted file mode 100644
index 2516a3c3..00000000
--- a/contracts/products/mFONE/mFONE.sol
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-import "../../mToken.sol";
-
-/**
- * @title mF-ONE
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mFONE is mToken {
- /**
- * @notice actor that can mint mF-ONE
- */
- bytes32 public constant M_FONE_MINT_OPERATOR_ROLE =
- keccak256("M_FONE_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mF-ONE
- */
- bytes32 public constant M_FONE_BURN_OPERATOR_ROLE =
- keccak256("M_FONE_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mF-ONE
- */
- bytes32 public constant M_FONE_PAUSE_OPERATOR_ROLE =
- keccak256("M_FONE_PAUSE_OPERATOR_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 ONE", "mF-ONE");
- }
-
- /**
- * @dev AC role, owner of which can mint mF-ONE token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_FONE_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mF-ONE token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_FONE_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mF-ONE token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_FONE_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mGLO/MGloCustomAggregatorFeed.sol b/contracts/products/mGLO/MGloCustomAggregatorFeed.sol
deleted file mode 100644
index 6d4ed57b..00000000
--- a/contracts/products/mGLO/MGloCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MGloMidasAccessControlRoles.sol";
-
-/**
- * @title MGloCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mGLO,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MGloCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MGloMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_GLO_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mGLO/MGloDataFeed.sol b/contracts/products/mGLO/MGloDataFeed.sol
deleted file mode 100644
index af0f5a74..00000000
--- a/contracts/products/mGLO/MGloDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MGloMidasAccessControlRoles.sol";
-
-/**
- * @title MGloDataFeed
- * @notice DataFeed for mGLO product
- * @author RedDuck Software
- */
-contract MGloDataFeed is DataFeed, MGloMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_GLO_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mGLO/MGloDepositVault.sol b/contracts/products/mGLO/MGloDepositVault.sol
deleted file mode 100644
index 52864442..00000000
--- a/contracts/products/mGLO/MGloDepositVault.sol
+++ /dev/null
@@ -1,31 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MGloMidasAccessControlRoles.sol";
-
-/**
- * @title MGloDepositVault
- * @notice Smart contract that handles mGLO minting
- * @author RedDuck Software
- */
-contract MGloDepositVault is DepositVault, MGloMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_GLO_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-
- /**
- * @inheritdoc Greenlistable
- */
- function greenlistedRole() public pure override returns (bytes32) {
- return M_GLOBAL_GREENLISTED_ROLE;
- }
-}
diff --git a/contracts/products/mGLO/MGloMidasAccessControlRoles.sol b/contracts/products/mGLO/MGloMidasAccessControlRoles.sol
deleted file mode 100644
index 7df4d865..00000000
--- a/contracts/products/mGLO/MGloMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,33 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MGloMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mGLO contracts
- * @author RedDuck Software
- */
-abstract contract MGloMidasAccessControlRoles {
- /**
- * @notice actor that can manage MGloDepositVault
- */
- bytes32 public constant M_GLO_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_GLO_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MGloRedemptionVault
- */
- bytes32 public constant M_GLO_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_GLO_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MGloCustomAggregatorFeed and MGloDataFeed
- */
- bytes32 public constant M_GLO_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_GLO_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-
- /**
- * @notice greenlist role for mGLO
- */
- bytes32 public constant M_GLOBAL_GREENLISTED_ROLE =
- keccak256("M_GLOBAL_GREENLISTED_ROLE");
-}
diff --git a/contracts/products/mGLO/MGloRedemptionVaultWithSwapper.sol b/contracts/products/mGLO/MGloRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 01b84ab3..00000000
--- a/contracts/products/mGLO/MGloRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,34 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MGloMidasAccessControlRoles.sol";
-
-/**
- * @title MGloRedemptionVaultWithSwapper
- * @notice Smart contract that handles mGLO redemptions
- * @author RedDuck Software
- */
-contract MGloRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MGloMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_GLO_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-
- /**
- * @inheritdoc Greenlistable
- */
- function greenlistedRole() public pure override returns (bytes32) {
- return M_GLOBAL_GREENLISTED_ROLE;
- }
-}
diff --git a/contracts/products/mGLO/mGLO.sol b/contracts/products/mGLO/mGLO.sol
deleted file mode 100644
index 4ba65cf8..00000000
--- a/contracts/products/mGLO/mGLO.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title mGLO
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mGLO is mToken {
- /**
- * @notice actor that can mint mGLO
- */
- bytes32 public constant M_GLO_MINT_OPERATOR_ROLE =
- keccak256("M_GLO_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mGLO
- */
- bytes32 public constant M_GLO_BURN_OPERATOR_ROLE =
- keccak256("M_GLO_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mGLO
- */
- bytes32 public constant M_GLO_PAUSE_OPERATOR_ROLE =
- keccak256("M_GLO_PAUSE_OPERATOR_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 Open", "mGLO");
- }
-
- /**
- * @dev AC role, owner of which can mint mGLO token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_GLO_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mGLO token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_GLO_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mGLO token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_GLO_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mGLOBAL/MGlobalCustomAggregatorFeedGrowth.sol b/contracts/products/mGLOBAL/MGlobalCustomAggregatorFeedGrowth.sol
deleted file mode 100644
index 7fc09a23..00000000
--- a/contracts/products/mGLOBAL/MGlobalCustomAggregatorFeedGrowth.sol
+++ /dev/null
@@ -1,29 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeedGrowth.sol";
-import "./MGlobalMidasAccessControlRoles.sol";
-
-/**
- * @title MGlobalCustomAggregatorFeedGrowth
- * @notice AggregatorV3 compatible feed for mGLOBAL,
- * where price is submitted manually by feed admins,
- * and growth apr applies to the answer.
- * @author RedDuck Software
- */
-contract MGlobalCustomAggregatorFeedGrowth is
- CustomAggregatorV3CompatibleFeedGrowth,
- MGlobalMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeedGrowth
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_GLOBAL_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mGLOBAL/MGlobalDataFeed.sol b/contracts/products/mGLOBAL/MGlobalDataFeed.sol
deleted file mode 100644
index cafba33a..00000000
--- a/contracts/products/mGLOBAL/MGlobalDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MGlobalMidasAccessControlRoles.sol";
-
-/**
- * @title MGlobalDataFeed
- * @notice DataFeed for mGLOBAL product
- * @author RedDuck Software
- */
-contract MGlobalDataFeed is DataFeed, MGlobalMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_GLOBAL_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mGLOBAL/MGlobalDepositVaultWithAave.sol b/contracts/products/mGLOBAL/MGlobalDepositVaultWithAave.sol
deleted file mode 100644
index f8d42b59..00000000
--- a/contracts/products/mGLOBAL/MGlobalDepositVaultWithAave.sol
+++ /dev/null
@@ -1,34 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVaultWithAave.sol";
-import "./MGlobalMidasAccessControlRoles.sol";
-
-/**
- * @title MGlobalDepositVaultWithAave
- * @notice Smart contract that handles mGLOBAL minting with Aave V3 auto-invest
- * @author RedDuck Software
- */
-contract MGlobalDepositVaultWithAave is
- DepositVaultWithAave,
- MGlobalMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_GLOBAL_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-
- /**
- * @inheritdoc Greenlistable
- */
- function greenlistedRole() public pure override returns (bytes32) {
- return M_GLOBAL_GREENLISTED_ROLE;
- }
-}
diff --git a/contracts/products/mGLOBAL/MGlobalInfiniFiCustomAggregatorFeedGrowth.sol b/contracts/products/mGLOBAL/MGlobalInfiniFiCustomAggregatorFeedGrowth.sol
deleted file mode 100644
index 7880f088..00000000
--- a/contracts/products/mGLOBAL/MGlobalInfiniFiCustomAggregatorFeedGrowth.sol
+++ /dev/null
@@ -1,35 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeedGrowth.sol";
-import "./MGlobalMidasAccessControlRoles.sol";
-
-/**
- * @title MGlobalInfiniFiCustomAggregatorFeedGrowth
- * @notice AggregatorV3 compatible feed for mGLOBAL dedicated to the InfiniFi
- * integration, where price is submitted manually by feed admins,
- * and growth apr applies to the answer.
- * @author RedDuck Software
- */
-contract MGlobalInfiniFiCustomAggregatorFeedGrowth is
- CustomAggregatorV3CompatibleFeedGrowth,
- MGlobalMidasAccessControlRoles
-{
- /**
- * @notice feed admin for this InfiniFi oracle only — not shared with core mGLOBAL role mixins.
- */
- bytes32 public constant INFINIFI_MG_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("INFINIFI_MG_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeedGrowth
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return INFINIFI_MG_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mGLOBAL/MGlobalMidasAccessControlRoles.sol b/contracts/products/mGLOBAL/MGlobalMidasAccessControlRoles.sol
deleted file mode 100644
index 561d0ea6..00000000
--- a/contracts/products/mGLOBAL/MGlobalMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,33 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MGlobalMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mGLOBAL contracts
- * @author RedDuck Software
- */
-abstract contract MGlobalMidasAccessControlRoles {
- /**
- * @notice actor that can manage MGlobalDepositVault
- */
- bytes32 public constant M_GLOBAL_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_GLOBAL_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MGlobalRedemptionVault
- */
- bytes32 public constant M_GLOBAL_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_GLOBAL_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MGlobalCustomAggregatorFeed and MGlobalDataFeed
- */
- bytes32 public constant M_GLOBAL_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_GLOBAL_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-
- /**
- * @notice greenlist role for mGLOBAL
- */
- bytes32 public constant M_GLOBAL_GREENLISTED_ROLE =
- keccak256("M_GLOBAL_GREENLISTED_ROLE");
-}
diff --git a/contracts/products/mGLOBAL/MGlobalRedemptionVaultWithAave.sol b/contracts/products/mGLOBAL/MGlobalRedemptionVaultWithAave.sol
deleted file mode 100644
index 17f19dbb..00000000
--- a/contracts/products/mGLOBAL/MGlobalRedemptionVaultWithAave.sol
+++ /dev/null
@@ -1,34 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithAave.sol";
-import "./MGlobalMidasAccessControlRoles.sol";
-
-/**
- * @title MGlobalRedemptionVaultWithAave
- * @notice Smart contract that handles mGLOBAL redemptions via Aave V3
- * @author RedDuck Software
- */
-contract MGlobalRedemptionVaultWithAave is
- RedemptionVaultWithAave,
- MGlobalMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_GLOBAL_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-
- /**
- * @inheritdoc Greenlistable
- */
- function greenlistedRole() public pure override returns (bytes32) {
- return M_GLOBAL_GREENLISTED_ROLE;
- }
-}
diff --git a/contracts/products/mGLOBAL/MGlobalRedemptionVaultWithSwapper.sol b/contracts/products/mGLOBAL/MGlobalRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 88f68ef1..00000000
--- a/contracts/products/mGLOBAL/MGlobalRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,34 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MGlobalMidasAccessControlRoles.sol";
-
-/**
- * @title MGlobalRedemptionVaultWithSwapper
- * @notice Smart contract that handles mGLOBAL redemptions
- * @author RedDuck Software
- */
-contract MGlobalRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MGlobalMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_GLOBAL_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-
- /**
- * @inheritdoc Greenlistable
- */
- function greenlistedRole() public pure override returns (bytes32) {
- return M_GLOBAL_GREENLISTED_ROLE;
- }
-}
diff --git a/contracts/products/mGLOBAL/mGLOBAL.sol b/contracts/products/mGLOBAL/mGLOBAL.sol
deleted file mode 100644
index 626639b4..00000000
--- a/contracts/products/mGLOBAL/mGLOBAL.sol
+++ /dev/null
@@ -1,75 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mTokenPermissioned.sol";
-import "./MGlobalMidasAccessControlRoles.sol";
-
-/**
- * @title mGLOBAL
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mGLOBAL is mTokenPermissioned, MGlobalMidasAccessControlRoles {
- /**
- * @notice actor that can mint mGLOBAL
- */
- bytes32 public constant M_GLOBAL_MINT_OPERATOR_ROLE =
- keccak256("M_GLOBAL_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mGLOBAL
- */
- bytes32 public constant M_GLOBAL_BURN_OPERATOR_ROLE =
- keccak256("M_GLOBAL_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mGLOBAL
- */
- bytes32 public constant M_GLOBAL_PAUSE_OPERATOR_ROLE =
- keccak256("M_GLOBAL_PAUSE_OPERATOR_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", "mGLOBAL");
- }
-
- /**
- * @dev AC role, owner of which can mint mGLOBAL token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_GLOBAL_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mGLOBAL token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_GLOBAL_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mGLOBAL token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_GLOBAL_PAUSE_OPERATOR_ROLE;
- }
-
- /**
- * @inheritdoc mTokenPermissioned
- */
- function _greenlistedRole() internal pure override returns (bytes32) {
- return M_GLOBAL_GREENLISTED_ROLE;
- }
-}
diff --git a/contracts/products/mHYPER/MHyperCustomAggregatorFeed.sol b/contracts/products/mHYPER/MHyperCustomAggregatorFeed.sol
deleted file mode 100644
index 13ab8841..00000000
--- a/contracts/products/mHYPER/MHyperCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MHyperMidasAccessControlRoles.sol";
-
-/**
- * @title MHyperCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mHYPER,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MHyperCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MHyperMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_HYPER_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mHYPER/MHyperDataFeed.sol b/contracts/products/mHYPER/MHyperDataFeed.sol
deleted file mode 100644
index c48ffa73..00000000
--- a/contracts/products/mHYPER/MHyperDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MHyperMidasAccessControlRoles.sol";
-
-/**
- * @title MHyperDataFeed
- * @notice DataFeed for mHYPER product
- * @author RedDuck Software
- */
-contract MHyperDataFeed is DataFeed, MHyperMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_HYPER_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mHYPER/MHyperDepositVault.sol b/contracts/products/mHYPER/MHyperDepositVault.sol
deleted file mode 100644
index e597d7e1..00000000
--- a/contracts/products/mHYPER/MHyperDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MHyperMidasAccessControlRoles.sol";
-
-/**
- * @title MHyperDepositVault
- * @notice Smart contract that handles mHYPER minting
- * @author RedDuck Software
- */
-contract MHyperDepositVault is DepositVault, MHyperMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_HYPER_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mHYPER/MHyperMidasAccessControlRoles.sol b/contracts/products/mHYPER/MHyperMidasAccessControlRoles.sol
deleted file mode 100644
index c9d13298..00000000
--- a/contracts/products/mHYPER/MHyperMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MHyperMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mHYPER contracts
- * @author RedDuck Software
- */
-abstract contract MHyperMidasAccessControlRoles {
- /**
- * @notice actor that can manage MHyperDepositVault
- */
- bytes32 public constant M_HYPER_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_HYPER_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MHyperRedemptionVault
- */
- bytes32 public constant M_HYPER_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_HYPER_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MHyperCustomAggregatorFeed and MHyperDataFeed
- */
- bytes32 public constant M_HYPER_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_HYPER_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/mHYPER/MHyperRedemptionVaultWithSwapper.sol b/contracts/products/mHYPER/MHyperRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 085ae9bd..00000000
--- a/contracts/products/mHYPER/MHyperRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MHyperMidasAccessControlRoles.sol";
-
-/**
- * @title MHyperRedemptionVaultWithSwapper
- * @notice Smart contract that handles mHYPER redemptions
- * @author RedDuck Software
- */
-contract MHyperRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MHyperMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_HYPER_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mHYPER/mHYPER.sol b/contracts/products/mHYPER/mHYPER.sol
deleted file mode 100644
index 4b15d5a2..00000000
--- a/contracts/products/mHYPER/mHYPER.sol
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-import "../../mToken.sol";
-
-/**
- * @title mHYPER
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mHYPER is mToken {
- /**
- * @notice actor that can mint mHYPER
- */
- bytes32 public constant M_HYPER_MINT_OPERATOR_ROLE =
- keccak256("M_HYPER_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mHYPER
- */
- bytes32 public constant M_HYPER_BURN_OPERATOR_ROLE =
- keccak256("M_HYPER_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mHYPER
- */
- bytes32 public constant M_HYPER_PAUSE_OPERATOR_ROLE =
- keccak256("M_HYPER_PAUSE_OPERATOR_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 Hyperithm", "mHYPER");
- }
-
- /**
- * @dev AC role, owner of which can mint mHYPER token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_HYPER_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mHYPER token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_HYPER_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mHYPER token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_HYPER_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mHyperBTC/MHyperBtcCustomAggregatorFeed.sol b/contracts/products/mHyperBTC/MHyperBtcCustomAggregatorFeed.sol
deleted file mode 100644
index 2428096d..00000000
--- a/contracts/products/mHyperBTC/MHyperBtcCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MHyperBtcMidasAccessControlRoles.sol";
-
-/**
- * @title MHyperBtcCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mHyperBTC,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MHyperBtcCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MHyperBtcMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_HYPER_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mHyperBTC/MHyperBtcDataFeed.sol b/contracts/products/mHyperBTC/MHyperBtcDataFeed.sol
deleted file mode 100644
index 045515a7..00000000
--- a/contracts/products/mHyperBTC/MHyperBtcDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MHyperBtcMidasAccessControlRoles.sol";
-
-/**
- * @title MHyperBtcDataFeed
- * @notice DataFeed for mHyperBTC product
- * @author RedDuck Software
- */
-contract MHyperBtcDataFeed is DataFeed, MHyperBtcMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_HYPER_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mHyperBTC/MHyperBtcDepositVault.sol b/contracts/products/mHyperBTC/MHyperBtcDepositVault.sol
deleted file mode 100644
index d48a1045..00000000
--- a/contracts/products/mHyperBTC/MHyperBtcDepositVault.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MHyperBtcMidasAccessControlRoles.sol";
-
-/**
- * @title MHyperBtcDepositVault
- * @notice Smart contract that handles mHyperBTC minting
- * @author RedDuck Software
- */
-contract MHyperBtcDepositVault is
- DepositVault,
- MHyperBtcMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_HYPER_BTC_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mHyperBTC/MHyperBtcMidasAccessControlRoles.sol b/contracts/products/mHyperBTC/MHyperBtcMidasAccessControlRoles.sol
deleted file mode 100644
index e044aa99..00000000
--- a/contracts/products/mHyperBTC/MHyperBtcMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MHyperBtcMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mHyperBTC contracts
- * @author RedDuck Software
- */
-abstract contract MHyperBtcMidasAccessControlRoles {
- /**
- * @notice actor that can manage MHyperBtcDepositVault
- */
- bytes32 public constant M_HYPER_BTC_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_HYPER_BTC_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MHyperBtcRedemptionVault
- */
- bytes32 public constant M_HYPER_BTC_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_HYPER_BTC_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MHyperBtcCustomAggregatorFeed and MHyperBtcDataFeed
- */
- bytes32 public constant M_HYPER_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_HYPER_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/mHyperBTC/MHyperBtcRedemptionVaultWithSwapper.sol b/contracts/products/mHyperBTC/MHyperBtcRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 9a8165df..00000000
--- a/contracts/products/mHyperBTC/MHyperBtcRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MHyperBtcMidasAccessControlRoles.sol";
-
-/**
- * @title MHyperBtcRedemptionVaultWithSwapper
- * @notice Smart contract that handles mHyperBTC redemptions
- * @author RedDuck Software
- */
-contract MHyperBtcRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MHyperBtcMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_HYPER_BTC_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mHyperBTC/mHyperBTC.sol b/contracts/products/mHyperBTC/mHyperBTC.sol
deleted file mode 100644
index 856f6ced..00000000
--- a/contracts/products/mHyperBTC/mHyperBTC.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title mHyperBTC
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mHyperBTC is mToken {
- /**
- * @notice actor that can mint mHyperBTC
- */
- bytes32 public constant M_HYPER_BTC_MINT_OPERATOR_ROLE =
- keccak256("M_HYPER_BTC_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mHyperBTC
- */
- bytes32 public constant M_HYPER_BTC_BURN_OPERATOR_ROLE =
- keccak256("M_HYPER_BTC_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mHyperBTC
- */
- bytes32 public constant M_HYPER_BTC_PAUSE_OPERATOR_ROLE =
- keccak256("M_HYPER_BTC_PAUSE_OPERATOR_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 Hyperithm BTC", "mHyperBTC");
- }
-
- /**
- * @dev AC role, owner of which can mint mHyperBTC token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_HYPER_BTC_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mHyperBTC token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_HYPER_BTC_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mHyperBTC token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_HYPER_BTC_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mHyperETH/MHyperEthCustomAggregatorFeed.sol b/contracts/products/mHyperETH/MHyperEthCustomAggregatorFeed.sol
deleted file mode 100644
index 6084afc5..00000000
--- a/contracts/products/mHyperETH/MHyperEthCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MHyperEthMidasAccessControlRoles.sol";
-
-/**
- * @title MHyperEthCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mHyperETH,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MHyperEthCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MHyperEthMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_HYPER_ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mHyperETH/MHyperEthDataFeed.sol b/contracts/products/mHyperETH/MHyperEthDataFeed.sol
deleted file mode 100644
index 67175d43..00000000
--- a/contracts/products/mHyperETH/MHyperEthDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MHyperEthMidasAccessControlRoles.sol";
-
-/**
- * @title MHyperEthDataFeed
- * @notice DataFeed for mHyperETH product
- * @author RedDuck Software
- */
-contract MHyperEthDataFeed is DataFeed, MHyperEthMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_HYPER_ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mHyperETH/MHyperEthDepositVault.sol b/contracts/products/mHyperETH/MHyperEthDepositVault.sol
deleted file mode 100644
index cc322735..00000000
--- a/contracts/products/mHyperETH/MHyperEthDepositVault.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MHyperEthMidasAccessControlRoles.sol";
-
-/**
- * @title MHyperEthDepositVault
- * @notice Smart contract that handles mHyperETH minting
- * @author RedDuck Software
- */
-contract MHyperEthDepositVault is
- DepositVault,
- MHyperEthMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_HYPER_ETH_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mHyperETH/MHyperEthMidasAccessControlRoles.sol b/contracts/products/mHyperETH/MHyperEthMidasAccessControlRoles.sol
deleted file mode 100644
index f76c5354..00000000
--- a/contracts/products/mHyperETH/MHyperEthMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MHyperEthMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mHyperETH contracts
- * @author RedDuck Software
- */
-abstract contract MHyperEthMidasAccessControlRoles {
- /**
- * @notice actor that can manage MHyperEthDepositVault
- */
- bytes32 public constant M_HYPER_ETH_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_HYPER_ETH_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MHyperEthRedemptionVault
- */
- bytes32 public constant M_HYPER_ETH_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_HYPER_ETH_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MHyperEthCustomAggregatorFeed and MHyperEthDataFeed
- */
- bytes32 public constant M_HYPER_ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_HYPER_ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/mHyperETH/MHyperEthRedemptionVaultWithSwapper.sol b/contracts/products/mHyperETH/MHyperEthRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 144c24fe..00000000
--- a/contracts/products/mHyperETH/MHyperEthRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MHyperEthMidasAccessControlRoles.sol";
-
-/**
- * @title MHyperEthRedemptionVaultWithSwapper
- * @notice Smart contract that handles mHyperETH redemptions
- * @author RedDuck Software
- */
-contract MHyperEthRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MHyperEthMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_HYPER_ETH_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mHyperETH/mHyperETH.sol b/contracts/products/mHyperETH/mHyperETH.sol
deleted file mode 100644
index 71e053cc..00000000
--- a/contracts/products/mHyperETH/mHyperETH.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title mHyperETH
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mHyperETH is mToken {
- /**
- * @notice actor that can mint mHyperETH
- */
- bytes32 public constant M_HYPER_ETH_MINT_OPERATOR_ROLE =
- keccak256("M_HYPER_ETH_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mHyperETH
- */
- bytes32 public constant M_HYPER_ETH_BURN_OPERATOR_ROLE =
- keccak256("M_HYPER_ETH_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mHyperETH
- */
- bytes32 public constant M_HYPER_ETH_PAUSE_OPERATOR_ROLE =
- keccak256("M_HYPER_ETH_PAUSE_OPERATOR_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 Hyperithm ETH", "mHyperETH");
- }
-
- /**
- * @dev AC role, owner of which can mint mHyperETH token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_HYPER_ETH_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mHyperETH token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_HYPER_ETH_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mHyperETH token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_HYPER_ETH_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mKRalpha/MKRalphaCustomAggregatorFeed.sol b/contracts/products/mKRalpha/MKRalphaCustomAggregatorFeed.sol
deleted file mode 100644
index 12a25371..00000000
--- a/contracts/products/mKRalpha/MKRalphaCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,29 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MKRalphaMidasAccessControlRoles.sol";
-
-/**
- * @title MKRalphaCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mKRalpha,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- * @dev last initialization version is 2
- */
-contract MKRalphaCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MKRalphaMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_KRALPHA_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mKRalpha/MKRalphaDataFeed.sol b/contracts/products/mKRalpha/MKRalphaDataFeed.sol
deleted file mode 100644
index 69814c70..00000000
--- a/contracts/products/mKRalpha/MKRalphaDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MKRalphaMidasAccessControlRoles.sol";
-
-/**
- * @title MKRalphaDataFeed
- * @notice DataFeed for mKRalpha product
- * @author RedDuck Software
- */
-contract MKRalphaDataFeed is DataFeed, MKRalphaMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_KRALPHA_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mKRalpha/MKRalphaDepositVault.sol b/contracts/products/mKRalpha/MKRalphaDepositVault.sol
deleted file mode 100644
index e3e52687..00000000
--- a/contracts/products/mKRalpha/MKRalphaDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MKRalphaMidasAccessControlRoles.sol";
-
-/**
- * @title MKRalphaDepositVault
- * @notice Smart contract that handles mKRalpha minting
- * @author RedDuck Software
- */
-contract MKRalphaDepositVault is DepositVault, MKRalphaMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_KRALPHA_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mKRalpha/MKRalphaMidasAccessControlRoles.sol b/contracts/products/mKRalpha/MKRalphaMidasAccessControlRoles.sol
deleted file mode 100644
index 0d982458..00000000
--- a/contracts/products/mKRalpha/MKRalphaMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MKRalphaMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mKRalpha contracts
- * @author RedDuck Software
- */
-abstract contract MKRalphaMidasAccessControlRoles {
- /**
- * @notice actor that can manage MKRalphaDepositVault
- */
- bytes32 public constant M_KRALPHA_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_KRALPHA_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MKRalphaRedemptionVault
- */
- bytes32 public constant M_KRALPHA_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_KRALPHA_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MKRalphaCustomAggregatorFeed and MKRalphaDataFeed
- */
- bytes32 public constant M_KRALPHA_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_KRALPHA_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/mKRalpha/MKRalphaRedemptionVaultWithSwapper.sol b/contracts/products/mKRalpha/MKRalphaRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 705fdda1..00000000
--- a/contracts/products/mKRalpha/MKRalphaRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MKRalphaMidasAccessControlRoles.sol";
-
-/**
- * @title MKRalphaRedemptionVaultWithSwapper
- * @notice Smart contract that handles mKRalpha redemptions
- * @author RedDuck Software
- */
-contract MKRalphaRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MKRalphaMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_KRALPHA_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mKRalpha/mKRalpha.sol b/contracts/products/mKRalpha/mKRalpha.sol
deleted file mode 100644
index 5b6c98fd..00000000
--- a/contracts/products/mKRalpha/mKRalpha.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title mKRalpha
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mKRalpha is mToken {
- /**
- * @notice actor that can mint mKRalpha
- */
- bytes32 public constant M_KRALPHA_MINT_OPERATOR_ROLE =
- keccak256("M_KRALPHA_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mKRalpha
- */
- bytes32 public constant M_KRALPHA_BURN_OPERATOR_ROLE =
- keccak256("M_KRALPHA_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mKRalpha
- */
- bytes32 public constant M_KRALPHA_PAUSE_OPERATOR_ROLE =
- keccak256("M_KRALPHA_PAUSE_OPERATOR_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 Keyrock Alpha", "mKRalpha");
- }
-
- /**
- * @dev AC role, owner of which can mint mKRalpha token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_KRALPHA_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mKRalpha token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_KRALPHA_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mKRalpha token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_KRALPHA_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mLIQUIDITY/MLiquidityCustomAggregatorFeed.sol b/contracts/products/mLIQUIDITY/MLiquidityCustomAggregatorFeed.sol
deleted file mode 100644
index 8215756f..00000000
--- a/contracts/products/mLIQUIDITY/MLiquidityCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MLiquidityMidasAccessControlRoles.sol";
-
-/**
- * @title MLiquidityCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mLIQUIDITY,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MLiquidityCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MLiquidityMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_LIQUIDITY_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mLIQUIDITY/MLiquidityDataFeed.sol b/contracts/products/mLIQUIDITY/MLiquidityDataFeed.sol
deleted file mode 100644
index 07cdce6d..00000000
--- a/contracts/products/mLIQUIDITY/MLiquidityDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MLiquidityMidasAccessControlRoles.sol";
-
-/**
- * @title MLiquidityDataFeed
- * @notice DataFeed for mLIQUIDITY product
- * @author RedDuck Software
- */
-contract MLiquidityDataFeed is DataFeed, MLiquidityMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_LIQUIDITY_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mLIQUIDITY/MLiquidityDepositVault.sol b/contracts/products/mLIQUIDITY/MLiquidityDepositVault.sol
deleted file mode 100644
index a0735cb4..00000000
--- a/contracts/products/mLIQUIDITY/MLiquidityDepositVault.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MLiquidityMidasAccessControlRoles.sol";
-
-/**
- * @title MLiquidityDepositVault
- * @notice Smart contract that handles mLIQUIDITY minting
- * @author RedDuck Software
- */
-contract MLiquidityDepositVault is
- DepositVault,
- MLiquidityMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_LIQUIDITY_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mLIQUIDITY/MLiquidityDepositVaultWithAave.sol b/contracts/products/mLIQUIDITY/MLiquidityDepositVaultWithAave.sol
deleted file mode 100644
index a415bb66..00000000
--- a/contracts/products/mLIQUIDITY/MLiquidityDepositVaultWithAave.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVaultWithAave.sol";
-import "./MLiquidityMidasAccessControlRoles.sol";
-
-/**
- * @title MLiquidityDepositVaultWithAave
- * @notice Smart contract that handles mLIQUIDITY minting with Aave V3 auto-invest
- * @author RedDuck Software
- */
-contract MLiquidityDepositVaultWithAave is
- DepositVaultWithAave,
- MLiquidityMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_LIQUIDITY_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mLIQUIDITY/MLiquidityDepositVaultWithMorpho.sol b/contracts/products/mLIQUIDITY/MLiquidityDepositVaultWithMorpho.sol
deleted file mode 100644
index 4223467b..00000000
--- a/contracts/products/mLIQUIDITY/MLiquidityDepositVaultWithMorpho.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVaultWithMorpho.sol";
-import "./MLiquidityMidasAccessControlRoles.sol";
-
-/**
- * @title MLiquidityDepositVaultWithMorpho
- * @notice Smart contract that handles mLIQUIDITY minting with Morpho auto-invest
- * @author RedDuck Software
- */
-contract MLiquidityDepositVaultWithMorpho is
- DepositVaultWithMorpho,
- MLiquidityMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_LIQUIDITY_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mLIQUIDITY/MLiquidityMidasAccessControlRoles.sol b/contracts/products/mLIQUIDITY/MLiquidityMidasAccessControlRoles.sol
deleted file mode 100644
index 4b173983..00000000
--- a/contracts/products/mLIQUIDITY/MLiquidityMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MLiquidityMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mLIQUIDITY contracts
- * @author RedDuck Software
- */
-abstract contract MLiquidityMidasAccessControlRoles {
- /**
- * @notice actor that can manage MLiquidityDepositVault
- */
- bytes32 public constant M_LIQUIDITY_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_LIQUIDITY_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MLiquidityRedemptionVault
- */
- bytes32 public constant M_LIQUIDITY_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_LIQUIDITY_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MLiquidityCustomAggregatorFeed and MLiquidityDataFeed
- */
- bytes32 public constant M_LIQUIDITY_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_LIQUIDITY_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/mLIQUIDITY/MLiquidityRedemptionVault.sol b/contracts/products/mLIQUIDITY/MLiquidityRedemptionVault.sol
deleted file mode 100644
index 5544193f..00000000
--- a/contracts/products/mLIQUIDITY/MLiquidityRedemptionVault.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVault.sol";
-import "./MLiquidityMidasAccessControlRoles.sol";
-
-/**
- * @title MLiquidityRedemptionVault
- * @notice Smart contract that handles mLIQUIDITY redemptions
- * @author RedDuck Software
- */
-contract MLiquidityRedemptionVault is
- RedemptionVault,
- MLiquidityMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_LIQUIDITY_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mLIQUIDITY/MLiquidityRedemptionVaultWithAave.sol b/contracts/products/mLIQUIDITY/MLiquidityRedemptionVaultWithAave.sol
deleted file mode 100644
index d866dcee..00000000
--- a/contracts/products/mLIQUIDITY/MLiquidityRedemptionVaultWithAave.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithAave.sol";
-import "./MLiquidityMidasAccessControlRoles.sol";
-
-/**
- * @title MLiquidityRedemptionVaultWithAave
- * @notice Smart contract that handles mLIQUIDITY redemptions via Aave V3
- * @author RedDuck Software
- */
-contract MLiquidityRedemptionVaultWithAave is
- RedemptionVaultWithAave,
- MLiquidityMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_LIQUIDITY_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mLIQUIDITY/MLiquidityRedemptionVaultWithMorpho.sol b/contracts/products/mLIQUIDITY/MLiquidityRedemptionVaultWithMorpho.sol
deleted file mode 100644
index 14ec2e87..00000000
--- a/contracts/products/mLIQUIDITY/MLiquidityRedemptionVaultWithMorpho.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithMorpho.sol";
-import "./MLiquidityMidasAccessControlRoles.sol";
-
-/**
- * @title MLiquidityRedemptionVaultWithMorpho
- * @notice Smart contract that handles mLIQUIDITY redemptions via Morpho Vault
- * @author RedDuck Software
- */
-contract MLiquidityRedemptionVaultWithMorpho is
- RedemptionVaultWithMorpho,
- MLiquidityMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_LIQUIDITY_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mLIQUIDITY/mLIQUIDITY.sol b/contracts/products/mLIQUIDITY/mLIQUIDITY.sol
deleted file mode 100644
index 7c71232f..00000000
--- a/contracts/products/mLIQUIDITY/mLIQUIDITY.sol
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-import "../../mToken.sol";
-
-/**
- * @title mLIQUIDITY
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mLIQUIDITY is mToken {
- /**
- * @notice actor that can mint mLIQUIDITY
- */
- bytes32 public constant M_LIQUIDITY_MINT_OPERATOR_ROLE =
- keccak256("M_LIQUIDITY_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mLIQUIDITY
- */
- bytes32 public constant M_LIQUIDITY_BURN_OPERATOR_ROLE =
- keccak256("M_LIQUIDITY_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mLIQUIDITY
- */
- bytes32 public constant M_LIQUIDITY_PAUSE_OPERATOR_ROLE =
- keccak256("M_LIQUIDITY_PAUSE_OPERATOR_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 mLIQUIDITY", "mLIQUIDITY");
- }
-
- /**
- * @dev AC role, owner of which can mint mLIQUIDITY token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_LIQUIDITY_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mLIQUIDITY token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_LIQUIDITY_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mLIQUIDITY token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_LIQUIDITY_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mM1BTC/MM1BtcCustomAggregatorFeed.sol b/contracts/products/mM1BTC/MM1BtcCustomAggregatorFeed.sol
deleted file mode 100644
index 76608f0c..00000000
--- a/contracts/products/mM1BTC/MM1BtcCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MM1BtcMidasAccessControlRoles.sol";
-
-/**
- * @title MM1BtcCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mM1BTC,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MM1BtcCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MM1BtcMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_M1_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mM1BTC/MM1BtcDataFeed.sol b/contracts/products/mM1BTC/MM1BtcDataFeed.sol
deleted file mode 100644
index b217f315..00000000
--- a/contracts/products/mM1BTC/MM1BtcDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MM1BtcMidasAccessControlRoles.sol";
-
-/**
- * @title MM1BtcDataFeed
- * @notice DataFeed for mM1BTC product
- * @author RedDuck Software
- */
-contract MM1BtcDataFeed is DataFeed, MM1BtcMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_M1_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mM1BTC/MM1BtcDepositVault.sol b/contracts/products/mM1BTC/MM1BtcDepositVault.sol
deleted file mode 100644
index d891855a..00000000
--- a/contracts/products/mM1BTC/MM1BtcDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MM1BtcMidasAccessControlRoles.sol";
-
-/**
- * @title MM1BtcDepositVault
- * @notice Smart contract that handles mM1BTC minting
- * @author RedDuck Software
- */
-contract MM1BtcDepositVault is DepositVault, MM1BtcMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_M1_BTC_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mM1BTC/MM1BtcMidasAccessControlRoles.sol b/contracts/products/mM1BTC/MM1BtcMidasAccessControlRoles.sol
deleted file mode 100644
index 350a47e4..00000000
--- a/contracts/products/mM1BTC/MM1BtcMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MM1BtcMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mM1BTC contracts
- * @author RedDuck Software
- */
-abstract contract MM1BtcMidasAccessControlRoles {
- /**
- * @notice actor that can manage MM1BtcDepositVault
- */
- bytes32 public constant M_M1_BTC_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_M1_BTC_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MM1BtcRedemptionVault
- */
- bytes32 public constant M_M1_BTC_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_M1_BTC_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MM1BtcCustomAggregatorFeed and MM1BtcDataFeed
- */
- bytes32 public constant M_M1_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_M1_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/mM1BTC/MM1BtcRedemptionVaultWithSwapper.sol b/contracts/products/mM1BTC/MM1BtcRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 6f5c08a4..00000000
--- a/contracts/products/mM1BTC/MM1BtcRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MM1BtcMidasAccessControlRoles.sol";
-
-/**
- * @title MM1BtcRedemptionVaultWithSwapper
- * @notice Smart contract that handles mM1BTC redemptions
- * @author RedDuck Software
- */
-contract MM1BtcRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MM1BtcMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_M1_BTC_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mM1BTC/mM1BTC.sol b/contracts/products/mM1BTC/mM1BTC.sol
deleted file mode 100644
index cb2c7b94..00000000
--- a/contracts/products/mM1BTC/mM1BTC.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title mM1BTC
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mM1BTC is mToken {
- /**
- * @notice actor that can mint mM1BTC
- */
- bytes32 public constant M_M1_BTC_MINT_OPERATOR_ROLE =
- keccak256("M_M1_BTC_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mM1BTC
- */
- bytes32 public constant M_M1_BTC_BURN_OPERATOR_ROLE =
- keccak256("M_M1_BTC_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mM1BTC
- */
- bytes32 public constant M_M1_BTC_PAUSE_OPERATOR_ROLE =
- keccak256("M_M1_BTC_PAUSE_OPERATOR_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 M1 BTC Market Neutral", "mM1-BTC");
- }
-
- /**
- * @dev AC role, owner of which can mint mM1BTC token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_M1_BTC_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mM1BTC token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_M1_BTC_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mM1BTC token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_M1_BTC_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mM1USD/MM1UsdCustomAggregatorFeed.sol b/contracts/products/mM1USD/MM1UsdCustomAggregatorFeed.sol
deleted file mode 100644
index c733bce1..00000000
--- a/contracts/products/mM1USD/MM1UsdCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MM1UsdMidasAccessControlRoles.sol";
-
-/**
- * @title MM1UsdCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mM1USD,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MM1UsdCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MM1UsdMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_M1_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mM1USD/MM1UsdDataFeed.sol b/contracts/products/mM1USD/MM1UsdDataFeed.sol
deleted file mode 100644
index 2a37b6df..00000000
--- a/contracts/products/mM1USD/MM1UsdDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MM1UsdMidasAccessControlRoles.sol";
-
-/**
- * @title MM1UsdDataFeed
- * @notice DataFeed for mM1USD product
- * @author RedDuck Software
- */
-contract MM1UsdDataFeed is DataFeed, MM1UsdMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_M1_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mM1USD/MM1UsdDepositVault.sol b/contracts/products/mM1USD/MM1UsdDepositVault.sol
deleted file mode 100644
index 55bfb3bd..00000000
--- a/contracts/products/mM1USD/MM1UsdDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MM1UsdMidasAccessControlRoles.sol";
-
-/**
- * @title MM1UsdDepositVault
- * @notice Smart contract that handles mM1USD minting
- * @author RedDuck Software
- */
-contract MM1UsdDepositVault is DepositVault, MM1UsdMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_M1_USD_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mM1USD/MM1UsdMidasAccessControlRoles.sol b/contracts/products/mM1USD/MM1UsdMidasAccessControlRoles.sol
deleted file mode 100644
index 7d894d04..00000000
--- a/contracts/products/mM1USD/MM1UsdMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MM1UsdMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mM1USD contracts
- * @author RedDuck Software
- */
-abstract contract MM1UsdMidasAccessControlRoles {
- /**
- * @notice actor that can manage MM1UsdDepositVault
- */
- bytes32 public constant M_M1_USD_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_M1_USD_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MM1UsdRedemptionVault
- */
- bytes32 public constant M_M1_USD_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_M1_USD_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MM1UsdCustomAggregatorFeed and MM1UsdDataFeed
- */
- bytes32 public constant M_M1_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_M1_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/mM1USD/MM1UsdRedemptionVaultWithSwapper.sol b/contracts/products/mM1USD/MM1UsdRedemptionVaultWithSwapper.sol
deleted file mode 100644
index c7c49a1e..00000000
--- a/contracts/products/mM1USD/MM1UsdRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MM1UsdMidasAccessControlRoles.sol";
-
-/**
- * @title MM1UsdRedemptionVaultWithSwapper
- * @notice Smart contract that handles mM1USD redemptions
- * @author RedDuck Software
- */
-contract MM1UsdRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MM1UsdMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_M1_USD_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mM1USD/mM1USD.sol b/contracts/products/mM1USD/mM1USD.sol
deleted file mode 100644
index 90aa2b1a..00000000
--- a/contracts/products/mM1USD/mM1USD.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title mM1USD
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mM1USD is mToken {
- /**
- * @notice actor that can mint mM1USD
- */
- bytes32 public constant M_M1_USD_MINT_OPERATOR_ROLE =
- keccak256("M_M1_USD_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mM1USD
- */
- bytes32 public constant M_M1_USD_BURN_OPERATOR_ROLE =
- keccak256("M_M1_USD_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mM1USD
- */
- bytes32 public constant M_M1_USD_PAUSE_OPERATOR_ROLE =
- keccak256("M_M1_USD_PAUSE_OPERATOR_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 M1 USD Market Neutral", "mM1-USD");
- }
-
- /**
- * @dev AC role, owner of which can mint mM1USD token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_M1_USD_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mM1USD token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_M1_USD_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mM1USD token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_M1_USD_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mMEV/MMevCustomAggregatorFeed.sol b/contracts/products/mMEV/MMevCustomAggregatorFeed.sol
deleted file mode 100644
index 1f7d0817..00000000
--- a/contracts/products/mMEV/MMevCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,23 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MMevMidasAccessControlRoles.sol";
-
-/**
- * @title MMevCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mMEV,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MMevCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MMevMidasAccessControlRoles
-{
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_MEV_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mMEV/MMevDataFeed.sol b/contracts/products/mMEV/MMevDataFeed.sol
deleted file mode 100644
index 6caed85f..00000000
--- a/contracts/products/mMEV/MMevDataFeed.sol
+++ /dev/null
@@ -1,19 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MMevMidasAccessControlRoles.sol";
-
-/**
- * @title MMevDataFeed
- * @notice DataFeed for mMEV product
- * @author RedDuck Software
- */
-contract MMevDataFeed is DataFeed, MMevMidasAccessControlRoles {
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_MEV_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mMEV/MMevDepositVault.sol b/contracts/products/mMEV/MMevDepositVault.sol
deleted file mode 100644
index b80949ed..00000000
--- a/contracts/products/mMEV/MMevDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MMevMidasAccessControlRoles.sol";
-
-/**
- * @title MMevDepositVault
- * @notice Smart contract that handles mMEV minting
- * @author RedDuck Software
- */
-contract MMevDepositVault is DepositVault, MMevMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_MEV_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mMEV/MMevMidasAccessControlRoles.sol b/contracts/products/mMEV/MMevMidasAccessControlRoles.sol
deleted file mode 100644
index 60c18ce6..00000000
--- a/contracts/products/mMEV/MMevMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MMevMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mMEV contracts
- * @author RedDuck Software
- */
-abstract contract MMevMidasAccessControlRoles {
- /**
- * @notice actor that can manage MMevDepositVault
- */
- bytes32 public constant M_MEV_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_MEV_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MMevRedemptionVault
- */
- bytes32 public constant M_MEV_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_MEV_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MMevCustomAggregatorFeed and MMevDataFeed
- */
- bytes32 public constant M_MEV_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_MEV_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/mMEV/MMevRedemptionVaultWithSwapper.sol b/contracts/products/mMEV/MMevRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 04296506..00000000
--- a/contracts/products/mMEV/MMevRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MMevMidasAccessControlRoles.sol";
-
-/**
- * @title MMevRedemptionVaultWithSwapper
- * @notice Smart contract that handles mMEV redemptions
- * @author RedDuck Software
- */
-contract MMevRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MMevMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_MEV_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mMEV/mMEV.sol b/contracts/products/mMEV/mMEV.sol
deleted file mode 100644
index 7a444a4c..00000000
--- a/contracts/products/mMEV/mMEV.sol
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-import "../../mToken.sol";
-
-/**
- * @title mMEV
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mMEV is mToken {
- /**
- * @notice actor that can mint mMEV
- */
- bytes32 public constant M_MEV_MINT_OPERATOR_ROLE =
- keccak256("M_MEV_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mMEV
- */
- bytes32 public constant M_MEV_BURN_OPERATOR_ROLE =
- keccak256("M_MEV_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mMEV
- */
- bytes32 public constant M_MEV_PAUSE_OPERATOR_ROLE =
- keccak256("M_MEV_PAUSE_OPERATOR_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 MEV", "mMEV");
- }
-
- /**
- * @dev AC role, owner of which can mint mMEV token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_MEV_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mMEV token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_MEV_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mMEV token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_MEV_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mMEV/tac/TACmMEV.sol b/contracts/products/mMEV/tac/TACmMEV.sol
deleted file mode 100644
index 00ae0718..00000000
--- a/contracts/products/mMEV/tac/TACmMEV.sol
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-import "../../../mToken.sol";
-
-/**
- * @title TACmMEV
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract TACmMEV is mToken {
- /**
- * @notice actor that can mint TACmMEV
- */
- bytes32 public constant TAC_M_MEV_MINT_OPERATOR_ROLE =
- keccak256("TAC_M_MEV_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn TACmMEV
- */
- bytes32 public constant TAC_M_MEV_BURN_OPERATOR_ROLE =
- keccak256("TAC_M_MEV_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause TACmMEV
- */
- bytes32 public constant TAC_M_MEV_PAUSE_OPERATOR_ROLE =
- keccak256("TAC_M_MEV_PAUSE_OPERATOR_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 TACmMEV Token", "TACmMEV");
- }
-
- /**
- * @dev AC role, owner of which can mint TACmMEV token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return TAC_M_MEV_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn TACmMEV token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return TAC_M_MEV_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause TACmMEV token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return TAC_M_MEV_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mMEV/tac/TACmMevDepositVault.sol b/contracts/products/mMEV/tac/TACmMevDepositVault.sol
deleted file mode 100644
index 026e7577..00000000
--- a/contracts/products/mMEV/tac/TACmMevDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../../DepositVault.sol";
-import "./TACmMevMidasAccessControlRoles.sol";
-
-/**
- * @title TACmMevDepositVault
- * @notice Smart contract that handles TACmMEV minting
- * @author RedDuck Software
- */
-contract TACmMevDepositVault is DepositVault, TACmMevMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return TAC_M_MEV_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mMEV/tac/TACmMevMidasAccessControlRoles.sol b/contracts/products/mMEV/tac/TACmMevMidasAccessControlRoles.sol
deleted file mode 100644
index 57dbc112..00000000
--- a/contracts/products/mMEV/tac/TACmMevMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,21 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title TACmMevMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for TACmMEV contracts
- * @author RedDuck Software
- */
-abstract contract TACmMevMidasAccessControlRoles {
- /**
- * @notice actor that can manage TACmMevDepositVault
- */
- bytes32 public constant TAC_M_MEV_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("TAC_M_MEV_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage TACmMevRedemptionVault
- */
- bytes32 public constant TAC_M_MEV_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("TAC_M_MEV_REDEMPTION_VAULT_ADMIN_ROLE");
-}
diff --git a/contracts/products/mMEV/tac/TACmMevRedemptionVault.sol b/contracts/products/mMEV/tac/TACmMevRedemptionVault.sol
deleted file mode 100644
index 57229003..00000000
--- a/contracts/products/mMEV/tac/TACmMevRedemptionVault.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../../RedemptionVault.sol";
-import "./TACmMevMidasAccessControlRoles.sol";
-
-/**
- * @title TACmMevRedemptionVault
- * @notice Smart contract that handles TACmMEV redemption
- * @author RedDuck Software
- */
-contract TACmMevRedemptionVault is
- RedemptionVault,
- TACmMevMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return TAC_M_MEV_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mPortofino/MPortofinoCustomAggregatorFeed.sol b/contracts/products/mPortofino/MPortofinoCustomAggregatorFeed.sol
deleted file mode 100644
index 235bfe41..00000000
--- a/contracts/products/mPortofino/MPortofinoCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MPortofinoMidasAccessControlRoles.sol";
-
-/**
- * @title MPortofinoCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mPortofino,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MPortofinoCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MPortofinoMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_PORTOFINO_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mPortofino/MPortofinoDataFeed.sol b/contracts/products/mPortofino/MPortofinoDataFeed.sol
deleted file mode 100644
index 3c263dee..00000000
--- a/contracts/products/mPortofino/MPortofinoDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MPortofinoMidasAccessControlRoles.sol";
-
-/**
- * @title MPortofinoDataFeed
- * @notice DataFeed for mPortofino product
- * @author RedDuck Software
- */
-contract MPortofinoDataFeed is DataFeed, MPortofinoMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_PORTOFINO_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mPortofino/MPortofinoDepositVault.sol b/contracts/products/mPortofino/MPortofinoDepositVault.sol
deleted file mode 100644
index eca4b865..00000000
--- a/contracts/products/mPortofino/MPortofinoDepositVault.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MPortofinoMidasAccessControlRoles.sol";
-
-/**
- * @title MPortofinoDepositVault
- * @notice Smart contract that handles mPortofino minting
- * @author RedDuck Software
- */
-contract MPortofinoDepositVault is
- DepositVault,
- MPortofinoMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_PORTOFINO_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mPortofino/MPortofinoMidasAccessControlRoles.sol b/contracts/products/mPortofino/MPortofinoMidasAccessControlRoles.sol
deleted file mode 100644
index b5094efa..00000000
--- a/contracts/products/mPortofino/MPortofinoMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MPortofinoMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mPortofino contracts
- * @author RedDuck Software
- */
-abstract contract MPortofinoMidasAccessControlRoles {
- /**
- * @notice actor that can manage MPortofinoDepositVault
- */
- bytes32 public constant M_PORTOFINO_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_PORTOFINO_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MPortofinoRedemptionVault
- */
- bytes32 public constant M_PORTOFINO_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_PORTOFINO_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MPortofinoCustomAggregatorFeed and MPortofinoDataFeed
- */
- bytes32 public constant M_PORTOFINO_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_PORTOFINO_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/mPortofino/MPortofinoRedemptionVaultWithSwapper.sol b/contracts/products/mPortofino/MPortofinoRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 9f78b1e9..00000000
--- a/contracts/products/mPortofino/MPortofinoRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MPortofinoMidasAccessControlRoles.sol";
-
-/**
- * @title MPortofinoRedemptionVaultWithSwapper
- * @notice Smart contract that handles mPortofino redemptions
- * @author RedDuck Software
- */
-contract MPortofinoRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MPortofinoMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_PORTOFINO_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mPortofino/mPortofino.sol b/contracts/products/mPortofino/mPortofino.sol
deleted file mode 100644
index 15e8d831..00000000
--- a/contracts/products/mPortofino/mPortofino.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title mPortofino
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mPortofino is mToken {
- /**
- * @notice actor that can mint mPortofino
- */
- bytes32 public constant M_PORTOFINO_MINT_OPERATOR_ROLE =
- keccak256("M_PORTOFINO_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mPortofino
- */
- bytes32 public constant M_PORTOFINO_BURN_OPERATOR_ROLE =
- keccak256("M_PORTOFINO_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mPortofino
- */
- bytes32 public constant M_PORTOFINO_PAUSE_OPERATOR_ROLE =
- keccak256("M_PORTOFINO_PAUSE_OPERATOR_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 Portofino", "mPortofino");
- }
-
- /**
- * @dev AC role, owner of which can mint mPortofino token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_PORTOFINO_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mPortofino token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_PORTOFINO_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mPortofino token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_PORTOFINO_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mRE7/MRe7CustomAggregatorFeed.sol b/contracts/products/mRE7/MRe7CustomAggregatorFeed.sol
deleted file mode 100644
index 3d6eed75..00000000
--- a/contracts/products/mRE7/MRe7CustomAggregatorFeed.sol
+++ /dev/null
@@ -1,29 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MRe7MidasAccessControlRoles.sol";
-
-/**
- * @title MRe7CustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mRE7,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- * @dev last initialization version is 3
- */
-contract MRe7CustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MRe7MidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_RE7_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mRE7/MRe7DataFeed.sol b/contracts/products/mRE7/MRe7DataFeed.sol
deleted file mode 100644
index 22c303ac..00000000
--- a/contracts/products/mRE7/MRe7DataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MRe7MidasAccessControlRoles.sol";
-
-/**
- * @title MRe7DataFeed
- * @notice DataFeed for mRE7 product
- * @author RedDuck Software
- */
-contract MRe7DataFeed is DataFeed, MRe7MidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_RE7_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mRE7/MRe7DepositVault.sol b/contracts/products/mRE7/MRe7DepositVault.sol
deleted file mode 100644
index d7486e22..00000000
--- a/contracts/products/mRE7/MRe7DepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MRe7MidasAccessControlRoles.sol";
-
-/**
- * @title MRe7DepositVault
- * @notice Smart contract that handles mRE7 minting
- * @author RedDuck Software
- */
-contract MRe7DepositVault is DepositVault, MRe7MidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_RE7_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mRE7/MRe7MidasAccessControlRoles.sol b/contracts/products/mRE7/MRe7MidasAccessControlRoles.sol
deleted file mode 100644
index 9b643b5c..00000000
--- a/contracts/products/mRE7/MRe7MidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MRe7MidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mRE7 contracts
- * @author RedDuck Software
- */
-abstract contract MRe7MidasAccessControlRoles {
- /**
- * @notice actor that can manage MRe7DepositVault
- */
- bytes32 public constant M_RE7_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_RE7_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MRe7RedemptionVault
- */
- bytes32 public constant M_RE7_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_RE7_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MRe7CustomAggregatorFeed and MRe7DataFeed
- */
- bytes32 public constant M_RE7_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_RE7_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/mRE7/MRe7RedemptionVaultWithSwapper.sol b/contracts/products/mRE7/MRe7RedemptionVaultWithSwapper.sol
deleted file mode 100644
index 803ff46f..00000000
--- a/contracts/products/mRE7/MRe7RedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MRe7MidasAccessControlRoles.sol";
-
-/**
- * @title MRe7RedemptionVaultWithSwapper
- * @notice Smart contract that handles mRE7 redemptions
- * @author RedDuck Software
- */
-contract MRe7RedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MRe7MidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_RE7_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mRE7/mRE7.sol b/contracts/products/mRE7/mRE7.sol
deleted file mode 100644
index 890ccb87..00000000
--- a/contracts/products/mRE7/mRE7.sol
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-import "../../mToken.sol";
-
-/**
- * @title mRE7
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mRE7 is mToken {
- /**
- * @notice actor that can mint mRE7
- */
- bytes32 public constant M_RE7_MINT_OPERATOR_ROLE =
- keccak256("M_RE7_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mRE7
- */
- bytes32 public constant M_RE7_BURN_OPERATOR_ROLE =
- keccak256("M_RE7_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mRE7
- */
- bytes32 public constant M_RE7_PAUSE_OPERATOR_ROLE =
- keccak256("M_RE7_PAUSE_OPERATOR_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 Re7 Yield", "mRe7YIELD");
- }
-
- /**
- * @dev AC role, owner of which can mint mRE7 token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_RE7_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mRE7 token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_RE7_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mRE7 token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_RE7_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mRE7BTC/MRe7BtcCustomAggregatorFeed.sol b/contracts/products/mRE7BTC/MRe7BtcCustomAggregatorFeed.sol
deleted file mode 100644
index b75bb03f..00000000
--- a/contracts/products/mRE7BTC/MRe7BtcCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MRe7BtcMidasAccessControlRoles.sol";
-
-/**
- * @title MRe7BtcCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mRE7BTC,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MRe7BtcCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MRe7BtcMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_RE7BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mRE7BTC/MRe7BtcDataFeed.sol b/contracts/products/mRE7BTC/MRe7BtcDataFeed.sol
deleted file mode 100644
index b703abd0..00000000
--- a/contracts/products/mRE7BTC/MRe7BtcDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MRe7BtcMidasAccessControlRoles.sol";
-
-/**
- * @title MRe7BtcDataFeed
- * @notice DataFeed for mRE7BTC product
- * @author RedDuck Software
- */
-contract MRe7BtcDataFeed is DataFeed, MRe7BtcMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_RE7BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mRE7BTC/MRe7BtcDepositVault.sol b/contracts/products/mRE7BTC/MRe7BtcDepositVault.sol
deleted file mode 100644
index 6913c126..00000000
--- a/contracts/products/mRE7BTC/MRe7BtcDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MRe7BtcMidasAccessControlRoles.sol";
-
-/**
- * @title MRe7BtcDepositVault
- * @notice Smart contract that handles mRE7BTC minting
- * @author RedDuck Software
- */
-contract MRe7BtcDepositVault is DepositVault, MRe7BtcMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_RE7BTC_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mRE7BTC/MRe7BtcMidasAccessControlRoles.sol b/contracts/products/mRE7BTC/MRe7BtcMidasAccessControlRoles.sol
deleted file mode 100644
index a0b269f2..00000000
--- a/contracts/products/mRE7BTC/MRe7BtcMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MRe7BtcMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mRE7BTC contracts
- * @author RedDuck Software
- */
-abstract contract MRe7BtcMidasAccessControlRoles {
- /**
- * @notice actor that can manage MRe7BtcDepositVault
- */
- bytes32 public constant M_RE7BTC_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_RE7BTC_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MRe7BtcRedemptionVault
- */
- bytes32 public constant M_RE7BTC_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_RE7BTC_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MRe7BtcCustomAggregatorFeed and MRe7BtcDataFeed
- */
- bytes32 public constant M_RE7BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_RE7BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/mRE7BTC/MRe7BtcRedemptionVaultWithSwapper.sol b/contracts/products/mRE7BTC/MRe7BtcRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 32b0b9dd..00000000
--- a/contracts/products/mRE7BTC/MRe7BtcRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MRe7BtcMidasAccessControlRoles.sol";
-
-/**
- * @title MRe7BtcRedemptionVaultWithSwapper
- * @notice Smart contract that handles mRE7BTC redemptions
- * @author RedDuck Software
- */
-contract MRe7BtcRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MRe7BtcMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_RE7BTC_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mRE7BTC/mRE7BTC.sol b/contracts/products/mRE7BTC/mRE7BTC.sol
deleted file mode 100644
index 79267022..00000000
--- a/contracts/products/mRE7BTC/mRE7BTC.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title mRE7BTC
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mRE7BTC is mToken {
- /**
- * @notice actor that can mint mRE7BTC
- */
- bytes32 public constant M_RE7BTC_MINT_OPERATOR_ROLE =
- keccak256("M_RE7BTC_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mRE7BTC
- */
- bytes32 public constant M_RE7BTC_BURN_OPERATOR_ROLE =
- keccak256("M_RE7BTC_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mRE7BTC
- */
- bytes32 public constant M_RE7BTC_PAUSE_OPERATOR_ROLE =
- keccak256("M_RE7BTC_PAUSE_OPERATOR_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 Re7 BTC", "mRe7BTC");
- }
-
- /**
- * @dev AC role, owner of which can mint mRE7BTC token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_RE7BTC_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mRE7BTC token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_RE7BTC_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mRE7BTC token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_RE7BTC_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mRE7SOL/MRe7SolCustomAggregatorFeed.sol b/contracts/products/mRE7SOL/MRe7SolCustomAggregatorFeed.sol
deleted file mode 100644
index 3558ea42..00000000
--- a/contracts/products/mRE7SOL/MRe7SolCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,23 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MRe7SolMidasAccessControlRoles.sol";
-
-/**
- * @title MRe7SolCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mRE7SOL,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MRe7SolCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MRe7SolMidasAccessControlRoles
-{
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_RE7SOL_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mRE7SOL/MRe7SolDataFeed.sol b/contracts/products/mRE7SOL/MRe7SolDataFeed.sol
deleted file mode 100644
index 4efce428..00000000
--- a/contracts/products/mRE7SOL/MRe7SolDataFeed.sol
+++ /dev/null
@@ -1,19 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MRe7SolMidasAccessControlRoles.sol";
-
-/**
- * @title MRe7SolDataFeed
- * @notice DataFeed for mRE7SOL product
- * @author RedDuck Software
- */
-contract MRe7SolDataFeed is DataFeed, MRe7SolMidasAccessControlRoles {
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_RE7SOL_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mRE7SOL/MRe7SolDepositVault.sol b/contracts/products/mRE7SOL/MRe7SolDepositVault.sol
deleted file mode 100644
index 495a9c9c..00000000
--- a/contracts/products/mRE7SOL/MRe7SolDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MRe7SolMidasAccessControlRoles.sol";
-
-/**
- * @title MRe7SolDepositVault
- * @notice Smart contract that handles mRE7SOL minting
- * @author RedDuck Software
- */
-contract MRe7SolDepositVault is DepositVault, MRe7SolMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_RE7SOL_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mRE7SOL/MRe7SolMidasAccessControlRoles.sol b/contracts/products/mRE7SOL/MRe7SolMidasAccessControlRoles.sol
deleted file mode 100644
index c173ffa3..00000000
--- a/contracts/products/mRE7SOL/MRe7SolMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MRe7SolMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mRE7SOL contracts
- * @author RedDuck Software
- */
-abstract contract MRe7SolMidasAccessControlRoles {
- /**
- * @notice actor that can manage MRe7SolDepositVault
- */
- bytes32 public constant M_RE7SOL_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_RE7SOL_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MRe7SolRedemptionVault
- */
- bytes32 public constant M_RE7SOL_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_RE7SOL_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MRe7SolCustomAggregatorFeed and MRe7SolDataFeed
- */
- bytes32 public constant M_RE7SOL_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_RE7SOL_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/mRE7SOL/MRe7SolRedemptionVault.sol b/contracts/products/mRE7SOL/MRe7SolRedemptionVault.sol
deleted file mode 100644
index 1706e790..00000000
--- a/contracts/products/mRE7SOL/MRe7SolRedemptionVault.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVault.sol";
-import "./MRe7SolMidasAccessControlRoles.sol";
-
-/**
- * @title MRe7SolRedemptionVault
- * @notice Smart contract that handles mRE7SOL redemptions
- * @author RedDuck Software
- */
-contract MRe7SolRedemptionVault is
- RedemptionVault,
- MRe7SolMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_RE7SOL_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mRE7SOL/mRE7SOL.sol b/contracts/products/mRE7SOL/mRE7SOL.sol
deleted file mode 100644
index 04898e9e..00000000
--- a/contracts/products/mRE7SOL/mRE7SOL.sol
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-import "../../mToken.sol";
-
-/**
- * @title mRE7SOL
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mRE7SOL is mToken {
- /**
- * @notice actor that can mint mRE7SOL
- */
- bytes32 public constant M_RE7SOL_MINT_OPERATOR_ROLE =
- keccak256("M_RE7SOL_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mRE7SOL
- */
- bytes32 public constant M_RE7SOL_BURN_OPERATOR_ROLE =
- keccak256("M_RE7SOL_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mRE7SOL
- */
- bytes32 public constant M_RE7SOL_PAUSE_OPERATOR_ROLE =
- keccak256("M_RE7SOL_PAUSE_OPERATOR_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 Re7SOL", "mRe7SOL");
- }
-
- /**
- * @dev AC role, owner of which can mint mRE7SOL token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_RE7SOL_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mRE7SOL token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_RE7SOL_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mRE7SOL token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_RE7SOL_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mROX/MRoxCustomAggregatorFeed.sol b/contracts/products/mROX/MRoxCustomAggregatorFeed.sol
deleted file mode 100644
index a3aa45f8..00000000
--- a/contracts/products/mROX/MRoxCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MRoxMidasAccessControlRoles.sol";
-
-/**
- * @title MRoxCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mROX,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MRoxCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MRoxMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_ROX_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mROX/MRoxDataFeed.sol b/contracts/products/mROX/MRoxDataFeed.sol
deleted file mode 100644
index 3c280591..00000000
--- a/contracts/products/mROX/MRoxDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MRoxMidasAccessControlRoles.sol";
-
-/**
- * @title MRoxDataFeed
- * @notice DataFeed for mROX product
- * @author RedDuck Software
- */
-contract MRoxDataFeed is DataFeed, MRoxMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_ROX_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mROX/MRoxDepositVault.sol b/contracts/products/mROX/MRoxDepositVault.sol
deleted file mode 100644
index a6e53cd7..00000000
--- a/contracts/products/mROX/MRoxDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MRoxMidasAccessControlRoles.sol";
-
-/**
- * @title MRoxDepositVault
- * @notice Smart contract that handles mROX minting
- * @author RedDuck Software
- */
-contract MRoxDepositVault is DepositVault, MRoxMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_ROX_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mROX/MRoxMidasAccessControlRoles.sol b/contracts/products/mROX/MRoxMidasAccessControlRoles.sol
deleted file mode 100644
index 8222c2ad..00000000
--- a/contracts/products/mROX/MRoxMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MRoxMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mROX contracts
- * @author RedDuck Software
- */
-abstract contract MRoxMidasAccessControlRoles {
- /**
- * @notice actor that can manage MRoxDepositVault
- */
- bytes32 public constant M_ROX_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_ROX_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MRoxRedemptionVault
- */
- bytes32 public constant M_ROX_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_ROX_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MRoxCustomAggregatorFeed and MRoxDataFeed
- */
- bytes32 public constant M_ROX_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_ROX_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/mROX/MRoxRedemptionVaultWithSwapper.sol b/contracts/products/mROX/MRoxRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 1b578af8..00000000
--- a/contracts/products/mROX/MRoxRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MRoxMidasAccessControlRoles.sol";
-
-/**
- * @title MRoxRedemptionVaultWithSwapper
- * @notice Smart contract that handles mROX redemptions
- * @author RedDuck Software
- */
-contract MRoxRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MRoxMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_ROX_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mROX/mROX.sol b/contracts/products/mROX/mROX.sol
deleted file mode 100644
index 26a6d71a..00000000
--- a/contracts/products/mROX/mROX.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title mROX
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mROX is mToken {
- /**
- * @notice actor that can mint mROX
- */
- bytes32 public constant M_ROX_MINT_OPERATOR_ROLE =
- keccak256("M_ROX_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mROX
- */
- bytes32 public constant M_ROX_BURN_OPERATOR_ROLE =
- keccak256("M_ROX_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mROX
- */
- bytes32 public constant M_ROX_PAUSE_OPERATOR_ROLE =
- keccak256("M_ROX_PAUSE_OPERATOR_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 Rockaway Market Neutral", "mROX");
- }
-
- /**
- * @dev AC role, owner of which can mint mROX token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_ROX_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mROX token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_ROX_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mROX token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_ROX_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mRe7ETH/MRe7EthCustomAggregatorFeed.sol b/contracts/products/mRe7ETH/MRe7EthCustomAggregatorFeed.sol
deleted file mode 100644
index f36167ea..00000000
--- a/contracts/products/mRe7ETH/MRe7EthCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MRe7EthMidasAccessControlRoles.sol";
-
-/**
- * @title MRe7EthCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mRe7ETH,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MRe7EthCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MRe7EthMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_RE7ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mRe7ETH/MRe7EthDataFeed.sol b/contracts/products/mRe7ETH/MRe7EthDataFeed.sol
deleted file mode 100644
index 2b445dc9..00000000
--- a/contracts/products/mRe7ETH/MRe7EthDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MRe7EthMidasAccessControlRoles.sol";
-
-/**
- * @title MRe7EthDataFeed
- * @notice DataFeed for mRe7ETH product
- * @author RedDuck Software
- */
-contract MRe7EthDataFeed is DataFeed, MRe7EthMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_RE7ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mRe7ETH/MRe7EthDepositVault.sol b/contracts/products/mRe7ETH/MRe7EthDepositVault.sol
deleted file mode 100644
index 75df184a..00000000
--- a/contracts/products/mRe7ETH/MRe7EthDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MRe7EthMidasAccessControlRoles.sol";
-
-/**
- * @title MRe7EthDepositVault
- * @notice Smart contract that handles mRe7ETH minting
- * @author RedDuck Software
- */
-contract MRe7EthDepositVault is DepositVault, MRe7EthMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_RE7ETH_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mRe7ETH/MRe7EthMidasAccessControlRoles.sol b/contracts/products/mRe7ETH/MRe7EthMidasAccessControlRoles.sol
deleted file mode 100644
index 8beda66b..00000000
--- a/contracts/products/mRe7ETH/MRe7EthMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MRe7EthMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mRe7ETH contracts
- * @author RedDuck Software
- */
-abstract contract MRe7EthMidasAccessControlRoles {
- /**
- * @notice actor that can manage MRe7EthDepositVault
- */
- bytes32 public constant M_RE7ETH_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_RE7ETH_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MRe7EthRedemptionVault
- */
- bytes32 public constant M_RE7ETH_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_RE7ETH_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MRe7EthCustomAggregatorFeed and MRe7EthDataFeed
- */
- bytes32 public constant M_RE7ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_RE7ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/mRe7ETH/MRe7EthRedemptionVaultWithSwapper.sol b/contracts/products/mRe7ETH/MRe7EthRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 7c517e45..00000000
--- a/contracts/products/mRe7ETH/MRe7EthRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MRe7EthMidasAccessControlRoles.sol";
-
-/**
- * @title MRe7EthRedemptionVaultWithSwapper
- * @notice Smart contract that handles mRe7ETH redemptions
- * @author RedDuck Software
- */
-contract MRe7EthRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MRe7EthMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_RE7ETH_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mRe7ETH/mRe7ETH.sol b/contracts/products/mRe7ETH/mRe7ETH.sol
deleted file mode 100644
index a08c04fa..00000000
--- a/contracts/products/mRe7ETH/mRe7ETH.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title mRe7ETH
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mRe7ETH is mToken {
- /**
- * @notice actor that can mint mRe7ETH
- */
- bytes32 public constant M_RE7ETH_MINT_OPERATOR_ROLE =
- keccak256("M_RE7ETH_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mRe7ETH
- */
- bytes32 public constant M_RE7ETH_BURN_OPERATOR_ROLE =
- keccak256("M_RE7ETH_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mRe7ETH
- */
- bytes32 public constant M_RE7ETH_PAUSE_OPERATOR_ROLE =
- keccak256("M_RE7ETH_PAUSE_OPERATOR_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 Re7 Ethereum", "mRe7ETH");
- }
-
- /**
- * @dev AC role, owner of which can mint mRe7ETH token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_RE7ETH_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mRe7ETH token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_RE7ETH_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mRe7ETH token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_RE7ETH_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mSL/MSLCustomAggregatorFeed.sol b/contracts/products/mSL/MSLCustomAggregatorFeed.sol
deleted file mode 100644
index 2b6dd7e5..00000000
--- a/contracts/products/mSL/MSLCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,29 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MSlMidasAccessControlRoles.sol";
-
-/**
- * @title MSlCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mSL,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- * @dev last initialization version is 2
- */
-contract MSlCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MSlMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_SL_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mSL/MSlDataFeed.sol b/contracts/products/mSL/MSlDataFeed.sol
deleted file mode 100644
index ba3b68d4..00000000
--- a/contracts/products/mSL/MSlDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MSlMidasAccessControlRoles.sol";
-
-/**
- * @title MSlDataFeed
- * @notice DataFeed for mSL product
- * @author RedDuck Software
- */
-contract MSlDataFeed is DataFeed, MSlMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_SL_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mSL/MSlDepositVault.sol b/contracts/products/mSL/MSlDepositVault.sol
deleted file mode 100644
index 8b20825c..00000000
--- a/contracts/products/mSL/MSlDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MSlMidasAccessControlRoles.sol";
-
-/**
- * @title MSlDepositVault
- * @notice Smart contract that handles mSL minting
- * @author RedDuck Software
- */
-contract MSlDepositVault is DepositVault, MSlMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_SL_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mSL/MSlMidasAccessControlRoles.sol b/contracts/products/mSL/MSlMidasAccessControlRoles.sol
deleted file mode 100644
index 0f29f7f6..00000000
--- a/contracts/products/mSL/MSlMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MSlMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mSL contracts
- * @author RedDuck Software
- */
-abstract contract MSlMidasAccessControlRoles {
- /**
- * @notice actor that can manage MSlDepositVault
- */
- bytes32 public constant M_SL_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_SL_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MSlRedemptionVault
- */
- bytes32 public constant M_SL_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_SL_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MSlCustomAggregatorFeed and MSlDataFeed
- */
- bytes32 public constant M_SL_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_SL_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/mSL/MSlRedemptionVaultWithMToken.sol b/contracts/products/mSL/MSlRedemptionVaultWithMToken.sol
deleted file mode 100644
index feef42cb..00000000
--- a/contracts/products/mSL/MSlRedemptionVaultWithMToken.sol
+++ /dev/null
@@ -1,29 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithMToken.sol";
-import "./MSlMidasAccessControlRoles.sol";
-
-/**
- * @title MSlRedemptionVaultWithMToken
- * @notice Smart contract that handles mSL redemptions using mToken
- * liquid strategy. Upgrade-compatible replacement for
- * MSlRedemptionVaultWithSwapper.
- * @author RedDuck Software
- */
-contract MSlRedemptionVaultWithMToken is
- RedemptionVaultWithMToken,
- MSlMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_SL_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mSL/MSlRedemptionVaultWithSwapper.sol b/contracts/products/mSL/MSlRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 64da13e2..00000000
--- a/contracts/products/mSL/MSlRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MSlMidasAccessControlRoles.sol";
-
-/**
- * @title MSlRedemptionVaultWithSwapper
- * @notice Smart contract that handles mSL redemptions
- * @author RedDuck Software
- */
-contract MSlRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MSlMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_SL_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mSL/mSL.sol b/contracts/products/mSL/mSL.sol
deleted file mode 100644
index c54f3ef0..00000000
--- a/contracts/products/mSL/mSL.sol
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-import "../../mToken.sol";
-
-/**
- * @title mSL
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mSL is mToken {
- /**
- * @notice actor that can mint mSL
- */
- bytes32 public constant M_SL_MINT_OPERATOR_ROLE =
- keccak256("M_SL_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mSL
- */
- bytes32 public constant M_SL_BURN_OPERATOR_ROLE =
- keccak256("M_SL_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mSL
- */
- bytes32 public constant M_SL_PAUSE_OPERATOR_ROLE =
- keccak256("M_SL_PAUSE_OPERATOR_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 Staked Liquidity", "mSL");
- }
-
- /**
- * @dev AC role, owner of which can mint mSL token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_SL_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mSL token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_SL_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mSL token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_SL_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mTBILL/MTBillCustomAggregatorFeed.sol b/contracts/products/mTBILL/MTBillCustomAggregatorFeed.sol
deleted file mode 100644
index 40741410..00000000
--- a/contracts/products/mTBILL/MTBillCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MTBillMidasAccessControlRoles.sol";
-
-/**
- * @title MTBillCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mTBILL,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MTBillCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MTBillMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_TBILL_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mTBILL/MTBillCustomAggregatorFeedGrowth.sol b/contracts/products/mTBILL/MTBillCustomAggregatorFeedGrowth.sol
deleted file mode 100644
index abc7dff5..00000000
--- a/contracts/products/mTBILL/MTBillCustomAggregatorFeedGrowth.sol
+++ /dev/null
@@ -1,29 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeedGrowth.sol";
-import "./MTBillMidasAccessControlRoles.sol";
-
-/**
- * @title MTBillCustomAggregatorFeedGrowth
- * @notice AggregatorV3 compatible feed for mTBILL,
- * where price is submitted manually by feed admins,
- * and growth apr applies to the answer.
- * @author RedDuck Software
- */
-contract MTBillCustomAggregatorFeedGrowth is
- CustomAggregatorV3CompatibleFeedGrowth,
- MTBillMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeedGrowth
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_TBILL_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mTBILL/MTBillDataFeed.sol b/contracts/products/mTBILL/MTBillDataFeed.sol
deleted file mode 100644
index 31f6096f..00000000
--- a/contracts/products/mTBILL/MTBillDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MTBillMidasAccessControlRoles.sol";
-
-/**
- * @title MTBillDataFeed
- * @notice DataFeed for mTBILL product
- * @author RedDuck Software
- */
-contract MTBillDataFeed is DataFeed, MTBillMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_TBILL_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mTBILL/MTBillMidasAccessControlRoles.sol b/contracts/products/mTBILL/MTBillMidasAccessControlRoles.sol
deleted file mode 100644
index 9c394b0d..00000000
--- a/contracts/products/mTBILL/MTBillMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,15 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MTBillMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mTBILL contracts
- * @author RedDuck Software
- */
-abstract contract MTBillMidasAccessControlRoles {
- /**
- * @notice actor that can manage MTBillCustomAggregatorFeed and MTBillDataFeed
- */
- bytes32 public constant M_TBILL_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_TBILL_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/mTBILL/mTBILL.sol b/contracts/products/mTBILL/mTBILL.sol
deleted file mode 100644
index ab4231c3..00000000
--- a/contracts/products/mTBILL/mTBILL.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title mTBILL
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mTBILL is mToken {
- /**
- * @notice actor that can mint mTBILL
- */
- bytes32 public constant M_TBILL_MINT_OPERATOR_ROLE =
- keccak256("M_TBILL_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mTBILL
- */
- bytes32 public constant M_TBILL_BURN_OPERATOR_ROLE =
- keccak256("M_TBILL_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mTBILL
- */
- bytes32 public constant M_TBILL_PAUSE_OPERATOR_ROLE =
- keccak256("M_TBILL_PAUSE_OPERATOR_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 US Treasury Bill Token", "mTBILL");
- }
-
- /**
- * @dev AC role, owner of which can mint mTBILL token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_TBILL_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mTBILL token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_TBILL_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mTBILL token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_TBILL_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mTEST/MTestCustomAggregatorFeedGrowth.sol b/contracts/products/mTEST/MTestCustomAggregatorFeedGrowth.sol
deleted file mode 100644
index f578a6f1..00000000
--- a/contracts/products/mTEST/MTestCustomAggregatorFeedGrowth.sol
+++ /dev/null
@@ -1,29 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeedGrowth.sol";
-import "./MTestMidasAccessControlRoles.sol";
-
-/**
- * @title MTestCustomAggregatorFeedGrowth
- * @notice AggregatorV3 compatible feed for mTEST,
- * where price is submitted manually by feed admins,
- * and growth apr applies to the answer.
- * @author RedDuck Software
- */
-contract MTestCustomAggregatorFeedGrowth is
- CustomAggregatorV3CompatibleFeedGrowth,
- MTestMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeedGrowth
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_TEST_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mTEST/MTestDataFeed.sol b/contracts/products/mTEST/MTestDataFeed.sol
deleted file mode 100644
index 6c2ed01e..00000000
--- a/contracts/products/mTEST/MTestDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MTestMidasAccessControlRoles.sol";
-
-/**
- * @title MTestDataFeed
- * @notice DataFeed for mTEST product
- * @author RedDuck Software
- */
-contract MTestDataFeed is DataFeed, MTestMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_TEST_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mTEST/MTestDepositVault.sol b/contracts/products/mTEST/MTestDepositVault.sol
deleted file mode 100644
index 031567de..00000000
--- a/contracts/products/mTEST/MTestDepositVault.sol
+++ /dev/null
@@ -1,31 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MTestMidasAccessControlRoles.sol";
-
-/**
- * @title MTestDepositVault
- * @notice Smart contract that handles mTEST minting
- * @author RedDuck Software
- */
-contract MTestDepositVault is DepositVault, MTestMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_TEST_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-
- /**
- * @inheritdoc Greenlistable
- */
- function greenlistedRole() public pure override returns (bytes32) {
- return M_TEST_GREENLISTED_ROLE;
- }
-}
diff --git a/contracts/products/mTEST/MTestMidasAccessControlRoles.sol b/contracts/products/mTEST/MTestMidasAccessControlRoles.sol
deleted file mode 100644
index cfba3aaa..00000000
--- a/contracts/products/mTEST/MTestMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,33 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MTestMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mTEST contracts
- * @author RedDuck Software
- */
-abstract contract MTestMidasAccessControlRoles {
- /**
- * @notice actor that can manage MTestDepositVault
- */
- bytes32 public constant M_TEST_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_TEST_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MTestRedemptionVault
- */
- bytes32 public constant M_TEST_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_TEST_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MTestCustomAggregatorFeed and MTestDataFeed
- */
- bytes32 public constant M_TEST_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_TEST_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-
- /**
- * @notice greenlist role for mTEST
- */
- bytes32 public constant M_TEST_GREENLISTED_ROLE =
- keccak256("M_TEST_GREENLISTED_ROLE");
-}
diff --git a/contracts/products/mTEST/MTestRedemptionVaultWithSwapper.sol b/contracts/products/mTEST/MTestRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 17982d05..00000000
--- a/contracts/products/mTEST/MTestRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,34 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MTestMidasAccessControlRoles.sol";
-
-/**
- * @title MTestRedemptionVaultWithSwapper
- * @notice Smart contract that handles mTEST redemptions
- * @author RedDuck Software
- */
-contract MTestRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MTestMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_TEST_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-
- /**
- * @inheritdoc Greenlistable
- */
- function greenlistedRole() public pure override returns (bytes32) {
- return M_TEST_GREENLISTED_ROLE;
- }
-}
diff --git a/contracts/products/mTEST/mTEST.sol b/contracts/products/mTEST/mTEST.sol
deleted file mode 100644
index 89955248..00000000
--- a/contracts/products/mTEST/mTEST.sol
+++ /dev/null
@@ -1,75 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mTokenPermissioned.sol";
-import "./MTestMidasAccessControlRoles.sol";
-
-/**
- * @title mTEST
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mTEST is mTokenPermissioned, MTestMidasAccessControlRoles {
- /**
- * @notice actor that can mint mTEST
- */
- bytes32 public constant M_TEST_MINT_OPERATOR_ROLE =
- keccak256("M_TEST_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mTEST
- */
- bytes32 public constant M_TEST_BURN_OPERATOR_ROLE =
- keccak256("M_TEST_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mTEST
- */
- bytes32 public constant M_TEST_PAUSE_OPERATOR_ROLE =
- keccak256("M_TEST_PAUSE_OPERATOR_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 Test", "mTEST");
- }
-
- /**
- * @dev AC role, owner of which can mint mTEST token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_TEST_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mTEST token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_TEST_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mTEST token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_TEST_PAUSE_OPERATOR_ROLE;
- }
-
- /**
- * @inheritdoc mTokenPermissioned
- */
- function _greenlistedRole() internal pure override returns (bytes32) {
- return M_TEST_GREENLISTED_ROLE;
- }
-}
diff --git a/contracts/products/mTU/MTuCustomAggregatorFeed.sol b/contracts/products/mTU/MTuCustomAggregatorFeed.sol
deleted file mode 100644
index 4cef4ef0..00000000
--- a/contracts/products/mTU/MTuCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MTuMidasAccessControlRoles.sol";
-
-/**
- * @title MTuCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mTU,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MTuCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MTuMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_TU_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mTU/MTuDataFeed.sol b/contracts/products/mTU/MTuDataFeed.sol
deleted file mode 100644
index 2a7061e8..00000000
--- a/contracts/products/mTU/MTuDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MTuMidasAccessControlRoles.sol";
-
-/**
- * @title MTuDataFeed
- * @notice DataFeed for mTU product
- * @author RedDuck Software
- */
-contract MTuDataFeed is DataFeed, MTuMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_TU_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mTU/MTuDepositVault.sol b/contracts/products/mTU/MTuDepositVault.sol
deleted file mode 100644
index 048ff7cf..00000000
--- a/contracts/products/mTU/MTuDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MTuMidasAccessControlRoles.sol";
-
-/**
- * @title MTuDepositVault
- * @notice Smart contract that handles mTU minting
- * @author RedDuck Software
- */
-contract MTuDepositVault is DepositVault, MTuMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_TU_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mTU/MTuMidasAccessControlRoles.sol b/contracts/products/mTU/MTuMidasAccessControlRoles.sol
deleted file mode 100644
index 758d4304..00000000
--- a/contracts/products/mTU/MTuMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MTuMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mTU contracts
- * @author RedDuck Software
- */
-abstract contract MTuMidasAccessControlRoles {
- /**
- * @notice actor that can manage MTuDepositVault
- */
- bytes32 public constant M_TU_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_TU_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MTuRedemptionVault
- */
- bytes32 public constant M_TU_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_TU_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MTuCustomAggregatorFeed and MTuDataFeed
- */
- bytes32 public constant M_TU_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_TU_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/mTU/MTuRedemptionVaultWithSwapper.sol b/contracts/products/mTU/MTuRedemptionVaultWithSwapper.sol
deleted file mode 100644
index e42dbcf9..00000000
--- a/contracts/products/mTU/MTuRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MTuMidasAccessControlRoles.sol";
-
-/**
- * @title MTuRedemptionVaultWithSwapper
- * @notice Smart contract that handles mTU redemptions
- * @author RedDuck Software
- */
-contract MTuRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MTuMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_TU_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mTU/mTU.sol b/contracts/products/mTU/mTU.sol
deleted file mode 100644
index ac1d81f8..00000000
--- a/contracts/products/mTU/mTU.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title mTU
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mTU is mToken {
- /**
- * @notice actor that can mint mTU
- */
- bytes32 public constant M_TU_MINT_OPERATOR_ROLE =
- keccak256("M_TU_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mTU
- */
- bytes32 public constant M_TU_BURN_OPERATOR_ROLE =
- keccak256("M_TU_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mTU
- */
- bytes32 public constant M_TU_PAUSE_OPERATOR_ROLE =
- keccak256("M_TU_PAUSE_OPERATOR_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 ("X", "mTU");
- }
-
- /**
- * @dev AC role, owner of which can mint mTU token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_TU_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mTU token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_TU_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mTU token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_TU_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mWIN/MWinCustomAggregatorFeed.sol b/contracts/products/mWIN/MWinCustomAggregatorFeed.sol
deleted file mode 100644
index 5744ec2b..00000000
--- a/contracts/products/mWIN/MWinCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MWinMidasAccessControlRoles.sol";
-
-/**
- * @title MWinCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mWIN,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MWinCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MWinMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_WIN_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mWIN/MWinDataFeed.sol b/contracts/products/mWIN/MWinDataFeed.sol
deleted file mode 100644
index b7a8dcc8..00000000
--- a/contracts/products/mWIN/MWinDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MWinMidasAccessControlRoles.sol";
-
-/**
- * @title MWinDataFeed
- * @notice DataFeed for mWIN product
- * @author RedDuck Software
- */
-contract MWinDataFeed is DataFeed, MWinMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_WIN_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mWIN/MWinDepositVault.sol b/contracts/products/mWIN/MWinDepositVault.sol
deleted file mode 100644
index ec16f047..00000000
--- a/contracts/products/mWIN/MWinDepositVault.sol
+++ /dev/null
@@ -1,31 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MWinMidasAccessControlRoles.sol";
-
-/**
- * @title MWinDepositVault
- * @notice Smart contract that handles mWIN minting
- * @author RedDuck Software
- */
-contract MWinDepositVault is DepositVault, MWinMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_WIN_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-
- /**
- * @inheritdoc Greenlistable
- */
- function greenlistedRole() public pure override returns (bytes32) {
- return M_WIN_GREENLISTED_ROLE;
- }
-}
diff --git a/contracts/products/mWIN/MWinMidasAccessControlRoles.sol b/contracts/products/mWIN/MWinMidasAccessControlRoles.sol
deleted file mode 100644
index 15165f03..00000000
--- a/contracts/products/mWIN/MWinMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,33 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MWinMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mWIN contracts
- * @author RedDuck Software
- */
-abstract contract MWinMidasAccessControlRoles {
- /**
- * @notice actor that can manage MWinDepositVault
- */
- bytes32 public constant M_WIN_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_WIN_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MWinRedemptionVault
- */
- bytes32 public constant M_WIN_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_WIN_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MWinCustomAggregatorFeed and MWinDataFeed
- */
- bytes32 public constant M_WIN_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_WIN_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-
- /**
- * @notice greenlist role for mWIN
- */
- bytes32 public constant M_WIN_GREENLISTED_ROLE =
- keccak256("M_WIN_GREENLISTED_ROLE");
-}
diff --git a/contracts/products/mWIN/MWinRedemptionVaultWithMToken.sol b/contracts/products/mWIN/MWinRedemptionVaultWithMToken.sol
deleted file mode 100644
index 45b5fc54..00000000
--- a/contracts/products/mWIN/MWinRedemptionVaultWithMToken.sol
+++ /dev/null
@@ -1,36 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithMToken.sol";
-import "./MWinMidasAccessControlRoles.sol";
-
-/**
- * @title MWinRedemptionVaultWithMToken
- * @notice Smart contract that handles mWIN redemptions using mToken
- * liquid strategy. Upgrade-compatible replacement for
- * MWinRedemptionVaultWithSwapper.
- * @author RedDuck Software
- */
-contract MWinRedemptionVaultWithMToken is
- RedemptionVaultWithMToken,
- MWinMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_WIN_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-
- /**
- * @inheritdoc Greenlistable
- */
- function greenlistedRole() public pure override returns (bytes32) {
- return M_WIN_GREENLISTED_ROLE;
- }
-}
diff --git a/contracts/products/mWIN/MWinRedemptionVaultWithSwapper.sol b/contracts/products/mWIN/MWinRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 92f99f39..00000000
--- a/contracts/products/mWIN/MWinRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,34 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MWinMidasAccessControlRoles.sol";
-
-/**
- * @title MWinRedemptionVaultWithSwapper
- * @notice Smart contract that handles mWIN redemptions
- * @author RedDuck Software
- */
-contract MWinRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MWinMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_WIN_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-
- /**
- * @inheritdoc Greenlistable
- */
- function greenlistedRole() public pure override returns (bytes32) {
- return M_WIN_GREENLISTED_ROLE;
- }
-}
diff --git a/contracts/products/mWIN/mWIN.sol b/contracts/products/mWIN/mWIN.sol
deleted file mode 100644
index c31efe28..00000000
--- a/contracts/products/mWIN/mWIN.sol
+++ /dev/null
@@ -1,75 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mTokenPermissioned.sol";
-import "./MWinMidasAccessControlRoles.sol";
-
-/**
- * @title mWIN
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mWIN is mTokenPermissioned, MWinMidasAccessControlRoles {
- /**
- * @notice actor that can mint mWIN
- */
- bytes32 public constant M_WIN_MINT_OPERATOR_ROLE =
- keccak256("M_WIN_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mWIN
- */
- bytes32 public constant M_WIN_BURN_OPERATOR_ROLE =
- keccak256("M_WIN_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mWIN
- */
- bytes32 public constant M_WIN_PAUSE_OPERATOR_ROLE =
- keccak256("M_WIN_PAUSE_OPERATOR_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 Wellington Income Opportunities", "mWIN");
- }
-
- /**
- * @dev AC role, owner of which can mint mWIN token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_WIN_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mWIN token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_WIN_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mWIN token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_WIN_PAUSE_OPERATOR_ROLE;
- }
-
- /**
- * @inheritdoc mTokenPermissioned
- */
- function _greenlistedRole() internal pure override returns (bytes32) {
- return M_WIN_GREENLISTED_ROLE;
- }
-}
diff --git a/contracts/products/mWildUSD/MWildUsdCustomAggregatorFeed.sol b/contracts/products/mWildUSD/MWildUsdCustomAggregatorFeed.sol
deleted file mode 100644
index df6f9bfc..00000000
--- a/contracts/products/mWildUSD/MWildUsdCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MWildUsdMidasAccessControlRoles.sol";
-
-/**
- * @title MWildUsdCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mWildUSD,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MWildUsdCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MWildUsdMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_WILD_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mWildUSD/MWildUsdDataFeed.sol b/contracts/products/mWildUSD/MWildUsdDataFeed.sol
deleted file mode 100644
index dad9700e..00000000
--- a/contracts/products/mWildUSD/MWildUsdDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MWildUsdMidasAccessControlRoles.sol";
-
-/**
- * @title MWildUsdDataFeed
- * @notice DataFeed for mWildUSD product
- * @author RedDuck Software
- */
-contract MWildUsdDataFeed is DataFeed, MWildUsdMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_WILD_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mWildUSD/MWildUsdDepositVault.sol b/contracts/products/mWildUSD/MWildUsdDepositVault.sol
deleted file mode 100644
index 8ed32089..00000000
--- a/contracts/products/mWildUSD/MWildUsdDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MWildUsdMidasAccessControlRoles.sol";
-
-/**
- * @title MWildUsdDepositVault
- * @notice Smart contract that handles mWildUSD minting
- * @author RedDuck Software
- */
-contract MWildUsdDepositVault is DepositVault, MWildUsdMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_WILD_USD_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mWildUSD/MWildUsdMidasAccessControlRoles.sol b/contracts/products/mWildUSD/MWildUsdMidasAccessControlRoles.sol
deleted file mode 100644
index 7701e53a..00000000
--- a/contracts/products/mWildUSD/MWildUsdMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MWildUsdMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mWildUSD contracts
- * @author RedDuck Software
- */
-abstract contract MWildUsdMidasAccessControlRoles {
- /**
- * @notice actor that can manage MWildUsdDepositVault
- */
- bytes32 public constant M_WILD_USD_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_WILD_USD_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MWildUsdRedemptionVault
- */
- bytes32 public constant M_WILD_USD_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_WILD_USD_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MWildUsdCustomAggregatorFeed and MWildUsdDataFeed
- */
- bytes32 public constant M_WILD_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_WILD_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/mWildUSD/MWildUsdRedemptionVaultWithSwapper.sol b/contracts/products/mWildUSD/MWildUsdRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 8af4a503..00000000
--- a/contracts/products/mWildUSD/MWildUsdRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MWildUsdMidasAccessControlRoles.sol";
-
-/**
- * @title MWildUsdRedemptionVaultWithSwapper
- * @notice Smart contract that handles mWildUSD redemptions
- * @author RedDuck Software
- */
-contract MWildUsdRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MWildUsdMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_WILD_USD_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mWildUSD/mWildUSD.sol b/contracts/products/mWildUSD/mWildUSD.sol
deleted file mode 100644
index 5289de18..00000000
--- a/contracts/products/mWildUSD/mWildUSD.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title mWildUSD
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mWildUSD is mToken {
- /**
- * @notice actor that can mint mWildUSD
- */
- bytes32 public constant M_WILD_USD_MINT_OPERATOR_ROLE =
- keccak256("M_WILD_USD_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mWildUSD
- */
- bytes32 public constant M_WILD_USD_BURN_OPERATOR_ROLE =
- keccak256("M_WILD_USD_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mWildUSD
- */
- bytes32 public constant M_WILD_USD_PAUSE_OPERATOR_ROLE =
- keccak256("M_WILD_USD_PAUSE_OPERATOR_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 ("mWildUSD", "mWildUSD");
- }
-
- /**
- * @dev AC role, owner of which can mint mWildUSD token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_WILD_USD_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mWildUSD token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_WILD_USD_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mWildUSD token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_WILD_USD_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mXRP/MXrpCustomAggregatorFeed.sol b/contracts/products/mXRP/MXrpCustomAggregatorFeed.sol
deleted file mode 100644
index ea0a91db..00000000
--- a/contracts/products/mXRP/MXrpCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MXrpMidasAccessControlRoles.sol";
-
-/**
- * @title MXrpCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mXRP,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MXrpCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MXrpMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_XRP_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mXRP/MXrpDataFeed.sol b/contracts/products/mXRP/MXrpDataFeed.sol
deleted file mode 100644
index df280f4f..00000000
--- a/contracts/products/mXRP/MXrpDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MXrpMidasAccessControlRoles.sol";
-
-/**
- * @title MXrpDataFeed
- * @notice DataFeed for mXRP product
- * @author RedDuck Software
- */
-contract MXrpDataFeed is DataFeed, MXrpMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_XRP_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mXRP/MXrpDepositVault.sol b/contracts/products/mXRP/MXrpDepositVault.sol
deleted file mode 100644
index c8b01ea8..00000000
--- a/contracts/products/mXRP/MXrpDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MXrpMidasAccessControlRoles.sol";
-
-/**
- * @title MXrpDepositVault
- * @notice Smart contract that handles mXRP minting
- * @author RedDuck Software
- */
-contract MXrpDepositVault is DepositVault, MXrpMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_XRP_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mXRP/MXrpMidasAccessControlRoles.sol b/contracts/products/mXRP/MXrpMidasAccessControlRoles.sol
deleted file mode 100644
index 86774ad0..00000000
--- a/contracts/products/mXRP/MXrpMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MXrpMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mXRP contracts
- * @author RedDuck Software
- */
-abstract contract MXrpMidasAccessControlRoles {
- /**
- * @notice actor that can manage MXrpDepositVault
- */
- bytes32 public constant M_XRP_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_XRP_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MXrpRedemptionVault
- */
- bytes32 public constant M_XRP_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_XRP_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MXrpCustomAggregatorFeed and MXrpDataFeed
- */
- bytes32 public constant M_XRP_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_XRP_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/mXRP/MXrpRedemptionVaultWithSwapper.sol b/contracts/products/mXRP/MXrpRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 03fe0b28..00000000
--- a/contracts/products/mXRP/MXrpRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MXrpMidasAccessControlRoles.sol";
-
-/**
- * @title MXrpRedemptionVaultWithSwapper
- * @notice Smart contract that handles mXRP redemptions
- * @author RedDuck Software
- */
-contract MXrpRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MXrpMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_XRP_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mXRP/mXRP.sol b/contracts/products/mXRP/mXRP.sol
deleted file mode 100644
index 47af05ea..00000000
--- a/contracts/products/mXRP/mXRP.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title mXRP
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mXRP is mToken {
- /**
- * @notice actor that can mint mXRP
- */
- bytes32 public constant M_XRP_MINT_OPERATOR_ROLE =
- keccak256("M_XRP_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mXRP
- */
- bytes32 public constant M_XRP_BURN_OPERATOR_ROLE =
- keccak256("M_XRP_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mXRP
- */
- bytes32 public constant M_XRP_PAUSE_OPERATOR_ROLE =
- keccak256("M_XRP_PAUSE_OPERATOR_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 XRP", "mXRP");
- }
-
- /**
- * @dev AC role, owner of which can mint mXRP token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_XRP_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mXRP token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_XRP_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mXRP token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_XRP_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/mevBTC/MevBtcCustomAggregatorFeed.sol b/contracts/products/mevBTC/MevBtcCustomAggregatorFeed.sol
deleted file mode 100644
index 97e36bc1..00000000
--- a/contracts/products/mevBTC/MevBtcCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,23 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MevBtcMidasAccessControlRoles.sol";
-
-/**
- * @title MevBtcCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for mevBTC,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MevBtcCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MevBtcMidasAccessControlRoles
-{
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return MEV_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mevBTC/MevBtcDataFeed.sol b/contracts/products/mevBTC/MevBtcDataFeed.sol
deleted file mode 100644
index a52b4772..00000000
--- a/contracts/products/mevBTC/MevBtcDataFeed.sol
+++ /dev/null
@@ -1,19 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MevBtcMidasAccessControlRoles.sol";
-
-/**
- * @title MevBtcDataFeed
- * @notice DataFeed for mevBTC product
- * @author RedDuck Software
- */
-contract MevBtcDataFeed is DataFeed, MevBtcMidasAccessControlRoles {
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return MEV_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mevBTC/MevBtcDepositVault.sol b/contracts/products/mevBTC/MevBtcDepositVault.sol
deleted file mode 100644
index f495a276..00000000
--- a/contracts/products/mevBTC/MevBtcDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MevBtcMidasAccessControlRoles.sol";
-
-/**
- * @title MevBtcDepositVault
- * @notice Smart contract that handles mevBTC minting
- * @author RedDuck Software
- */
-contract MevBtcDepositVault is DepositVault, MevBtcMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return MEV_BTC_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mevBTC/MevBtcMidasAccessControlRoles.sol b/contracts/products/mevBTC/MevBtcMidasAccessControlRoles.sol
deleted file mode 100644
index 026b7833..00000000
--- a/contracts/products/mevBTC/MevBtcMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MevBtcMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for mevBTC contracts
- * @author RedDuck Software
- */
-abstract contract MevBtcMidasAccessControlRoles {
- /**
- * @notice actor that can manage MevBtcDepositVault
- */
- bytes32 public constant MEV_BTC_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("MEV_BTC_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MevBtcRedemptionVault
- */
- bytes32 public constant MEV_BTC_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("MEV_BTC_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MevBtcCustomAggregatorFeed and MevBtcDataFeed
- */
- bytes32 public constant MEV_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("MEV_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/mevBTC/MevBtcRedemptionVaultWithSwapper.sol b/contracts/products/mevBTC/MevBtcRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 4b659bbc..00000000
--- a/contracts/products/mevBTC/MevBtcRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MevBtcMidasAccessControlRoles.sol";
-
-/**
- * @title MevBtcRedemptionVaultWithSwapper
- * @notice Smart contract that handles mevBTC redemptions
- * @author RedDuck Software
- */
-contract MevBtcRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MevBtcMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return MEV_BTC_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/mevBTC/mevBTC.sol b/contracts/products/mevBTC/mevBTC.sol
deleted file mode 100644
index 18e423fc..00000000
--- a/contracts/products/mevBTC/mevBTC.sol
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-import "../../mToken.sol";
-
-/**
- * @title mevBTC
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract mevBTC is mToken {
- /**
- * @notice actor that can mint mevBTC
- */
- bytes32 public constant MEV_BTC_MINT_OPERATOR_ROLE =
- keccak256("MEV_BTC_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn mevBTC
- */
- bytes32 public constant MEV_BTC_BURN_OPERATOR_ROLE =
- keccak256("MEV_BTC_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause mevBTC
- */
- bytes32 public constant MEV_BTC_PAUSE_OPERATOR_ROLE =
- keccak256("MEV_BTC_PAUSE_OPERATOR_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 ("Bitcoin MEV Capital", "mevBTC");
- }
-
- /**
- * @dev AC role, owner of which can mint mevBTC token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return MEV_BTC_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn mevBTC token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return MEV_BTC_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause mevBTC token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return MEV_BTC_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/msyrupUSD/MSyrupUsdCustomAggregatorFeed.sol b/contracts/products/msyrupUSD/MSyrupUsdCustomAggregatorFeed.sol
deleted file mode 100644
index 773e9ac5..00000000
--- a/contracts/products/msyrupUSD/MSyrupUsdCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MSyrupUsdMidasAccessControlRoles.sol";
-
-/**
- * @title MSyrupUsdCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for msyrupUSD,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MSyrupUsdCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MSyrupUsdMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_SYRUP_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/msyrupUSD/MSyrupUsdDataFeed.sol b/contracts/products/msyrupUSD/MSyrupUsdDataFeed.sol
deleted file mode 100644
index 14cd87ff..00000000
--- a/contracts/products/msyrupUSD/MSyrupUsdDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MSyrupUsdMidasAccessControlRoles.sol";
-
-/**
- * @title MSyrupUsdDataFeed
- * @notice DataFeed for msyrupUSD product
- * @author RedDuck Software
- */
-contract MSyrupUsdDataFeed is DataFeed, MSyrupUsdMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_SYRUP_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/msyrupUSD/MSyrupUsdDepositVault.sol b/contracts/products/msyrupUSD/MSyrupUsdDepositVault.sol
deleted file mode 100644
index 847f29ef..00000000
--- a/contracts/products/msyrupUSD/MSyrupUsdDepositVault.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MSyrupUsdMidasAccessControlRoles.sol";
-
-/**
- * @title MSyrupUsdDepositVault
- * @notice Smart contract that handles msyrupUSD minting
- * @author RedDuck Software
- */
-contract MSyrupUsdDepositVault is
- DepositVault,
- MSyrupUsdMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_SYRUP_USD_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/msyrupUSD/MSyrupUsdMidasAccessControlRoles.sol b/contracts/products/msyrupUSD/MSyrupUsdMidasAccessControlRoles.sol
deleted file mode 100644
index 3102c7f2..00000000
--- a/contracts/products/msyrupUSD/MSyrupUsdMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MSyrupUsdMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for msyrupUSD contracts
- * @author RedDuck Software
- */
-abstract contract MSyrupUsdMidasAccessControlRoles {
- /**
- * @notice actor that can manage MSyrupUsdDepositVault
- */
- bytes32 public constant M_SYRUP_USD_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_SYRUP_USD_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MSyrupUsdRedemptionVault
- */
- bytes32 public constant M_SYRUP_USD_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_SYRUP_USD_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MSyrupUsdCustomAggregatorFeed and MSyrupUsdDataFeed
- */
- bytes32 public constant M_SYRUP_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_SYRUP_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/msyrupUSD/MSyrupUsdRedemptionVaultWithSwapper.sol b/contracts/products/msyrupUSD/MSyrupUsdRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 5e154686..00000000
--- a/contracts/products/msyrupUSD/MSyrupUsdRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MSyrupUsdMidasAccessControlRoles.sol";
-
-/**
- * @title MSyrupUsdRedemptionVaultWithSwapper
- * @notice Smart contract that handles msyrupUSD redemptions
- * @author RedDuck Software
- */
-contract MSyrupUsdRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MSyrupUsdMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_SYRUP_USD_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/msyrupUSD/msyrupUSD.sol b/contracts/products/msyrupUSD/msyrupUSD.sol
deleted file mode 100644
index 7868e407..00000000
--- a/contracts/products/msyrupUSD/msyrupUSD.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title msyrupUSD
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract msyrupUSD is mToken {
- /**
- * @notice actor that can mint msyrupUSD
- */
- bytes32 public constant M_SYRUP_USD_MINT_OPERATOR_ROLE =
- keccak256("M_SYRUP_USD_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn msyrupUSD
- */
- bytes32 public constant M_SYRUP_USD_BURN_OPERATOR_ROLE =
- keccak256("M_SYRUP_USD_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause msyrupUSD
- */
- bytes32 public constant M_SYRUP_USD_PAUSE_OPERATOR_ROLE =
- keccak256("M_SYRUP_USD_PAUSE_OPERATOR_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 ("syrupUSDC supercharged", "msyrupUSD");
- }
-
- /**
- * @dev AC role, owner of which can mint msyrupUSD token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_SYRUP_USD_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn msyrupUSD token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_SYRUP_USD_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause msyrupUSD token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_SYRUP_USD_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/msyrupUSDp/MSyrupUsdpCustomAggregatorFeed.sol b/contracts/products/msyrupUSDp/MSyrupUsdpCustomAggregatorFeed.sol
deleted file mode 100644
index f02a90cb..00000000
--- a/contracts/products/msyrupUSDp/MSyrupUsdpCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./MSyrupUsdpMidasAccessControlRoles.sol";
-
-/**
- * @title MSyrupUsdpCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for msyrupUSDp,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract MSyrupUsdpCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- MSyrupUsdpMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_SYRUP_USDP_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/msyrupUSDp/MSyrupUsdpDataFeed.sol b/contracts/products/msyrupUSDp/MSyrupUsdpDataFeed.sol
deleted file mode 100644
index e751883d..00000000
--- a/contracts/products/msyrupUSDp/MSyrupUsdpDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./MSyrupUsdpMidasAccessControlRoles.sol";
-
-/**
- * @title MSyrupUsdpDataFeed
- * @notice DataFeed for msyrupUSDp product
- * @author RedDuck Software
- */
-contract MSyrupUsdpDataFeed is DataFeed, MSyrupUsdpMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return M_SYRUP_USDP_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/msyrupUSDp/MSyrupUsdpDepositVault.sol b/contracts/products/msyrupUSDp/MSyrupUsdpDepositVault.sol
deleted file mode 100644
index 11224905..00000000
--- a/contracts/products/msyrupUSDp/MSyrupUsdpDepositVault.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./MSyrupUsdpMidasAccessControlRoles.sol";
-
-/**
- * @title MSyrupUsdpDepositVault
- * @notice Smart contract that handles msyrupUSDp minting
- * @author RedDuck Software
- */
-contract MSyrupUsdpDepositVault is
- DepositVault,
- MSyrupUsdpMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_SYRUP_USDP_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/msyrupUSDp/MSyrupUsdpMidasAccessControlRoles.sol b/contracts/products/msyrupUSDp/MSyrupUsdpMidasAccessControlRoles.sol
deleted file mode 100644
index 9b2bc7d6..00000000
--- a/contracts/products/msyrupUSDp/MSyrupUsdpMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title MSyrupUsdpMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for msyrupUSDp contracts
- * @author RedDuck Software
- */
-abstract contract MSyrupUsdpMidasAccessControlRoles {
- /**
- * @notice actor that can manage MSyrupUsdpDepositVault
- */
- bytes32 public constant M_SYRUP_USDP_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("M_SYRUP_USDP_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MSyrupUsdpRedemptionVault
- */
- bytes32 public constant M_SYRUP_USDP_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("M_SYRUP_USDP_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage MSyrupUsdpCustomAggregatorFeed and MSyrupUsdpDataFeed
- */
- bytes32 public constant M_SYRUP_USDP_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("M_SYRUP_USDP_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/msyrupUSDp/MSyrupUsdpRedemptionVaultWithSwapper.sol b/contracts/products/msyrupUSDp/MSyrupUsdpRedemptionVaultWithSwapper.sol
deleted file mode 100644
index b9b14ce1..00000000
--- a/contracts/products/msyrupUSDp/MSyrupUsdpRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./MSyrupUsdpMidasAccessControlRoles.sol";
-
-/**
- * @title MSyrupUsdpRedemptionVaultWithSwapper
- * @notice Smart contract that handles msyrupUSDp redemptions
- * @author RedDuck Software
- */
-contract MSyrupUsdpRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- MSyrupUsdpMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return M_SYRUP_USDP_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/msyrupUSDp/msyrupUSDp.sol b/contracts/products/msyrupUSDp/msyrupUSDp.sol
deleted file mode 100644
index f2d4f831..00000000
--- a/contracts/products/msyrupUSDp/msyrupUSDp.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title msyrupUSDp
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract msyrupUSDp is mToken {
- /**
- * @notice actor that can mint msyrupUSDp
- */
- bytes32 public constant M_SYRUP_USDP_MINT_OPERATOR_ROLE =
- keccak256("M_SYRUP_USDP_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn msyrupUSDp
- */
- bytes32 public constant M_SYRUP_USDP_BURN_OPERATOR_ROLE =
- keccak256("M_SYRUP_USDP_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause msyrupUSDp
- */
- bytes32 public constant M_SYRUP_USDP_PAUSE_OPERATOR_ROLE =
- keccak256("M_SYRUP_USDP_PAUSE_OPERATOR_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 ("Plasma syrupUSD Pre-deposit Midas Vault", "msyrupUSDp");
- }
-
- /**
- * @dev AC role, owner of which can mint msyrupUSDp token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return M_SYRUP_USDP_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn msyrupUSDp token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return M_SYRUP_USDP_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause msyrupUSDp token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return M_SYRUP_USDP_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/obeatUSD/ObeatUsdCustomAggregatorFeed.sol b/contracts/products/obeatUSD/ObeatUsdCustomAggregatorFeed.sol
deleted file mode 100644
index ad286a4e..00000000
--- a/contracts/products/obeatUSD/ObeatUsdCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./ObeatUsdMidasAccessControlRoles.sol";
-
-/**
- * @title ObeatUsdCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for obeatUSD,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract ObeatUsdCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- ObeatUsdMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return OBEAT_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/obeatUSD/ObeatUsdDataFeed.sol b/contracts/products/obeatUSD/ObeatUsdDataFeed.sol
deleted file mode 100644
index 5e79eef5..00000000
--- a/contracts/products/obeatUSD/ObeatUsdDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./ObeatUsdMidasAccessControlRoles.sol";
-
-/**
- * @title ObeatUsdDataFeed
- * @notice DataFeed for obeatUSD product
- * @author RedDuck Software
- */
-contract ObeatUsdDataFeed is DataFeed, ObeatUsdMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return OBEAT_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/obeatUSD/ObeatUsdDepositVault.sol b/contracts/products/obeatUSD/ObeatUsdDepositVault.sol
deleted file mode 100644
index 7ee00a56..00000000
--- a/contracts/products/obeatUSD/ObeatUsdDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./ObeatUsdMidasAccessControlRoles.sol";
-
-/**
- * @title ObeatUsdDepositVault
- * @notice Smart contract that handles obeatUSD minting
- * @author RedDuck Software
- */
-contract ObeatUsdDepositVault is DepositVault, ObeatUsdMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return OBEAT_USD_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/obeatUSD/ObeatUsdMidasAccessControlRoles.sol b/contracts/products/obeatUSD/ObeatUsdMidasAccessControlRoles.sol
deleted file mode 100644
index e05ada06..00000000
--- a/contracts/products/obeatUSD/ObeatUsdMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title ObeatUsdMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for obeatUSD contracts
- * @author RedDuck Software
- */
-abstract contract ObeatUsdMidasAccessControlRoles {
- /**
- * @notice actor that can manage ObeatUsdDepositVault
- */
- bytes32 public constant OBEAT_USD_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("OBEAT_USD_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage ObeatUsdRedemptionVault
- */
- bytes32 public constant OBEAT_USD_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("OBEAT_USD_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage ObeatUsdCustomAggregatorFeed and ObeatUsdDataFeed
- */
- bytes32 public constant OBEAT_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("OBEAT_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/obeatUSD/ObeatUsdRedemptionVaultWithSwapper.sol b/contracts/products/obeatUSD/ObeatUsdRedemptionVaultWithSwapper.sol
deleted file mode 100644
index b3a470f9..00000000
--- a/contracts/products/obeatUSD/ObeatUsdRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./ObeatUsdMidasAccessControlRoles.sol";
-
-/**
- * @title ObeatUsdRedemptionVaultWithSwapper
- * @notice Smart contract that handles obeatUSD redemptions
- * @author RedDuck Software
- */
-contract ObeatUsdRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- ObeatUsdMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return OBEAT_USD_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/obeatUSD/obeatUSD.sol b/contracts/products/obeatUSD/obeatUSD.sol
deleted file mode 100644
index 32147418..00000000
--- a/contracts/products/obeatUSD/obeatUSD.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title obeatUSD
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract obeatUSD is mToken {
- /**
- * @notice actor that can mint obeatUSD
- */
- bytes32 public constant OBEAT_USD_MINT_OPERATOR_ROLE =
- keccak256("OBEAT_USD_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn obeatUSD
- */
- bytes32 public constant OBEAT_USD_BURN_OPERATOR_ROLE =
- keccak256("OBEAT_USD_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause obeatUSD
- */
- bytes32 public constant OBEAT_USD_PAUSE_OPERATOR_ROLE =
- keccak256("OBEAT_USD_PAUSE_OPERATOR_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 ("OmniBeat USD", "obeatUSD");
- }
-
- /**
- * @dev AC role, owner of which can mint obeatUSD token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return OBEAT_USD_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn obeatUSD token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return OBEAT_USD_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause obeatUSD token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return OBEAT_USD_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/plUSD/PlUsdCustomAggregatorFeed.sol b/contracts/products/plUSD/PlUsdCustomAggregatorFeed.sol
deleted file mode 100644
index df02dbfc..00000000
--- a/contracts/products/plUSD/PlUsdCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./PlUsdMidasAccessControlRoles.sol";
-
-/**
- * @title PlUsdCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for plUSD,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract PlUsdCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- PlUsdMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return PL_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/plUSD/PlUsdDataFeed.sol b/contracts/products/plUSD/PlUsdDataFeed.sol
deleted file mode 100644
index 49a6fcc1..00000000
--- a/contracts/products/plUSD/PlUsdDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./PlUsdMidasAccessControlRoles.sol";
-
-/**
- * @title PlUsdDataFeed
- * @notice DataFeed for plUSD product
- * @author RedDuck Software
- */
-contract PlUsdDataFeed is DataFeed, PlUsdMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return PL_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/plUSD/PlUsdDepositVault.sol b/contracts/products/plUSD/PlUsdDepositVault.sol
deleted file mode 100644
index 4a2d1a47..00000000
--- a/contracts/products/plUSD/PlUsdDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./PlUsdMidasAccessControlRoles.sol";
-
-/**
- * @title PlUsdDepositVault
- * @notice Smart contract that handles plUSD minting
- * @author RedDuck Software
- */
-contract PlUsdDepositVault is DepositVault, PlUsdMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return PL_USD_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/plUSD/PlUsdMidasAccessControlRoles.sol b/contracts/products/plUSD/PlUsdMidasAccessControlRoles.sol
deleted file mode 100644
index 714c50a9..00000000
--- a/contracts/products/plUSD/PlUsdMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title PlUsdMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for plUSD contracts
- * @author RedDuck Software
- */
-abstract contract PlUsdMidasAccessControlRoles {
- /**
- * @notice actor that can manage PlUsdDepositVault
- */
- bytes32 public constant PL_USD_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("PL_USD_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage PlUsdRedemptionVault
- */
- bytes32 public constant PL_USD_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("PL_USD_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage PlUsdCustomAggregatorFeed and PlUsdDataFeed
- */
- bytes32 public constant PL_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("PL_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/plUSD/PlUsdRedemptionVaultWithSwapper.sol b/contracts/products/plUSD/PlUsdRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 62a07b27..00000000
--- a/contracts/products/plUSD/PlUsdRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./PlUsdMidasAccessControlRoles.sol";
-
-/**
- * @title PlUsdRedemptionVaultWithSwapper
- * @notice Smart contract that handles plUSD redemptions
- * @author RedDuck Software
- */
-contract PlUsdRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- PlUsdMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return PL_USD_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/plUSD/plUSD.sol b/contracts/products/plUSD/plUSD.sol
deleted file mode 100644
index e11750d1..00000000
--- a/contracts/products/plUSD/plUSD.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title plUSD
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract plUSD is mToken {
- /**
- * @notice actor that can mint plUSD
- */
- bytes32 public constant PL_USD_MINT_OPERATOR_ROLE =
- keccak256("PL_USD_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn plUSD
- */
- bytes32 public constant PL_USD_BURN_OPERATOR_ROLE =
- keccak256("PL_USD_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause plUSD
- */
- bytes32 public constant PL_USD_PAUSE_OPERATOR_ROLE =
- keccak256("PL_USD_PAUSE_OPERATOR_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 ("Plasma USD", "plUSD");
- }
-
- /**
- * @dev AC role, owner of which can mint plUSD token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return PL_USD_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn plUSD token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return PL_USD_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause plUSD token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return PL_USD_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/qHVNUSD/QHVNUsdCustomAggregatorFeed.sol b/contracts/products/qHVNUSD/QHVNUsdCustomAggregatorFeed.sol
deleted file mode 100644
index bf0ac580..00000000
--- a/contracts/products/qHVNUSD/QHVNUsdCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./QHVNUsdMidasAccessControlRoles.sol";
-
-/**
- * @title QHVNUsdCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for qHVNUSD,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract QHVNUsdCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- QHVNUsdMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return Q_HVN_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/qHVNUSD/QHVNUsdDataFeed.sol b/contracts/products/qHVNUSD/QHVNUsdDataFeed.sol
deleted file mode 100644
index c6753244..00000000
--- a/contracts/products/qHVNUSD/QHVNUsdDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./QHVNUsdMidasAccessControlRoles.sol";
-
-/**
- * @title QHVNUsdDataFeed
- * @notice DataFeed for qHVNUSD product
- * @author RedDuck Software
- */
-contract QHVNUsdDataFeed is DataFeed, QHVNUsdMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return Q_HVN_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/qHVNUSD/QHVNUsdDepositVault.sol b/contracts/products/qHVNUSD/QHVNUsdDepositVault.sol
deleted file mode 100644
index cdc3fc9c..00000000
--- a/contracts/products/qHVNUSD/QHVNUsdDepositVault.sol
+++ /dev/null
@@ -1,31 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./QHVNUsdMidasAccessControlRoles.sol";
-
-/**
- * @title QHVNUsdDepositVault
- * @notice Smart contract that handles qHVNUSD minting
- * @author RedDuck Software
- */
-contract QHVNUsdDepositVault is DepositVault, QHVNUsdMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return Q_HVN_USD_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-
- /**
- * @inheritdoc Greenlistable
- */
- function greenlistedRole() public pure override returns (bytes32) {
- return Q_HVN_USD_GREENLISTED_ROLE;
- }
-}
diff --git a/contracts/products/qHVNUSD/QHVNUsdMidasAccessControlRoles.sol b/contracts/products/qHVNUSD/QHVNUsdMidasAccessControlRoles.sol
deleted file mode 100644
index 00229ccd..00000000
--- a/contracts/products/qHVNUSD/QHVNUsdMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,33 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title QHVNUsdMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for qHVNUSD contracts
- * @author RedDuck Software
- */
-abstract contract QHVNUsdMidasAccessControlRoles {
- /**
- * @notice actor that can manage QHVNUsdDepositVault
- */
- bytes32 public constant Q_HVN_USD_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("Q_HVN_USD_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage QHVNUsdRedemptionVault
- */
- bytes32 public constant Q_HVN_USD_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("Q_HVN_USD_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage QHVNUsdCustomAggregatorFeed and QHVNUsdDataFeed
- */
- bytes32 public constant Q_HVN_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("Q_HVN_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-
- /**
- * @notice greenlist role for qHVNUSD
- */
- bytes32 public constant Q_HVN_USD_GREENLISTED_ROLE =
- keccak256("Q_HVN_USD_GREENLISTED_ROLE");
-}
diff --git a/contracts/products/qHVNUSD/QHVNUsdRedemptionVaultWithSwapper.sol b/contracts/products/qHVNUSD/QHVNUsdRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 7efc066a..00000000
--- a/contracts/products/qHVNUSD/QHVNUsdRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,34 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./QHVNUsdMidasAccessControlRoles.sol";
-
-/**
- * @title QHVNUsdRedemptionVaultWithSwapper
- * @notice Smart contract that handles qHVNUSD redemptions
- * @author RedDuck Software
- */
-contract QHVNUsdRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- QHVNUsdMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return Q_HVN_USD_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-
- /**
- * @inheritdoc Greenlistable
- */
- function greenlistedRole() public pure override returns (bytes32) {
- return Q_HVN_USD_GREENLISTED_ROLE;
- }
-}
diff --git a/contracts/products/qHVNUSD/qHVNUSD.sol b/contracts/products/qHVNUSD/qHVNUSD.sol
deleted file mode 100644
index 2afba0b7..00000000
--- a/contracts/products/qHVNUSD/qHVNUSD.sol
+++ /dev/null
@@ -1,75 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mTokenPermissioned.sol";
-import "./QHVNUsdMidasAccessControlRoles.sol";
-
-/**
- * @title qHVNUSD
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract qHVNUSD is mTokenPermissioned, QHVNUsdMidasAccessControlRoles {
- /**
- * @notice actor that can mint qHVNUSD
- */
- bytes32 public constant Q_HVN_USD_MINT_OPERATOR_ROLE =
- keccak256("Q_HVN_USD_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn qHVNUSD
- */
- bytes32 public constant Q_HVN_USD_BURN_OPERATOR_ROLE =
- keccak256("Q_HVN_USD_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause qHVNUSD
- */
- bytes32 public constant Q_HVN_USD_PAUSE_OPERATOR_ROLE =
- keccak256("Q_HVN_USD_PAUSE_OPERATOR_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 ("Qapture Safe Haven", "QHVN-USD");
- }
-
- /**
- * @dev AC role, owner of which can mint qHVNUSD token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return Q_HVN_USD_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn qHVNUSD token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return Q_HVN_USD_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause qHVNUSD token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return Q_HVN_USD_PAUSE_OPERATOR_ROLE;
- }
-
- /**
- * @inheritdoc mTokenPermissioned
- */
- function _greenlistedRole() internal pure override returns (bytes32) {
- return Q_HVN_USD_GREENLISTED_ROLE;
- }
-}
diff --git a/contracts/products/sGold/SGoldCustomAggregatorFeed.sol b/contracts/products/sGold/SGoldCustomAggregatorFeed.sol
deleted file mode 100644
index 4326157d..00000000
--- a/contracts/products/sGold/SGoldCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./SGoldMidasAccessControlRoles.sol";
-
-/**
- * @title SGoldCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for sGold,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract SGoldCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- SGoldMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return S_GOLD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/sGold/SGoldDataFeed.sol b/contracts/products/sGold/SGoldDataFeed.sol
deleted file mode 100644
index 89ca2adb..00000000
--- a/contracts/products/sGold/SGoldDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./SGoldMidasAccessControlRoles.sol";
-
-/**
- * @title SGoldDataFeed
- * @notice DataFeed for sGold product
- * @author RedDuck Software
- */
-contract SGoldDataFeed is DataFeed, SGoldMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return S_GOLD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/sGold/SGoldDepositVault.sol b/contracts/products/sGold/SGoldDepositVault.sol
deleted file mode 100644
index 9ebe8b58..00000000
--- a/contracts/products/sGold/SGoldDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./SGoldMidasAccessControlRoles.sol";
-
-/**
- * @title SGoldDepositVault
- * @notice Smart contract that handles sGold minting
- * @author RedDuck Software
- */
-contract SGoldDepositVault is DepositVault, SGoldMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return S_GOLD_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/sGold/SGoldMidasAccessControlRoles.sol b/contracts/products/sGold/SGoldMidasAccessControlRoles.sol
deleted file mode 100644
index 6df363a0..00000000
--- a/contracts/products/sGold/SGoldMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title SGoldMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for sGold contracts
- * @author RedDuck Software
- */
-abstract contract SGoldMidasAccessControlRoles {
- /**
- * @notice actor that can manage SGoldDepositVault
- */
- bytes32 public constant S_GOLD_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("S_GOLD_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage SGoldRedemptionVault
- */
- bytes32 public constant S_GOLD_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("S_GOLD_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage SGoldCustomAggregatorFeed and SGoldDataFeed
- */
- bytes32 public constant S_GOLD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("S_GOLD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/sGold/SGoldRedemptionVaultWithSwapper.sol b/contracts/products/sGold/SGoldRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 9468bff2..00000000
--- a/contracts/products/sGold/SGoldRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./SGoldMidasAccessControlRoles.sol";
-
-/**
- * @title SGoldRedemptionVaultWithSwapper
- * @notice Smart contract that handles sGold redemptions
- * @author RedDuck Software
- */
-contract SGoldRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- SGoldMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return S_GOLD_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/sGold/sGold.sol b/contracts/products/sGold/sGold.sol
deleted file mode 100644
index 3bcefc35..00000000
--- a/contracts/products/sGold/sGold.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title sGold
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract sGold is mToken {
- /**
- * @notice actor that can mint sGold
- */
- bytes32 public constant S_GOLD_MINT_OPERATOR_ROLE =
- keccak256("S_GOLD_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn sGold
- */
- bytes32 public constant S_GOLD_BURN_OPERATOR_ROLE =
- keccak256("S_GOLD_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause sGold
- */
- bytes32 public constant S_GOLD_PAUSE_OPERATOR_ROLE =
- keccak256("S_GOLD_PAUSE_OPERATOR_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 ("Suissequant Gold Yield", "sGold");
- }
-
- /**
- * @dev AC role, owner of which can mint sGold token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return S_GOLD_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn sGold token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return S_GOLD_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause sGold token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return S_GOLD_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/sLINJ/SLInjCustomAggregatorFeed.sol b/contracts/products/sLINJ/SLInjCustomAggregatorFeed.sol
deleted file mode 100644
index 97ccf1f7..00000000
--- a/contracts/products/sLINJ/SLInjCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./SLInjMidasAccessControlRoles.sol";
-
-/**
- * @title SLInjCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for sLINJ,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract SLInjCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- SLInjMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return SL_INJ_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/sLINJ/SLInjDataFeed.sol b/contracts/products/sLINJ/SLInjDataFeed.sol
deleted file mode 100644
index e2111018..00000000
--- a/contracts/products/sLINJ/SLInjDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./SLInjMidasAccessControlRoles.sol";
-
-/**
- * @title SLInjDataFeed
- * @notice DataFeed for sLINJ product
- * @author RedDuck Software
- */
-contract SLInjDataFeed is DataFeed, SLInjMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return SL_INJ_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/sLINJ/SLInjDepositVault.sol b/contracts/products/sLINJ/SLInjDepositVault.sol
deleted file mode 100644
index 0a59144b..00000000
--- a/contracts/products/sLINJ/SLInjDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./SLInjMidasAccessControlRoles.sol";
-
-/**
- * @title SLInjDepositVault
- * @notice Smart contract that handles sLINJ minting
- * @author RedDuck Software
- */
-contract SLInjDepositVault is DepositVault, SLInjMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return SL_INJ_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/sLINJ/SLInjMidasAccessControlRoles.sol b/contracts/products/sLINJ/SLInjMidasAccessControlRoles.sol
deleted file mode 100644
index e1ac857b..00000000
--- a/contracts/products/sLINJ/SLInjMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title SLInjMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for sLINJ contracts
- * @author RedDuck Software
- */
-abstract contract SLInjMidasAccessControlRoles {
- /**
- * @notice actor that can manage SLInjDepositVault
- */
- bytes32 public constant SL_INJ_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("SL_INJ_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage SLInjRedemptionVault
- */
- bytes32 public constant SL_INJ_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("SL_INJ_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage SLInjCustomAggregatorFeed and SLInjDataFeed
- */
- bytes32 public constant SL_INJ_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("SL_INJ_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/sLINJ/SLInjRedemptionVaultWithSwapper.sol b/contracts/products/sLINJ/SLInjRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 631f57c5..00000000
--- a/contracts/products/sLINJ/SLInjRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./SLInjMidasAccessControlRoles.sol";
-
-/**
- * @title SLInjRedemptionVaultWithSwapper
- * @notice Smart contract that handles sLINJ redemptions
- * @author RedDuck Software
- */
-contract SLInjRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- SLInjMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return SL_INJ_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/sLINJ/sLINJ.sol b/contracts/products/sLINJ/sLINJ.sol
deleted file mode 100644
index 2e18767a..00000000
--- a/contracts/products/sLINJ/sLINJ.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title sLINJ
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract sLINJ is mToken {
- /**
- * @notice actor that can mint sLINJ
- */
- bytes32 public constant SL_INJ_MINT_OPERATOR_ROLE =
- keccak256("SL_INJ_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn sLINJ
- */
- bytes32 public constant SL_INJ_BURN_OPERATOR_ROLE =
- keccak256("SL_INJ_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause sLINJ
- */
- bytes32 public constant SL_INJ_PAUSE_OPERATOR_ROLE =
- keccak256("SL_INJ_PAUSE_OPERATOR_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 ("INJ Loop Stake Vault", "sLINJ");
- }
-
- /**
- * @dev AC role, owner of which can mint sLINJ token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return SL_INJ_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn sLINJ token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return SL_INJ_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause sLINJ token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return SL_INJ_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/splUSD/SplUsdCustomAggregatorFeed.sol b/contracts/products/splUSD/SplUsdCustomAggregatorFeed.sol
deleted file mode 100644
index a68f0bc0..00000000
--- a/contracts/products/splUSD/SplUsdCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./SplUsdMidasAccessControlRoles.sol";
-
-/**
- * @title SplUsdCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for splUSD,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract SplUsdCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- SplUsdMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return SPL_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/splUSD/SplUsdDataFeed.sol b/contracts/products/splUSD/SplUsdDataFeed.sol
deleted file mode 100644
index f8920ebc..00000000
--- a/contracts/products/splUSD/SplUsdDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./SplUsdMidasAccessControlRoles.sol";
-
-/**
- * @title SplUsdDataFeed
- * @notice DataFeed for splUSD product
- * @author RedDuck Software
- */
-contract SplUsdDataFeed is DataFeed, SplUsdMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return SPL_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/splUSD/SplUsdDepositVault.sol b/contracts/products/splUSD/SplUsdDepositVault.sol
deleted file mode 100644
index 24f3b764..00000000
--- a/contracts/products/splUSD/SplUsdDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./SplUsdMidasAccessControlRoles.sol";
-
-/**
- * @title SplUsdDepositVault
- * @notice Smart contract that handles splUSD minting
- * @author RedDuck Software
- */
-contract SplUsdDepositVault is DepositVault, SplUsdMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return SPL_USD_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/splUSD/SplUsdMidasAccessControlRoles.sol b/contracts/products/splUSD/SplUsdMidasAccessControlRoles.sol
deleted file mode 100644
index a06865b4..00000000
--- a/contracts/products/splUSD/SplUsdMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title SplUsdMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for splUSD contracts
- * @author RedDuck Software
- */
-abstract contract SplUsdMidasAccessControlRoles {
- /**
- * @notice actor that can manage SplUsdDepositVault
- */
- bytes32 public constant SPL_USD_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("SPL_USD_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage SplUsdRedemptionVault
- */
- bytes32 public constant SPL_USD_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("SPL_USD_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage SplUsdCustomAggregatorFeed and SplUsdDataFeed
- */
- bytes32 public constant SPL_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("SPL_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/splUSD/SplUsdRedemptionVaultWithSwapper.sol b/contracts/products/splUSD/SplUsdRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 4b7ed3ac..00000000
--- a/contracts/products/splUSD/SplUsdRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./SplUsdMidasAccessControlRoles.sol";
-
-/**
- * @title SplUsdRedemptionVaultWithSwapper
- * @notice Smart contract that handles splUSD redemptions
- * @author RedDuck Software
- */
-contract SplUsdRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- SplUsdMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return SPL_USD_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/splUSD/splUSD.sol b/contracts/products/splUSD/splUSD.sol
deleted file mode 100644
index 7e0c22c3..00000000
--- a/contracts/products/splUSD/splUSD.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title splUSD
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract splUSD is mToken {
- /**
- * @notice actor that can mint splUSD
- */
- bytes32 public constant SPL_USD_MINT_OPERATOR_ROLE =
- keccak256("SPL_USD_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn splUSD
- */
- bytes32 public constant SPL_USD_BURN_OPERATOR_ROLE =
- keccak256("SPL_USD_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause splUSD
- */
- bytes32 public constant SPL_USD_PAUSE_OPERATOR_ROLE =
- keccak256("SPL_USD_PAUSE_OPERATOR_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 ("Staked Plasma USD", "splUSD");
- }
-
- /**
- * @dev AC role, owner of which can mint splUSD token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return SPL_USD_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn splUSD token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return SPL_USD_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause splUSD token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return SPL_USD_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/stockMarketTRBasisTrade/StockMarketTRBasisTradeCustomAggregatorFeed.sol b/contracts/products/stockMarketTRBasisTrade/StockMarketTRBasisTradeCustomAggregatorFeed.sol
deleted file mode 100644
index 3dd7eb35..00000000
--- a/contracts/products/stockMarketTRBasisTrade/StockMarketTRBasisTradeCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./StockMarketTRBasisTradeMidasAccessControlRoles.sol";
-
-/**
- * @title StockMarketTRBasisTradeCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for stockMarketTRBasisTrade,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract StockMarketTRBasisTradeCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- StockMarketTRBasisTradeMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return STOCK_MARKET_TR_BASIS_TRADE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/stockMarketTRBasisTrade/StockMarketTRBasisTradeDataFeed.sol b/contracts/products/stockMarketTRBasisTrade/StockMarketTRBasisTradeDataFeed.sol
deleted file mode 100644
index 2bd592e5..00000000
--- a/contracts/products/stockMarketTRBasisTrade/StockMarketTRBasisTradeDataFeed.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./StockMarketTRBasisTradeMidasAccessControlRoles.sol";
-
-/**
- * @title StockMarketTRBasisTradeDataFeed
- * @notice DataFeed for stockMarketTRBasisTrade product
- * @author RedDuck Software
- */
-contract StockMarketTRBasisTradeDataFeed is
- DataFeed,
- StockMarketTRBasisTradeMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return STOCK_MARKET_TR_BASIS_TRADE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/stockMarketTRBasisTrade/StockMarketTRBasisTradeDepositVault.sol b/contracts/products/stockMarketTRBasisTrade/StockMarketTRBasisTradeDepositVault.sol
deleted file mode 100644
index 68626fab..00000000
--- a/contracts/products/stockMarketTRBasisTrade/StockMarketTRBasisTradeDepositVault.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./StockMarketTRBasisTradeMidasAccessControlRoles.sol";
-
-/**
- * @title StockMarketTRBasisTradeDepositVault
- * @notice Smart contract that handles stockMarketTRBasisTrade minting
- * @author RedDuck Software
- */
-contract StockMarketTRBasisTradeDepositVault is
- DepositVault,
- StockMarketTRBasisTradeMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return STOCK_MARKET_TR_BASIS_TRADE_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/stockMarketTRBasisTrade/StockMarketTRBasisTradeMidasAccessControlRoles.sol b/contracts/products/stockMarketTRBasisTrade/StockMarketTRBasisTradeMidasAccessControlRoles.sol
deleted file mode 100644
index bacf33cb..00000000
--- a/contracts/products/stockMarketTRBasisTrade/StockMarketTRBasisTradeMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,32 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title StockMarketTRBasisTradeMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for stockMarketTRBasisTrade contracts
- * @author RedDuck Software
- */
-abstract contract StockMarketTRBasisTradeMidasAccessControlRoles {
- /**
- * @notice actor that can manage StockMarketTRBasisTradeDepositVault
- */
- bytes32
- public constant STOCK_MARKET_TR_BASIS_TRADE_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("STOCK_MARKET_TR_BASIS_TRADE_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage StockMarketTRBasisTradeRedemptionVault
- */
- bytes32
- public constant STOCK_MARKET_TR_BASIS_TRADE_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("STOCK_MARKET_TR_BASIS_TRADE_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage StockMarketTRBasisTradeCustomAggregatorFeed and StockMarketTRBasisTradeDataFeed
- */
- bytes32
- public constant STOCK_MARKET_TR_BASIS_TRADE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256(
- "STOCK_MARKET_TR_BASIS_TRADE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE"
- );
-}
diff --git a/contracts/products/stockMarketTRBasisTrade/StockMarketTRBasisTradeRedemptionVaultWithSwapper.sol b/contracts/products/stockMarketTRBasisTrade/StockMarketTRBasisTradeRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 95e965a3..00000000
--- a/contracts/products/stockMarketTRBasisTrade/StockMarketTRBasisTradeRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./StockMarketTRBasisTradeMidasAccessControlRoles.sol";
-
-/**
- * @title StockMarketTRBasisTradeRedemptionVaultWithSwapper
- * @notice Smart contract that handles stockMarketTRBasisTrade redemptions
- * @author RedDuck Software
- */
-contract StockMarketTRBasisTradeRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- StockMarketTRBasisTradeMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return STOCK_MARKET_TR_BASIS_TRADE_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/stockMarketTRBasisTrade/stockMarketTRBasisTrade.sol b/contracts/products/stockMarketTRBasisTrade/stockMarketTRBasisTrade.sol
deleted file mode 100644
index 70751f90..00000000
--- a/contracts/products/stockMarketTRBasisTrade/stockMarketTRBasisTrade.sol
+++ /dev/null
@@ -1,70 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title stockMarketTRBasisTrade
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract stockMarketTRBasisTrade is mToken {
- /**
- * @notice actor that can mint stockMarketTRBasisTrade
- */
- bytes32 public constant STOCK_MARKET_TR_BASIS_TRADE_MINT_OPERATOR_ROLE =
- keccak256("STOCK_MARKET_TR_BASIS_TRADE_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn stockMarketTRBasisTrade
- */
- bytes32 public constant STOCK_MARKET_TR_BASIS_TRADE_BURN_OPERATOR_ROLE =
- keccak256("STOCK_MARKET_TR_BASIS_TRADE_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause stockMarketTRBasisTrade
- */
- bytes32 public constant STOCK_MARKET_TR_BASIS_TRADE_PAUSE_OPERATOR_ROLE =
- keccak256("STOCK_MARKET_TR_BASIS_TRADE_PAUSE_OPERATOR_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 (
- "Morini StockMarketTRBasisTrade Vault",
- "StockMarketTRBasisTrade"
- );
- }
-
- /**
- * @dev AC role, owner of which can mint stockMarketTRBasisTrade token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return STOCK_MARKET_TR_BASIS_TRADE_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn stockMarketTRBasisTrade token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return STOCK_MARKET_TR_BASIS_TRADE_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause stockMarketTRBasisTrade token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return STOCK_MARKET_TR_BASIS_TRADE_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/tBTC/TBtcCustomAggregatorFeed.sol b/contracts/products/tBTC/TBtcCustomAggregatorFeed.sol
deleted file mode 100644
index 7030e53a..00000000
--- a/contracts/products/tBTC/TBtcCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./TBtcMidasAccessControlRoles.sol";
-
-/**
- * @title TBtcCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for tBTC,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract TBtcCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- TBtcMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return T_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/tBTC/TBtcDataFeed.sol b/contracts/products/tBTC/TBtcDataFeed.sol
deleted file mode 100644
index 8659ea20..00000000
--- a/contracts/products/tBTC/TBtcDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./TBtcMidasAccessControlRoles.sol";
-
-/**
- * @title TBtcDataFeed
- * @notice DataFeed for tBTC product
- * @author RedDuck Software
- */
-contract TBtcDataFeed is DataFeed, TBtcMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return T_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/tBTC/TBtcDepositVault.sol b/contracts/products/tBTC/TBtcDepositVault.sol
deleted file mode 100644
index cfd1180e..00000000
--- a/contracts/products/tBTC/TBtcDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./TBtcMidasAccessControlRoles.sol";
-
-/**
- * @title TBtcDepositVault
- * @notice Smart contract that handles tBTC minting
- * @author RedDuck Software
- */
-contract TBtcDepositVault is DepositVault, TBtcMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return T_BTC_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/tBTC/TBtcMidasAccessControlRoles.sol b/contracts/products/tBTC/TBtcMidasAccessControlRoles.sol
deleted file mode 100644
index b108394b..00000000
--- a/contracts/products/tBTC/TBtcMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title TBtcMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for tBTC contracts
- * @author RedDuck Software
- */
-abstract contract TBtcMidasAccessControlRoles {
- /**
- * @notice actor that can manage TBtcDepositVault
- */
- bytes32 public constant T_BTC_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("T_BTC_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage TBtcRedemptionVault
- */
- bytes32 public constant T_BTC_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("T_BTC_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage TBtcCustomAggregatorFeed and TBtcDataFeed
- */
- bytes32 public constant T_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("T_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/tBTC/TBtcRedemptionVaultWithSwapper.sol b/contracts/products/tBTC/TBtcRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 682950fb..00000000
--- a/contracts/products/tBTC/TBtcRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./TBtcMidasAccessControlRoles.sol";
-
-/**
- * @title TBtcRedemptionVaultWithSwapper
- * @notice Smart contract that handles tBTC redemptions
- * @author RedDuck Software
- */
-contract TBtcRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- TBtcMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return T_BTC_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/tBTC/tBTC.sol b/contracts/products/tBTC/tBTC.sol
deleted file mode 100644
index 06d7d437..00000000
--- a/contracts/products/tBTC/tBTC.sol
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-import "../../mToken.sol";
-
-/**
- * @title tBTC
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract tBTC is mToken {
- /**
- * @notice actor that can mint tBTC
- */
- bytes32 public constant T_BTC_MINT_OPERATOR_ROLE =
- keccak256("T_BTC_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn tBTC
- */
- bytes32 public constant T_BTC_BURN_OPERATOR_ROLE =
- keccak256("T_BTC_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause tBTC
- */
- bytes32 public constant T_BTC_PAUSE_OPERATOR_ROLE =
- keccak256("T_BTC_PAUSE_OPERATOR_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 ("Terminal WBTC", "tBTC");
- }
-
- /**
- * @dev AC role, owner of which can mint tBTC token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return T_BTC_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn tBTC token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return T_BTC_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause tBTC token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return T_BTC_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/tETH/TEthCustomAggregatorFeed.sol b/contracts/products/tETH/TEthCustomAggregatorFeed.sol
deleted file mode 100644
index 6b166603..00000000
--- a/contracts/products/tETH/TEthCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./TEthMidasAccessControlRoles.sol";
-
-/**
- * @title TEthCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for tETH,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract TEthCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- TEthMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return T_ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/tETH/TEthDataFeed.sol b/contracts/products/tETH/TEthDataFeed.sol
deleted file mode 100644
index b954f09a..00000000
--- a/contracts/products/tETH/TEthDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./TEthMidasAccessControlRoles.sol";
-
-/**
- * @title TEthDataFeed
- * @notice DataFeed for tETH product
- * @author RedDuck Software
- */
-contract TEthDataFeed is DataFeed, TEthMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return T_ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/tETH/TEthDepositVault.sol b/contracts/products/tETH/TEthDepositVault.sol
deleted file mode 100644
index 18b2a9ed..00000000
--- a/contracts/products/tETH/TEthDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./TEthMidasAccessControlRoles.sol";
-
-/**
- * @title TEthDepositVault
- * @notice Smart contract that handles tETH minting
- * @author RedDuck Software
- */
-contract TEthDepositVault is DepositVault, TEthMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return T_ETH_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/tETH/TEthMidasAccessControlRoles.sol b/contracts/products/tETH/TEthMidasAccessControlRoles.sol
deleted file mode 100644
index 6ca27744..00000000
--- a/contracts/products/tETH/TEthMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title TEthMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for tETH contracts
- * @author RedDuck Software
- */
-abstract contract TEthMidasAccessControlRoles {
- /**
- * @notice actor that can manage TEthDepositVault
- */
- bytes32 public constant T_ETH_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("T_ETH_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage TEthRedemptionVault
- */
- bytes32 public constant T_ETH_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("T_ETH_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage TEthCustomAggregatorFeed and TEthDataFeed
- */
- bytes32 public constant T_ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("T_ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/tETH/TEthRedemptionVaultWithSwapper.sol b/contracts/products/tETH/TEthRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 65f6f942..00000000
--- a/contracts/products/tETH/TEthRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./TEthMidasAccessControlRoles.sol";
-
-/**
- * @title TEthRedemptionVaultWithSwapper
- * @notice Smart contract that handles tETH redemptions
- * @author RedDuck Software
- */
-contract TEthRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- TEthMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return T_ETH_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/tETH/tETH.sol b/contracts/products/tETH/tETH.sol
deleted file mode 100644
index 49c0814a..00000000
--- a/contracts/products/tETH/tETH.sol
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-import "../../mToken.sol";
-
-/**
- * @title tETH
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract tETH is mToken {
- /**
- * @notice actor that can mint tETH
- */
- bytes32 public constant T_ETH_MINT_OPERATOR_ROLE =
- keccak256("T_ETH_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn tETH
- */
- bytes32 public constant T_ETH_BURN_OPERATOR_ROLE =
- keccak256("T_ETH_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause tETH
- */
- bytes32 public constant T_ETH_PAUSE_OPERATOR_ROLE =
- keccak256("T_ETH_PAUSE_OPERATOR_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 ("Terminal WETH", "tETH");
- }
-
- /**
- * @dev AC role, owner of which can mint tETH token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return T_ETH_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn tETH token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return T_ETH_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause tETH token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return T_ETH_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/tUSDe/TUsdeCustomAggregatorFeed.sol b/contracts/products/tUSDe/TUsdeCustomAggregatorFeed.sol
deleted file mode 100644
index 9028869b..00000000
--- a/contracts/products/tUSDe/TUsdeCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./TUsdeMidasAccessControlRoles.sol";
-
-/**
- * @title TUsdeCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for tUSDe,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract TUsdeCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- TUsdeMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return T_USDE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/tUSDe/TUsdeDataFeed.sol b/contracts/products/tUSDe/TUsdeDataFeed.sol
deleted file mode 100644
index e302a64d..00000000
--- a/contracts/products/tUSDe/TUsdeDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./TUsdeMidasAccessControlRoles.sol";
-
-/**
- * @title TUsdeDataFeed
- * @notice DataFeed for tUSDe product
- * @author RedDuck Software
- */
-contract TUsdeDataFeed is DataFeed, TUsdeMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return T_USDE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/tUSDe/TUsdeDepositVault.sol b/contracts/products/tUSDe/TUsdeDepositVault.sol
deleted file mode 100644
index 0829b461..00000000
--- a/contracts/products/tUSDe/TUsdeDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./TUsdeMidasAccessControlRoles.sol";
-
-/**
- * @title TUsdeDepositVault
- * @notice Smart contract that handles tUSDe minting
- * @author RedDuck Software
- */
-contract TUsdeDepositVault is DepositVault, TUsdeMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return T_USDE_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/tUSDe/TUsdeMidasAccessControlRoles.sol b/contracts/products/tUSDe/TUsdeMidasAccessControlRoles.sol
deleted file mode 100644
index b6feb409..00000000
--- a/contracts/products/tUSDe/TUsdeMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title TUsdeMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for tUSDe contracts
- * @author RedDuck Software
- */
-abstract contract TUsdeMidasAccessControlRoles {
- /**
- * @notice actor that can manage TUsdeDepositVault
- */
- bytes32 public constant T_USDE_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("T_USDE_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage TUsdeRedemptionVault
- */
- bytes32 public constant T_USDE_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("T_USDE_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage TUsdeCustomAggregatorFeed and TUsdeDataFeed
- */
- bytes32 public constant T_USDE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("T_USDE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/tUSDe/TUsdeRedemptionVaultWithSwapper.sol b/contracts/products/tUSDe/TUsdeRedemptionVaultWithSwapper.sol
deleted file mode 100644
index b3a9c7c7..00000000
--- a/contracts/products/tUSDe/TUsdeRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./TUsdeMidasAccessControlRoles.sol";
-
-/**
- * @title TUsdeRedemptionVaultWithSwapper
- * @notice Smart contract that handles tUSDe redemptions
- * @author RedDuck Software
- */
-contract TUsdeRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- TUsdeMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return T_USDE_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/tUSDe/tUSDe.sol b/contracts/products/tUSDe/tUSDe.sol
deleted file mode 100644
index 1e08d6b2..00000000
--- a/contracts/products/tUSDe/tUSDe.sol
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-import "../../mToken.sol";
-
-/**
- * @title tUSDe
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract tUSDe is mToken {
- /**
- * @notice actor that can mint tUSDe
- */
- bytes32 public constant T_USDE_MINT_OPERATOR_ROLE =
- keccak256("T_USDE_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn tUSDe
- */
- bytes32 public constant T_USDE_BURN_OPERATOR_ROLE =
- keccak256("T_USDE_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause tUSDe
- */
- bytes32 public constant T_USDE_PAUSE_OPERATOR_ROLE =
- keccak256("T_USDE_PAUSE_OPERATOR_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 ("Terminal USDe", "tUSDe");
- }
-
- /**
- * @dev AC role, owner of which can mint tUSDe token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return T_USDE_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn tUSDe token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return T_USDE_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause tUSDe token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return T_USDE_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/tacTON/TacTonCustomAggregatorFeed.sol b/contracts/products/tacTON/TacTonCustomAggregatorFeed.sol
deleted file mode 100644
index e61434bf..00000000
--- a/contracts/products/tacTON/TacTonCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./TacTonMidasAccessControlRoles.sol";
-
-/**
- * @title TacTonCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for tacTON,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract TacTonCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- TacTonMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return TAC_TON_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/tacTON/TacTonDataFeed.sol b/contracts/products/tacTON/TacTonDataFeed.sol
deleted file mode 100644
index 6a6090b9..00000000
--- a/contracts/products/tacTON/TacTonDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./TacTonMidasAccessControlRoles.sol";
-
-/**
- * @title TacTonDataFeed
- * @notice DataFeed for tacTON product
- * @author RedDuck Software
- */
-contract TacTonDataFeed is DataFeed, TacTonMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return TAC_TON_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/tacTON/TacTonDepositVault.sol b/contracts/products/tacTON/TacTonDepositVault.sol
deleted file mode 100644
index 6e377917..00000000
--- a/contracts/products/tacTON/TacTonDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./TacTonMidasAccessControlRoles.sol";
-
-/**
- * @title TacTonDepositVault
- * @notice Smart contract that handles tacTON minting
- * @author RedDuck Software
- */
-contract TacTonDepositVault is DepositVault, TacTonMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return TAC_TON_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/tacTON/TacTonMidasAccessControlRoles.sol b/contracts/products/tacTON/TacTonMidasAccessControlRoles.sol
deleted file mode 100644
index d4073452..00000000
--- a/contracts/products/tacTON/TacTonMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title TacTonMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for tacTON contracts
- * @author RedDuck Software
- */
-abstract contract TacTonMidasAccessControlRoles {
- /**
- * @notice actor that can manage TacTonDepositVault
- */
- bytes32 public constant TAC_TON_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("TAC_TON_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage TacTonRedemptionVault
- */
- bytes32 public constant TAC_TON_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("TAC_TON_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage TacTonCustomAggregatorFeed and TacTonDataFeed
- */
- bytes32 public constant TAC_TON_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("TAC_TON_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/tacTON/TacTonRedemptionVaultWithSwapper.sol b/contracts/products/tacTON/TacTonRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 5c4f512d..00000000
--- a/contracts/products/tacTON/TacTonRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./TacTonMidasAccessControlRoles.sol";
-
-/**
- * @title TacTonRedemptionVaultWithSwapper
- * @notice Smart contract that handles tacTON redemptions
- * @author RedDuck Software
- */
-contract TacTonRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- TacTonMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return TAC_TON_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/tacTON/tacTON.sol b/contracts/products/tacTON/tacTON.sol
deleted file mode 100644
index c2c2daec..00000000
--- a/contracts/products/tacTON/tacTON.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title tacTON
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract tacTON is mToken {
- /**
- * @notice actor that can mint tacTON
- */
- bytes32 public constant TAC_TON_MINT_OPERATOR_ROLE =
- keccak256("TAC_TON_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn tacTON
- */
- bytes32 public constant TAC_TON_BURN_OPERATOR_ROLE =
- keccak256("TAC_TON_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause tacTON
- */
- bytes32 public constant TAC_TON_PAUSE_OPERATOR_ROLE =
- keccak256("TAC_TON_PAUSE_OPERATOR_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 ("tacTON", "tacTON");
- }
-
- /**
- * @dev AC role, owner of which can mint tacTON token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return TAC_TON_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn tacTON token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return TAC_TON_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause tacTON token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return TAC_TON_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/turtlePST/TurtlePstCustomAggregatorFeed.sol b/contracts/products/turtlePST/TurtlePstCustomAggregatorFeed.sol
deleted file mode 100644
index 4761d652..00000000
--- a/contracts/products/turtlePST/TurtlePstCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./TurtlePstMidasAccessControlRoles.sol";
-
-/**
- * @title TurtlePstCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for turtlePST,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract TurtlePstCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- TurtlePstMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return TURTLE_PST_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/turtlePST/TurtlePstDataFeed.sol b/contracts/products/turtlePST/TurtlePstDataFeed.sol
deleted file mode 100644
index 7155a0da..00000000
--- a/contracts/products/turtlePST/TurtlePstDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./TurtlePstMidasAccessControlRoles.sol";
-
-/**
- * @title TurtlePstDataFeed
- * @notice DataFeed for turtlePST product
- * @author RedDuck Software
- */
-contract TurtlePstDataFeed is DataFeed, TurtlePstMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return TURTLE_PST_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/turtlePST/TurtlePstDepositVault.sol b/contracts/products/turtlePST/TurtlePstDepositVault.sol
deleted file mode 100644
index 95b67345..00000000
--- a/contracts/products/turtlePST/TurtlePstDepositVault.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./TurtlePstMidasAccessControlRoles.sol";
-
-/**
- * @title TurtlePstDepositVault
- * @notice Smart contract that handles turtlePST minting
- * @author RedDuck Software
- */
-contract TurtlePstDepositVault is
- DepositVault,
- TurtlePstMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return TURTLE_PST_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/turtlePST/TurtlePstMidasAccessControlRoles.sol b/contracts/products/turtlePST/TurtlePstMidasAccessControlRoles.sol
deleted file mode 100644
index 1db8f66a..00000000
--- a/contracts/products/turtlePST/TurtlePstMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title TurtlePstMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for turtlePST contracts
- * @author RedDuck Software
- */
-abstract contract TurtlePstMidasAccessControlRoles {
- /**
- * @notice actor that can manage TurtlePstDepositVault
- */
- bytes32 public constant TURTLE_PST_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("TURTLE_PST_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage TurtlePstRedemptionVault
- */
- bytes32 public constant TURTLE_PST_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("TURTLE_PST_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage TurtlePstCustomAggregatorFeed and TurtlePstDataFeed
- */
- bytes32 public constant TURTLE_PST_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("TURTLE_PST_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/turtlePST/TurtlePstRedemptionVaultWithSwapper.sol b/contracts/products/turtlePST/TurtlePstRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 3469976d..00000000
--- a/contracts/products/turtlePST/TurtlePstRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./TurtlePstMidasAccessControlRoles.sol";
-
-/**
- * @title TurtlePstRedemptionVaultWithSwapper
- * @notice Smart contract that handles turtlePST redemptions
- * @author RedDuck Software
- */
-contract TurtlePstRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- TurtlePstMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return TURTLE_PST_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/turtlePST/turtlePST.sol b/contracts/products/turtlePST/turtlePST.sol
deleted file mode 100644
index 5d1e6752..00000000
--- a/contracts/products/turtlePST/turtlePST.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title turtlePST
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract turtlePST is mToken {
- /**
- * @notice actor that can mint turtlePST
- */
- bytes32 public constant TURTLE_PST_MINT_OPERATOR_ROLE =
- keccak256("TURTLE_PST_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn turtlePST
- */
- bytes32 public constant TURTLE_PST_BURN_OPERATOR_ROLE =
- keccak256("TURTLE_PST_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause turtlePST
- */
- bytes32 public constant TURTLE_PST_PAUSE_OPERATOR_ROLE =
- keccak256("TURTLE_PST_PAUSE_OPERATOR_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 ("Turtle Huma PST Vault", "turtlePST");
- }
-
- /**
- * @dev AC role, owner of which can mint turtlePST token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return TURTLE_PST_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn turtlePST token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return TURTLE_PST_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause turtlePST token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return TURTLE_PST_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/wNLP/WNlpCustomAggregatorFeed.sol b/contracts/products/wNLP/WNlpCustomAggregatorFeed.sol
deleted file mode 100644
index 95243a6a..00000000
--- a/contracts/products/wNLP/WNlpCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./WNlpMidasAccessControlRoles.sol";
-
-/**
- * @title WNlpCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for wNLP,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract WNlpCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- WNlpMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return W_NLP_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/wNLP/WNlpDataFeed.sol b/contracts/products/wNLP/WNlpDataFeed.sol
deleted file mode 100644
index 9c96d64c..00000000
--- a/contracts/products/wNLP/WNlpDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./WNlpMidasAccessControlRoles.sol";
-
-/**
- * @title WNlpDataFeed
- * @notice DataFeed for wNLP product
- * @author RedDuck Software
- */
-contract WNlpDataFeed is DataFeed, WNlpMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return W_NLP_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/wNLP/WNlpDepositVault.sol b/contracts/products/wNLP/WNlpDepositVault.sol
deleted file mode 100644
index 43f4dae5..00000000
--- a/contracts/products/wNLP/WNlpDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./WNlpMidasAccessControlRoles.sol";
-
-/**
- * @title WNlpDepositVault
- * @notice Smart contract that handles wNLP minting
- * @author RedDuck Software
- */
-contract WNlpDepositVault is DepositVault, WNlpMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return W_NLP_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/wNLP/WNlpMidasAccessControlRoles.sol b/contracts/products/wNLP/WNlpMidasAccessControlRoles.sol
deleted file mode 100644
index ea05d1f3..00000000
--- a/contracts/products/wNLP/WNlpMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title WNlpMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for wNLP contracts
- * @author RedDuck Software
- */
-abstract contract WNlpMidasAccessControlRoles {
- /**
- * @notice actor that can manage WNlpDepositVault
- */
- bytes32 public constant W_NLP_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("W_NLP_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage WNlpRedemptionVault
- */
- bytes32 public constant W_NLP_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("W_NLP_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage WNlpCustomAggregatorFeed and WNlpDataFeed
- */
- bytes32 public constant W_NLP_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("W_NLP_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/wNLP/WNlpRedemptionVaultWithSwapper.sol b/contracts/products/wNLP/WNlpRedemptionVaultWithSwapper.sol
deleted file mode 100644
index b9a3c7a7..00000000
--- a/contracts/products/wNLP/WNlpRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./WNlpMidasAccessControlRoles.sol";
-
-/**
- * @title WNlpRedemptionVaultWithSwapper
- * @notice Smart contract that handles wNLP redemptions
- * @author RedDuck Software
- */
-contract WNlpRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- WNlpMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return W_NLP_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/wNLP/wNLP.sol b/contracts/products/wNLP/wNLP.sol
deleted file mode 100644
index 362b0aaf..00000000
--- a/contracts/products/wNLP/wNLP.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title wNLP
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract wNLP is mToken {
- /**
- * @notice actor that can mint wNLP
- */
- bytes32 public constant W_NLP_MINT_OPERATOR_ROLE =
- keccak256("W_NLP_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn wNLP
- */
- bytes32 public constant W_NLP_BURN_OPERATOR_ROLE =
- keccak256("W_NLP_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause wNLP
- */
- bytes32 public constant W_NLP_PAUSE_OPERATOR_ROLE =
- keccak256("W_NLP_PAUSE_OPERATOR_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 ("Nunch wNLP", "wNLP");
- }
-
- /**
- * @dev AC role, owner of which can mint wNLP token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return W_NLP_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn wNLP token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return W_NLP_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause wNLP token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return W_NLP_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/wVLP/WVLPCustomAggregatorFeed.sol b/contracts/products/wVLP/WVLPCustomAggregatorFeed.sol
deleted file mode 100644
index 683e7fef..00000000
--- a/contracts/products/wVLP/WVLPCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./WVLPMidasAccessControlRoles.sol";
-
-/**
- * @title WVLPCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for wVLP,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract WVLPCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- WVLPMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return W_VLP_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/wVLP/WVLPDataFeed.sol b/contracts/products/wVLP/WVLPDataFeed.sol
deleted file mode 100644
index ebf61818..00000000
--- a/contracts/products/wVLP/WVLPDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./WVLPMidasAccessControlRoles.sol";
-
-/**
- * @title WVLPDataFeed
- * @notice DataFeed for wVLP product
- * @author RedDuck Software
- */
-contract WVLPDataFeed is DataFeed, WVLPMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return W_VLP_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/wVLP/WVLPDepositVault.sol b/contracts/products/wVLP/WVLPDepositVault.sol
deleted file mode 100644
index bc83903b..00000000
--- a/contracts/products/wVLP/WVLPDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./WVLPMidasAccessControlRoles.sol";
-
-/**
- * @title WVLPDepositVault
- * @notice Smart contract that handles wVLP minting
- * @author RedDuck Software
- */
-contract WVLPDepositVault is DepositVault, WVLPMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return W_VLP_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/wVLP/WVLPMidasAccessControlRoles.sol b/contracts/products/wVLP/WVLPMidasAccessControlRoles.sol
deleted file mode 100644
index c41a48ee..00000000
--- a/contracts/products/wVLP/WVLPMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title WVLPMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for wVLP contracts
- * @author RedDuck Software
- */
-abstract contract WVLPMidasAccessControlRoles {
- /**
- * @notice actor that can manage WVLPDepositVault
- */
- bytes32 public constant W_VLP_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("W_VLP_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage WVLPRedemptionVault
- */
- bytes32 public constant W_VLP_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("W_VLP_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage WVLPCustomAggregatorFeed and WVLPDataFeed
- */
- bytes32 public constant W_VLP_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("W_VLP_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/wVLP/WVLPRedemptionVaultWithSwapper.sol b/contracts/products/wVLP/WVLPRedemptionVaultWithSwapper.sol
deleted file mode 100644
index f018f15a..00000000
--- a/contracts/products/wVLP/WVLPRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./WVLPMidasAccessControlRoles.sol";
-
-/**
- * @title WVLPRedemptionVaultWithSwapper
- * @notice Smart contract that handles wVLP redemptions
- * @author RedDuck Software
- */
-contract WVLPRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- WVLPMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return W_VLP_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/wVLP/wVLP.sol b/contracts/products/wVLP/wVLP.sol
deleted file mode 100644
index 3d1185c3..00000000
--- a/contracts/products/wVLP/wVLP.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title wVLP
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract wVLP is mToken {
- /**
- * @notice actor that can mint wVLP
- */
- bytes32 public constant W_VLP_MINT_OPERATOR_ROLE =
- keccak256("W_VLP_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn wVLP
- */
- bytes32 public constant W_VLP_BURN_OPERATOR_ROLE =
- keccak256("W_VLP_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause wVLP
- */
- bytes32 public constant W_VLP_PAUSE_OPERATOR_ROLE =
- keccak256("W_VLP_PAUSE_OPERATOR_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 ("Hyperbeat VLP", "wVLP");
- }
-
- /**
- * @dev AC role, owner of which can mint wVLP token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return W_VLP_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn wVLP token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return W_VLP_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause wVLP token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return W_VLP_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/weEUR/WeEurCustomAggregatorFeed.sol b/contracts/products/weEUR/WeEurCustomAggregatorFeed.sol
deleted file mode 100644
index 83fdf77c..00000000
--- a/contracts/products/weEUR/WeEurCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./WeEurMidasAccessControlRoles.sol";
-
-/**
- * @title WeEurCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for weEUR,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract WeEurCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- WeEurMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return WE_EUR_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/weEUR/WeEurDataFeed.sol b/contracts/products/weEUR/WeEurDataFeed.sol
deleted file mode 100644
index e2ae7ac6..00000000
--- a/contracts/products/weEUR/WeEurDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./WeEurMidasAccessControlRoles.sol";
-
-/**
- * @title WeEurDataFeed
- * @notice DataFeed for weEUR product
- * @author RedDuck Software
- */
-contract WeEurDataFeed is DataFeed, WeEurMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return WE_EUR_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/weEUR/WeEurDepositVault.sol b/contracts/products/weEUR/WeEurDepositVault.sol
deleted file mode 100644
index e678d59f..00000000
--- a/contracts/products/weEUR/WeEurDepositVault.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./WeEurMidasAccessControlRoles.sol";
-
-/**
- * @title WeEurDepositVault
- * @notice Smart contract that handles weEUR minting
- * @author RedDuck Software
- */
-contract WeEurDepositVault is DepositVault, WeEurMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return WE_EUR_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/weEUR/WeEurMidasAccessControlRoles.sol b/contracts/products/weEUR/WeEurMidasAccessControlRoles.sol
deleted file mode 100644
index 67882d53..00000000
--- a/contracts/products/weEUR/WeEurMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title WeEurMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for weEUR contracts
- * @author RedDuck Software
- */
-abstract contract WeEurMidasAccessControlRoles {
- /**
- * @notice actor that can manage WeEurDepositVault
- */
- bytes32 public constant WE_EUR_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("WE_EUR_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage WeEurRedemptionVault
- */
- bytes32 public constant WE_EUR_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("WE_EUR_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage WeEurCustomAggregatorFeed and WeEurDataFeed
- */
- bytes32 public constant WE_EUR_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("WE_EUR_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/weEUR/WeEurRedemptionVaultWithSwapper.sol b/contracts/products/weEUR/WeEurRedemptionVaultWithSwapper.sol
deleted file mode 100644
index d358c439..00000000
--- a/contracts/products/weEUR/WeEurRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./WeEurMidasAccessControlRoles.sol";
-
-/**
- * @title WeEurRedemptionVaultWithSwapper
- * @notice Smart contract that handles weEUR redemptions
- * @author RedDuck Software
- */
-contract WeEurRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- WeEurMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return WE_EUR_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/weEUR/weEUR.sol b/contracts/products/weEUR/weEUR.sol
deleted file mode 100644
index 8b34ef47..00000000
--- a/contracts/products/weEUR/weEUR.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title weEUR
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract weEUR is mToken {
- /**
- * @notice actor that can mint weEUR
- */
- bytes32 public constant WE_EUR_MINT_OPERATOR_ROLE =
- keccak256("WE_EUR_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn weEUR
- */
- bytes32 public constant WE_EUR_BURN_OPERATOR_ROLE =
- keccak256("WE_EUR_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause weEUR
- */
- bytes32 public constant WE_EUR_PAUSE_OPERATOR_ROLE =
- keccak256("WE_EUR_PAUSE_OPERATOR_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 ("Liquid Euro", "weEUR");
- }
-
- /**
- * @dev AC role, owner of which can mint weEUR token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return WE_EUR_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn weEUR token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return WE_EUR_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause weEUR token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return WE_EUR_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/zeroGBTCV/ZeroGBtcvCustomAggregatorFeed.sol b/contracts/products/zeroGBTCV/ZeroGBtcvCustomAggregatorFeed.sol
deleted file mode 100644
index 9c956568..00000000
--- a/contracts/products/zeroGBTCV/ZeroGBtcvCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./ZeroGBtcvMidasAccessControlRoles.sol";
-
-/**
- * @title ZeroGBtcvCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for zeroGBTCV,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract ZeroGBtcvCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- ZeroGBtcvMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return ZEROG_BTCV_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/zeroGBTCV/ZeroGBtcvDataFeed.sol b/contracts/products/zeroGBTCV/ZeroGBtcvDataFeed.sol
deleted file mode 100644
index a0cade96..00000000
--- a/contracts/products/zeroGBTCV/ZeroGBtcvDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./ZeroGBtcvMidasAccessControlRoles.sol";
-
-/**
- * @title ZeroGBtcvDataFeed
- * @notice DataFeed for zeroGBTCV product
- * @author RedDuck Software
- */
-contract ZeroGBtcvDataFeed is DataFeed, ZeroGBtcvMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return ZEROG_BTCV_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/zeroGBTCV/ZeroGBtcvDepositVault.sol b/contracts/products/zeroGBTCV/ZeroGBtcvDepositVault.sol
deleted file mode 100644
index 6a676629..00000000
--- a/contracts/products/zeroGBTCV/ZeroGBtcvDepositVault.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./ZeroGBtcvMidasAccessControlRoles.sol";
-
-/**
- * @title ZeroGBtcvDepositVault
- * @notice Smart contract that handles zeroGBTCV minting
- * @author RedDuck Software
- */
-contract ZeroGBtcvDepositVault is
- DepositVault,
- ZeroGBtcvMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return ZEROG_BTCV_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/zeroGBTCV/ZeroGBtcvMidasAccessControlRoles.sol b/contracts/products/zeroGBTCV/ZeroGBtcvMidasAccessControlRoles.sol
deleted file mode 100644
index dff21f5a..00000000
--- a/contracts/products/zeroGBTCV/ZeroGBtcvMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title ZeroGBtcvMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for zeroGBTCV contracts
- * @author RedDuck Software
- */
-abstract contract ZeroGBtcvMidasAccessControlRoles {
- /**
- * @notice actor that can manage ZeroGBtcvDepositVault
- */
- bytes32 public constant ZEROG_BTCV_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("ZEROG_BTCV_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage ZeroGBtcvRedemptionVault
- */
- bytes32 public constant ZEROG_BTCV_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("ZEROG_BTCV_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage ZeroGBtcvCustomAggregatorFeed and ZeroGBtcvDataFeed
- */
- bytes32 public constant ZEROG_BTCV_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("ZEROG_BTCV_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/zeroGBTCV/ZeroGBtcvRedemptionVaultWithSwapper.sol b/contracts/products/zeroGBTCV/ZeroGBtcvRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 784b6138..00000000
--- a/contracts/products/zeroGBTCV/ZeroGBtcvRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./ZeroGBtcvMidasAccessControlRoles.sol";
-
-/**
- * @title ZeroGBtcvRedemptionVaultWithSwapper
- * @notice Smart contract that handles zeroGBTCV redemptions
- * @author RedDuck Software
- */
-contract ZeroGBtcvRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- ZeroGBtcvMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return ZEROG_BTCV_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/zeroGBTCV/zeroGBTCV.sol b/contracts/products/zeroGBTCV/zeroGBTCV.sol
deleted file mode 100644
index bfac1708..00000000
--- a/contracts/products/zeroGBTCV/zeroGBTCV.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title zeroGBTCV
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract zeroGBTCV is mToken {
- /**
- * @notice actor that can mint zeroGBTCV
- */
- bytes32 public constant ZEROG_BTCV_MINT_OPERATOR_ROLE =
- keccak256("ZEROG_BTCV_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn zeroGBTCV
- */
- bytes32 public constant ZEROG_BTCV_BURN_OPERATOR_ROLE =
- keccak256("ZEROG_BTCV_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause zeroGBTCV
- */
- bytes32 public constant ZEROG_BTCV_PAUSE_OPERATOR_ROLE =
- keccak256("ZEROG_BTCV_PAUSE_OPERATOR_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 ("0G BTC Vault", "0gBTCV");
- }
-
- /**
- * @dev AC role, owner of which can mint zeroGBTCV token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return ZEROG_BTCV_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn zeroGBTCV token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return ZEROG_BTCV_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause zeroGBTCV token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return ZEROG_BTCV_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/zeroGETHV/ZeroGEthvCustomAggregatorFeed.sol b/contracts/products/zeroGETHV/ZeroGEthvCustomAggregatorFeed.sol
deleted file mode 100644
index edea5652..00000000
--- a/contracts/products/zeroGETHV/ZeroGEthvCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./ZeroGEthvMidasAccessControlRoles.sol";
-
-/**
- * @title ZeroGEthvCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for zeroGETHV,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract ZeroGEthvCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- ZeroGEthvMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return ZEROG_ETHV_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/zeroGETHV/ZeroGEthvDataFeed.sol b/contracts/products/zeroGETHV/ZeroGEthvDataFeed.sol
deleted file mode 100644
index 692b0e03..00000000
--- a/contracts/products/zeroGETHV/ZeroGEthvDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./ZeroGEthvMidasAccessControlRoles.sol";
-
-/**
- * @title ZeroGEthvDataFeed
- * @notice DataFeed for zeroGETHV product
- * @author RedDuck Software
- */
-contract ZeroGEthvDataFeed is DataFeed, ZeroGEthvMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return ZEROG_ETHV_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/zeroGETHV/ZeroGEthvDepositVault.sol b/contracts/products/zeroGETHV/ZeroGEthvDepositVault.sol
deleted file mode 100644
index 848f9dc0..00000000
--- a/contracts/products/zeroGETHV/ZeroGEthvDepositVault.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./ZeroGEthvMidasAccessControlRoles.sol";
-
-/**
- * @title ZeroGEthvDepositVault
- * @notice Smart contract that handles zeroGETHV minting
- * @author RedDuck Software
- */
-contract ZeroGEthvDepositVault is
- DepositVault,
- ZeroGEthvMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return ZEROG_ETHV_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/zeroGETHV/ZeroGEthvMidasAccessControlRoles.sol b/contracts/products/zeroGETHV/ZeroGEthvMidasAccessControlRoles.sol
deleted file mode 100644
index 015393d0..00000000
--- a/contracts/products/zeroGETHV/ZeroGEthvMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title ZeroGEthvMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for zeroGETHV contracts
- * @author RedDuck Software
- */
-abstract contract ZeroGEthvMidasAccessControlRoles {
- /**
- * @notice actor that can manage ZeroGEthvDepositVault
- */
- bytes32 public constant ZEROG_ETHV_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("ZEROG_ETHV_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage ZeroGEthvRedemptionVault
- */
- bytes32 public constant ZEROG_ETHV_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("ZEROG_ETHV_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage ZeroGEthvCustomAggregatorFeed and ZeroGEthvDataFeed
- */
- bytes32 public constant ZEROG_ETHV_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("ZEROG_ETHV_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/zeroGETHV/ZeroGEthvRedemptionVaultWithSwapper.sol b/contracts/products/zeroGETHV/ZeroGEthvRedemptionVaultWithSwapper.sol
deleted file mode 100644
index 3f67d2a8..00000000
--- a/contracts/products/zeroGETHV/ZeroGEthvRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./ZeroGEthvMidasAccessControlRoles.sol";
-
-/**
- * @title ZeroGEthvRedemptionVaultWithSwapper
- * @notice Smart contract that handles zeroGETHV redemptions
- * @author RedDuck Software
- */
-contract ZeroGEthvRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- ZeroGEthvMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return ZEROG_ETHV_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/zeroGETHV/zeroGETHV.sol b/contracts/products/zeroGETHV/zeroGETHV.sol
deleted file mode 100644
index 1ed2a5b4..00000000
--- a/contracts/products/zeroGETHV/zeroGETHV.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title zeroGETHV
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract zeroGETHV is mToken {
- /**
- * @notice actor that can mint zeroGETHV
- */
- bytes32 public constant ZEROG_ETHV_MINT_OPERATOR_ROLE =
- keccak256("ZEROG_ETHV_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn zeroGETHV
- */
- bytes32 public constant ZEROG_ETHV_BURN_OPERATOR_ROLE =
- keccak256("ZEROG_ETHV_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause zeroGETHV
- */
- bytes32 public constant ZEROG_ETHV_PAUSE_OPERATOR_ROLE =
- keccak256("ZEROG_ETHV_PAUSE_OPERATOR_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 ("0G ETH Vault", "0gETHV");
- }
-
- /**
- * @dev AC role, owner of which can mint zeroGETHV token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return ZEROG_ETHV_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn zeroGETHV token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return ZEROG_ETHV_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause zeroGETHV token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return ZEROG_ETHV_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/products/zeroGUSDV/ZeroGUsdvCustomAggregatorFeed.sol b/contracts/products/zeroGUSDV/ZeroGUsdvCustomAggregatorFeed.sol
deleted file mode 100644
index e431100d..00000000
--- a/contracts/products/zeroGUSDV/ZeroGUsdvCustomAggregatorFeed.sol
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
-import "./ZeroGUsdvMidasAccessControlRoles.sol";
-
-/**
- * @title ZeroGUsdvCustomAggregatorFeed
- * @notice AggregatorV3 compatible feed for zeroGUSDV,
- * where price is submitted manually by feed admins
- * @author RedDuck Software
- */
-contract ZeroGUsdvCustomAggregatorFeed is
- CustomAggregatorV3CompatibleFeed,
- ZeroGUsdvMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc CustomAggregatorV3CompatibleFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return ZEROG_USDV_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/zeroGUSDV/ZeroGUsdvDataFeed.sol b/contracts/products/zeroGUSDV/ZeroGUsdvDataFeed.sol
deleted file mode 100644
index cc3adf28..00000000
--- a/contracts/products/zeroGUSDV/ZeroGUsdvDataFeed.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../feeds/DataFeed.sol";
-import "./ZeroGUsdvMidasAccessControlRoles.sol";
-
-/**
- * @title ZeroGUsdvDataFeed
- * @notice DataFeed for zeroGUSDV product
- * @author RedDuck Software
- */
-contract ZeroGUsdvDataFeed is DataFeed, ZeroGUsdvMidasAccessControlRoles {
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc DataFeed
- */
- function feedAdminRole() public pure override returns (bytes32) {
- return ZEROG_USDV_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/zeroGUSDV/ZeroGUsdvDepositVault.sol b/contracts/products/zeroGUSDV/ZeroGUsdvDepositVault.sol
deleted file mode 100644
index eff5efc5..00000000
--- a/contracts/products/zeroGUSDV/ZeroGUsdvDepositVault.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../DepositVault.sol";
-import "./ZeroGUsdvMidasAccessControlRoles.sol";
-
-/**
- * @title ZeroGUsdvDepositVault
- * @notice Smart contract that handles zeroGUSDV minting
- * @author RedDuck Software
- */
-contract ZeroGUsdvDepositVault is
- DepositVault,
- ZeroGUsdvMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return ZEROG_USDV_DEPOSIT_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/zeroGUSDV/ZeroGUsdvMidasAccessControlRoles.sol b/contracts/products/zeroGUSDV/ZeroGUsdvMidasAccessControlRoles.sol
deleted file mode 100644
index 432e4b76..00000000
--- a/contracts/products/zeroGUSDV/ZeroGUsdvMidasAccessControlRoles.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-/**
- * @title ZeroGUsdvMidasAccessControlRoles
- * @notice Base contract that stores all roles descriptors for zeroGUSDV contracts
- * @author RedDuck Software
- */
-abstract contract ZeroGUsdvMidasAccessControlRoles {
- /**
- * @notice actor that can manage ZeroGUsdvDepositVault
- */
- bytes32 public constant ZEROG_USDV_DEPOSIT_VAULT_ADMIN_ROLE =
- keccak256("ZEROG_USDV_DEPOSIT_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage ZeroGUsdvRedemptionVault
- */
- bytes32 public constant ZEROG_USDV_REDEMPTION_VAULT_ADMIN_ROLE =
- keccak256("ZEROG_USDV_REDEMPTION_VAULT_ADMIN_ROLE");
-
- /**
- * @notice actor that can manage ZeroGUsdvCustomAggregatorFeed and ZeroGUsdvDataFeed
- */
- bytes32 public constant ZEROG_USDV_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("ZEROG_USDV_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
-}
diff --git a/contracts/products/zeroGUSDV/ZeroGUsdvRedemptionVaultWithSwapper.sol b/contracts/products/zeroGUSDV/ZeroGUsdvRedemptionVaultWithSwapper.sol
deleted file mode 100644
index d203a082..00000000
--- a/contracts/products/zeroGUSDV/ZeroGUsdvRedemptionVaultWithSwapper.sol
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../RedemptionVaultWithSwapper.sol";
-import "./ZeroGUsdvMidasAccessControlRoles.sol";
-
-/**
- * @title ZeroGUsdvRedemptionVaultWithSwapper
- * @notice Smart contract that handles zeroGUSDV redemptions
- * @author RedDuck Software
- */
-contract ZeroGUsdvRedemptionVaultWithSwapper is
- RedemptionVaultWithSwapper,
- ZeroGUsdvMidasAccessControlRoles
-{
- /**
- * @dev leaving a storage gap for futures updates
- */
- uint256[50] private __gap;
-
- /**
- * @inheritdoc ManageableVault
- */
- function vaultRole() public pure override returns (bytes32) {
- return ZEROG_USDV_REDEMPTION_VAULT_ADMIN_ROLE;
- }
-}
diff --git a/contracts/products/zeroGUSDV/zeroGUSDV.sol b/contracts/products/zeroGUSDV/zeroGUSDV.sol
deleted file mode 100644
index 3de5cebe..00000000
--- a/contracts/products/zeroGUSDV/zeroGUSDV.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../../mToken.sol";
-
-/**
- * @title zeroGUSDV
- * @author RedDuck Software
- */
-//solhint-disable contract-name-camelcase
-contract zeroGUSDV is mToken {
- /**
- * @notice actor that can mint zeroGUSDV
- */
- bytes32 public constant ZEROG_USDV_MINT_OPERATOR_ROLE =
- keccak256("ZEROG_USDV_MINT_OPERATOR_ROLE");
-
- /**
- * @notice actor that can burn zeroGUSDV
- */
- bytes32 public constant ZEROG_USDV_BURN_OPERATOR_ROLE =
- keccak256("ZEROG_USDV_BURN_OPERATOR_ROLE");
-
- /**
- * @notice actor that can pause zeroGUSDV
- */
- bytes32 public constant ZEROG_USDV_PAUSE_OPERATOR_ROLE =
- keccak256("ZEROG_USDV_PAUSE_OPERATOR_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 ("0G USD Vault", "0gUSDV");
- }
-
- /**
- * @dev AC role, owner of which can mint zeroGUSDV token
- */
- function _minterRole() internal pure override returns (bytes32) {
- return ZEROG_USDV_MINT_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can burn zeroGUSDV token
- */
- function _burnerRole() internal pure override returns (bytes32) {
- return ZEROG_USDV_BURN_OPERATOR_ROLE;
- }
-
- /**
- * @dev AC role, owner of which can pause zeroGUSDV token
- */
- function _pauserRole() internal pure override returns (bytes32) {
- return ZEROG_USDV_PAUSE_OPERATOR_ROLE;
- }
-}
diff --git a/contracts/testers/BlacklistableTester.sol b/contracts/testers/BlacklistableTester.sol
index eb4b64af..a037b87d 100644
--- a/contracts/testers/BlacklistableTester.sol
+++ b/contracts/testers/BlacklistableTester.sol
@@ -1,19 +1,11 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "../access/Blacklistable.sol";
contract BlacklistableTester is Blacklistable {
function initialize(address _accessControl) external initializer {
- __Blacklistable_init(_accessControl);
- }
-
- function initializeWithoutInitializer(address _accessControl) external {
- __Blacklistable_init(_accessControl);
- }
-
- function initializeUnchainedWithoutInitializer() external {
- __Blacklistable_init_unchained();
+ __WithMidasAccessControl_init(_accessControl);
}
function onlyNotBlacklistedTester(address account)
@@ -21,5 +13,11 @@ contract BlacklistableTester is Blacklistable {
onlyNotBlacklisted(account)
{}
+ function contractAdminRole() public pure override returns (bytes32) {
+ return _DEFAULT_ADMIN_ROLE;
+ }
+
function _disableInitializers() internal override {}
+
+ function _onlyProxyAdmin() internal view override {}
}
diff --git a/contracts/testers/CompositeDataFeedTest.sol b/contracts/testers/CompositeDataFeedTest.sol
index 59c31b44..183c00b3 100644
--- a/contracts/testers/CompositeDataFeedTest.sol
+++ b/contracts/testers/CompositeDataFeedTest.sol
@@ -1,8 +1,12 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "../feeds/CompositeDataFeed.sol";
contract CompositeDataFeedTest is CompositeDataFeed {
+ constructor() CompositeDataFeed(_DEFAULT_ADMIN_ROLE) {}
+
function _disableInitializers() internal override {}
+
+ function _onlyProxyAdmin() internal view override {}
}
diff --git a/contracts/testers/CustomAggregatorV3CompatibleFeedAdjustedTester.sol b/contracts/testers/CustomAggregatorV3CompatibleFeedAdjustedTester.sol
index c58fda64..d7f8d23a 100644
--- a/contracts/testers/CustomAggregatorV3CompatibleFeedAdjustedTester.sol
+++ b/contracts/testers/CustomAggregatorV3CompatibleFeedAdjustedTester.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "../feeds/CustomAggregatorV3CompatibleFeedAdjusted.sol";
diff --git a/contracts/testers/CustomAggregatorV3CompatibleFeedGrowthTester.sol b/contracts/testers/CustomAggregatorV3CompatibleFeedGrowthTester.sol
index f7de4053..50578a44 100644
--- a/contracts/testers/CustomAggregatorV3CompatibleFeedGrowthTester.sol
+++ b/contracts/testers/CustomAggregatorV3CompatibleFeedGrowthTester.sol
@@ -1,23 +1,20 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "../feeds/CustomAggregatorV3CompatibleFeedGrowth.sol";
contract CustomAggregatorV3CompatibleFeedGrowthTester is
CustomAggregatorV3CompatibleFeedGrowth
{
- bytes32 public constant CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
+ constructor()
+ CustomAggregatorV3CompatibleFeedGrowth(
+ keccak256("CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE")
+ )
+ {}
function _disableInitializers() internal override {}
- function feedAdminRole() public pure override returns (bytes32) {
- return CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
-
- function setMaxAnswerDeviation(uint256 _deviation) public {
- maxAnswerDeviation = _deviation;
- }
+ function _onlyProxyAdmin() internal view override {}
function getDeviation(
int256 _lastPrice,
diff --git a/contracts/testers/CustomAggregatorV3CompatibleFeedTester.sol b/contracts/testers/CustomAggregatorV3CompatibleFeedTester.sol
index 25ad6944..ef38f00f 100644
--- a/contracts/testers/CustomAggregatorV3CompatibleFeedTester.sol
+++ b/contracts/testers/CustomAggregatorV3CompatibleFeedTester.sol
@@ -1,19 +1,20 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "../feeds/CustomAggregatorV3CompatibleFeed.sol";
contract CustomAggregatorV3CompatibleFeedTester is
CustomAggregatorV3CompatibleFeed
{
- bytes32 public constant CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE =
- keccak256("CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE");
+ constructor()
+ CustomAggregatorV3CompatibleFeed(
+ keccak256("CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE")
+ )
+ {}
function _disableInitializers() internal override {}
- function feedAdminRole() public pure override returns (bytes32) {
- return CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE;
- }
+ function _onlyProxyAdmin() internal view override {}
function getDeviation(int256 _lastPrice, int256 _newPrice)
public
diff --git a/contracts/testers/DataFeedTest.sol b/contracts/testers/DataFeedTest.sol
index f67b8db3..38d9eba3 100644
--- a/contracts/testers/DataFeedTest.sol
+++ b/contracts/testers/DataFeedTest.sol
@@ -1,8 +1,12 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "../feeds/DataFeed.sol";
contract DataFeedTest is DataFeed {
+ constructor() DataFeed(_DEFAULT_ADMIN_ROLE) {}
+
function _disableInitializers() internal override {}
+
+ function _onlyProxyAdmin() internal view override {}
}
diff --git a/contracts/testers/DecimalsCorrectionTester.sol b/contracts/testers/DecimalsCorrectionTester.sol
index 73b7e251..697debc1 100644
--- a/contracts/testers/DecimalsCorrectionTester.sol
+++ b/contracts/testers/DecimalsCorrectionTester.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "../libraries/DecimalsCorrectionLibrary.sol";
diff --git a/contracts/testers/DepositVaultTest.sol b/contracts/testers/DepositVaultTest.sol
index 8f1df7b8..fb6b88f2 100644
--- a/contracts/testers/DepositVaultTest.sol
+++ b/contracts/testers/DepositVaultTest.sol
@@ -1,39 +1,36 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
-import "../DepositVault.sol";
-
-contract DepositVaultTest is DepositVault {
- bool private _overrideGetTokenRate;
- uint256 private _getTokenRateValue;
-
- function _disableInitializers() internal override {}
+import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
- function tokenTransferFromToTester(
- address token,
- address from,
- address to,
- uint256 amount,
- uint256 tokenDecimals
- ) external {
- _tokenTransferFromTo(token, from, to, amount, tokenDecimals);
- }
+import "../DepositVault.sol";
+import "../libraries/MidasAuthLibrary.sol";
+import {ManageableVaultTesterBase} from "./ManageableVaultTester.sol";
- function tokenTransferToUserTester(
- address token,
- address to,
- uint256 amount,
- uint256 tokenDecimals
- ) external {
- _tokenTransferToUser(token, to, amount, tokenDecimals);
- }
+abstract contract DepositVaultTestBase is
+ DepositVault,
+ ManageableVaultTesterBase
+{
+ function _disableInitializers()
+ internal
+ virtual
+ override(Initializable, ManageableVaultTesterBase)
+ {}
- function setOverrideGetTokenRate(bool val) external {
- _overrideGetTokenRate = val;
+ function convertTokenToUsdTest(address tokenIn, uint256 amount)
+ external
+ view
+ returns (uint256 amountInUsd, uint256 rate)
+ {
+ return _convertTokenToUsd(tokenIn, amount);
}
- function setGetTokenRateValue(uint256 val) external {
- _getTokenRateValue = val;
+ function convertUsdToMTokenTest(uint256 amountUsd)
+ external
+ view
+ returns (uint256 amountMToken, uint256 mTokenRate)
+ {
+ return _convertUsdToMToken(amountUsd);
}
function calcAndValidateDeposit(
@@ -45,30 +42,55 @@ contract DepositVaultTest is DepositVault {
return _calcAndValidateDeposit(user, tokenIn, amountToken, isInstant);
}
- function convertTokenToUsdTest(address tokenIn, uint256 amount)
- external
- returns (uint256 amountInUsd, uint256 rate)
- {
- return _convertTokenToUsd(tokenIn, amount);
- }
-
- function convertUsdToMTokenTest(uint256 amountUsd)
- external
- returns (uint256 amountMToken, uint256 mTokenRate)
- {
- return _convertUsdToMToken(amountUsd);
+ function calculateHoldbackPartRateFromAvgTest(
+ uint256 depositedUsdAmount,
+ uint256 depositedInstantUsdAmount,
+ uint256 mTokenRate,
+ uint256 avgMTokenRate
+ ) external pure returns (uint256) {
+ return
+ _calculateHoldbackPartRateFromAvg(
+ Request({
+ depositedInstantUsdAmount: depositedInstantUsdAmount,
+ tokenOutRate: mTokenRate,
+ approvedTokenOutRate: 0,
+ depositedUsdAmount: depositedUsdAmount,
+ usdAmountWithoutFees: 0,
+ recipient: address(0),
+ tokenIn: address(0),
+ status: RequestStatus.Pending,
+ amountMToken: 0
+ }),
+ avgMTokenRate
+ );
}
function _getTokenRate(address dataFeed, bool stable)
internal
view
- override
+ virtual
+ override(ManageableVaultTesterBase, ManageableVault)
returns (uint256)
{
- if (_overrideGetTokenRate) {
- return _getTokenRateValue;
- }
+ return ManageableVaultTesterBase._getTokenRate(dataFeed, stable);
+ }
- return super._getTokenRate(dataFeed, stable);
+ function contractAdminRole()
+ public
+ view
+ virtual
+ override(ManageableVaultTesterBase, ManageableVault)
+ returns (bytes32)
+ {
+ return ManageableVault.contractAdminRole();
}
}
+
+contract DepositVaultTest is DepositVaultTestBase {
+ constructor()
+ DepositVault(
+ keccak256("DEPOSIT_VAULT_ADMIN_ROLE"),
+ MidasAuthLibrary.DEFAULT_GREENLISTED_ROLE
+ )
+ {}
+}
diff --git a/contracts/testers/DepositVaultWithAaveTest.sol b/contracts/testers/DepositVaultWithAaveTest.sol
index a1c0c958..dc02ed85 100644
--- a/contracts/testers/DepositVaultWithAaveTest.sol
+++ b/contracts/testers/DepositVaultWithAaveTest.sol
@@ -1,74 +1,68 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
-import "../DepositVaultWithAave.sol";
-
-contract DepositVaultWithAaveTest is DepositVaultWithAave {
- bool private _overrideGetTokenRate;
- uint256 private _getTokenRateValue;
-
- function _disableInitializers() internal override {}
-
- function tokenTransferFromToTester(
- address token,
- address from,
- address to,
- uint256 amount,
- uint256 tokenDecimals
- ) external {
- _tokenTransferFromTo(token, from, to, amount, tokenDecimals);
- }
+import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
- function tokenTransferToUserTester(
- address token,
- address to,
- uint256 amount,
- uint256 tokenDecimals
- ) external {
- _tokenTransferToUser(token, to, amount, tokenDecimals);
- }
+import "../DepositVaultWithAave.sol";
+import "./DepositVaultTest.sol";
- function setOverrideGetTokenRate(bool val) external {
- _overrideGetTokenRate = val;
- }
+contract DepositVaultWithAaveTest is
+ DepositVaultTestBase,
+ DepositVaultWithAave
+{
+ constructor()
+ DepositVaultWithAave(
+ keccak256("DEPOSIT_VAULT_ADMIN_ROLE"),
+ MidasAuthLibrary.DEFAULT_GREENLISTED_ROLE
+ )
+ {}
- function setGetTokenRateValue(uint256 val) external {
- _getTokenRateValue = val;
+ function _disableInitializers()
+ internal
+ override(Initializable, DepositVaultTestBase)
+ {
+ DepositVaultTestBase._disableInitializers();
}
- function calcAndValidateDeposit(
- address user,
+ function _instantTransferTokensToTokensReceiver(
address tokenIn,
uint256 amountToken,
- bool isInstant
- ) external returns (CalcAndValidateDepositResult memory) {
- return _calcAndValidateDeposit(user, tokenIn, amountToken, isInstant);
+ uint256 tokensDecimals
+ ) internal virtual override(DepositVaultWithAave, DepositVault) {
+ DepositVaultWithAave._instantTransferTokensToTokensReceiver(
+ tokenIn,
+ amountToken,
+ tokensDecimals
+ );
}
- function convertTokenToUsdTest(address tokenIn, uint256 amount)
- external
- returns (uint256 amountInUsd, uint256 rate)
- {
- return _convertTokenToUsd(tokenIn, amount);
- }
-
- function convertUsdToMTokenTest(uint256 amountUsd)
- external
- returns (uint256 amountMToken, uint256 mTokenRate)
- {
- return _convertUsdToMToken(amountUsd);
+ function _requestTransferTokensToTokensReceiver(
+ address tokenIn,
+ uint256 amountToken,
+ uint256 tokensDecimals
+ ) internal override(DepositVaultWithAave, DepositVault) {
+ DepositVaultWithAave._requestTransferTokensToTokensReceiver(
+ tokenIn,
+ amountToken,
+ tokensDecimals
+ );
}
function _getTokenRate(address dataFeed, bool stable)
internal
view
- override
+ override(DepositVaultTestBase, ManageableVault)
returns (uint256)
{
- if (_overrideGetTokenRate) {
- return _getTokenRateValue;
- }
+ return DepositVaultTestBase._getTokenRate(dataFeed, stable);
+ }
- return super._getTokenRate(dataFeed, stable);
+ function contractAdminRole()
+ public
+ view
+ override(DepositVaultTestBase, ManageableVault)
+ returns (bytes32)
+ {
+ return DepositVaultTestBase.contractAdminRole();
}
}
diff --git a/contracts/testers/DepositVaultWithMTokenTest.sol b/contracts/testers/DepositVaultWithMTokenTest.sol
index c07b431c..f4917042 100644
--- a/contracts/testers/DepositVaultWithMTokenTest.sol
+++ b/contracts/testers/DepositVaultWithMTokenTest.sol
@@ -1,74 +1,68 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
-import "../DepositVaultWithMToken.sol";
-
-contract DepositVaultWithMTokenTest is DepositVaultWithMToken {
- bool private _overrideGetTokenRate;
- uint256 private _getTokenRateValue;
-
- function _disableInitializers() internal override {}
-
- function tokenTransferFromToTester(
- address token,
- address from,
- address to,
- uint256 amount,
- uint256 tokenDecimals
- ) external {
- _tokenTransferFromTo(token, from, to, amount, tokenDecimals);
- }
+import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
- function tokenTransferToUserTester(
- address token,
- address to,
- uint256 amount,
- uint256 tokenDecimals
- ) external {
- _tokenTransferToUser(token, to, amount, tokenDecimals);
- }
+import "../DepositVaultWithMToken.sol";
+import "./DepositVaultTest.sol";
- function setOverrideGetTokenRate(bool val) external {
- _overrideGetTokenRate = val;
- }
+contract DepositVaultWithMTokenTest is
+ DepositVaultTestBase,
+ DepositVaultWithMToken
+{
+ constructor()
+ DepositVaultWithMToken(
+ keccak256("DEPOSIT_VAULT_ADMIN_ROLE"),
+ MidasAuthLibrary.DEFAULT_GREENLISTED_ROLE
+ )
+ {}
- function setGetTokenRateValue(uint256 val) external {
- _getTokenRateValue = val;
+ function _disableInitializers()
+ internal
+ override(Initializable, DepositVaultTestBase)
+ {
+ DepositVaultTestBase._disableInitializers();
}
- function calcAndValidateDeposit(
- address user,
+ function _instantTransferTokensToTokensReceiver(
address tokenIn,
uint256 amountToken,
- bool isInstant
- ) external returns (CalcAndValidateDepositResult memory) {
- return _calcAndValidateDeposit(user, tokenIn, amountToken, isInstant);
+ uint256 tokensDecimals
+ ) internal virtual override(DepositVaultWithMToken, DepositVault) {
+ DepositVaultWithMToken._instantTransferTokensToTokensReceiver(
+ tokenIn,
+ amountToken,
+ tokensDecimals
+ );
}
- function convertTokenToUsdTest(address tokenIn, uint256 amount)
- external
- returns (uint256 amountInUsd, uint256 rate)
- {
- return _convertTokenToUsd(tokenIn, amount);
- }
-
- function convertUsdToMTokenTest(uint256 amountUsd)
- external
- returns (uint256 amountMToken, uint256 mTokenRate)
- {
- return _convertUsdToMToken(amountUsd);
+ function _requestTransferTokensToTokensReceiver(
+ address tokenIn,
+ uint256 amountToken,
+ uint256 tokensDecimals
+ ) internal override(DepositVaultWithMToken, DepositVault) {
+ DepositVaultWithMToken._requestTransferTokensToTokensReceiver(
+ tokenIn,
+ amountToken,
+ tokensDecimals
+ );
}
function _getTokenRate(address dataFeed, bool stable)
internal
view
- override
+ override(DepositVaultTestBase, ManageableVault)
returns (uint256)
{
- if (_overrideGetTokenRate) {
- return _getTokenRateValue;
- }
+ return DepositVaultTestBase._getTokenRate(dataFeed, stable);
+ }
- return super._getTokenRate(dataFeed, stable);
+ function contractAdminRole()
+ public
+ view
+ override(DepositVaultTestBase, ManageableVault)
+ returns (bytes32)
+ {
+ return DepositVaultTestBase.contractAdminRole();
}
}
diff --git a/contracts/testers/DepositVaultWithMorphoTest.sol b/contracts/testers/DepositVaultWithMorphoTest.sol
index dd282045..790e3c77 100644
--- a/contracts/testers/DepositVaultWithMorphoTest.sol
+++ b/contracts/testers/DepositVaultWithMorphoTest.sol
@@ -1,74 +1,68 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
-import "../DepositVaultWithMorpho.sol";
-
-contract DepositVaultWithMorphoTest is DepositVaultWithMorpho {
- bool private _overrideGetTokenRate;
- uint256 private _getTokenRateValue;
-
- function _disableInitializers() internal override {}
-
- function tokenTransferFromToTester(
- address token,
- address from,
- address to,
- uint256 amount,
- uint256 tokenDecimals
- ) external {
- _tokenTransferFromTo(token, from, to, amount, tokenDecimals);
- }
+import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
- function tokenTransferToUserTester(
- address token,
- address to,
- uint256 amount,
- uint256 tokenDecimals
- ) external {
- _tokenTransferToUser(token, to, amount, tokenDecimals);
- }
+import "../DepositVaultWithMorpho.sol";
+import "./DepositVaultTest.sol";
- function setOverrideGetTokenRate(bool val) external {
- _overrideGetTokenRate = val;
- }
+contract DepositVaultWithMorphoTest is
+ DepositVaultTestBase,
+ DepositVaultWithMorpho
+{
+ constructor()
+ DepositVaultWithMorpho(
+ keccak256("DEPOSIT_VAULT_ADMIN_ROLE"),
+ MidasAuthLibrary.DEFAULT_GREENLISTED_ROLE
+ )
+ {}
- function setGetTokenRateValue(uint256 val) external {
- _getTokenRateValue = val;
+ function _disableInitializers()
+ internal
+ override(Initializable, DepositVaultTestBase)
+ {
+ DepositVaultTestBase._disableInitializers();
}
- function calcAndValidateDeposit(
- address user,
+ function _instantTransferTokensToTokensReceiver(
address tokenIn,
uint256 amountToken,
- bool isInstant
- ) external returns (CalcAndValidateDepositResult memory) {
- return _calcAndValidateDeposit(user, tokenIn, amountToken, isInstant);
+ uint256 tokensDecimals
+ ) internal virtual override(DepositVaultWithMorpho, DepositVault) {
+ DepositVaultWithMorpho._instantTransferTokensToTokensReceiver(
+ tokenIn,
+ amountToken,
+ tokensDecimals
+ );
}
- function convertTokenToUsdTest(address tokenIn, uint256 amount)
- external
- returns (uint256 amountInUsd, uint256 rate)
- {
- return _convertTokenToUsd(tokenIn, amount);
- }
-
- function convertUsdToMTokenTest(uint256 amountUsd)
- external
- returns (uint256 amountMToken, uint256 mTokenRate)
- {
- return _convertUsdToMToken(amountUsd);
+ function _requestTransferTokensToTokensReceiver(
+ address tokenIn,
+ uint256 amountToken,
+ uint256 tokensDecimals
+ ) internal override(DepositVaultWithMorpho, DepositVault) {
+ DepositVaultWithMorpho._requestTransferTokensToTokensReceiver(
+ tokenIn,
+ amountToken,
+ tokensDecimals
+ );
}
function _getTokenRate(address dataFeed, bool stable)
internal
view
- override
+ override(DepositVaultTestBase, ManageableVault)
returns (uint256)
{
- if (_overrideGetTokenRate) {
- return _getTokenRateValue;
- }
+ return DepositVaultTestBase._getTokenRate(dataFeed, stable);
+ }
- return super._getTokenRate(dataFeed, stable);
+ function contractAdminRole()
+ public
+ view
+ override(DepositVaultTestBase, ManageableVault)
+ returns (bytes32)
+ {
+ return DepositVaultTestBase.contractAdminRole();
}
}
diff --git a/contracts/testers/DepositVaultWithUSTBTest.sol b/contracts/testers/DepositVaultWithUSTBTest.sol
index 995989c3..f8c5fbd9 100644
--- a/contracts/testers/DepositVaultWithUSTBTest.sol
+++ b/contracts/testers/DepositVaultWithUSTBTest.sol
@@ -1,74 +1,56 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
-import "../DepositVaultWithUSTB.sol";
-
-contract DepositVaultWithUSTBTest is DepositVaultWithUSTB {
- bool private _overrideGetTokenRate;
- uint256 private _getTokenRateValue;
-
- function _disableInitializers() internal override {}
+import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
- function tokenTransferFromToTester(
- address token,
- address from,
- address to,
- uint256 amount,
- uint256 tokenDecimals
- ) external {
- _tokenTransferFromTo(token, from, to, amount, tokenDecimals);
- }
-
- function tokenTransferToUserTester(
- address token,
- address to,
- uint256 amount,
- uint256 tokenDecimals
- ) external {
- _tokenTransferToUser(token, to, amount, tokenDecimals);
- }
-
- function setOverrideGetTokenRate(bool val) external {
- _overrideGetTokenRate = val;
- }
-
- function setGetTokenRateValue(uint256 val) external {
- _getTokenRateValue = val;
+import "../DepositVaultWithUSTB.sol";
+import "./DepositVaultTest.sol";
+
+contract DepositVaultWithUSTBTest is
+ DepositVaultTestBase,
+ DepositVaultWithUSTB
+{
+ constructor()
+ DepositVaultWithUSTB(
+ keccak256("DEPOSIT_VAULT_ADMIN_ROLE"),
+ MidasAuthLibrary.DEFAULT_GREENLISTED_ROLE
+ )
+ {}
+
+ function _disableInitializers()
+ internal
+ override(Initializable, DepositVaultTestBase)
+ {
+ DepositVaultTestBase._disableInitializers();
}
- function calcAndValidateDeposit(
- address user,
+ function _instantTransferTokensToTokensReceiver(
address tokenIn,
uint256 amountToken,
- bool isInstant
- ) external returns (CalcAndValidateDepositResult memory) {
- return _calcAndValidateDeposit(user, tokenIn, amountToken, isInstant);
- }
-
- function convertTokenToUsdTest(address tokenIn, uint256 amount)
- external
- returns (uint256 amountInUsd, uint256 rate)
- {
- return _convertTokenToUsd(tokenIn, amount);
- }
-
- function convertUsdToMTokenTest(uint256 amountUsd)
- external
- returns (uint256 amountMToken, uint256 mTokenRate)
- {
- return _convertUsdToMToken(amountUsd);
+ uint256 tokensDecimals
+ ) internal virtual override(DepositVaultWithUSTB, DepositVault) {
+ DepositVaultWithUSTB._instantTransferTokensToTokensReceiver(
+ tokenIn,
+ amountToken,
+ tokensDecimals
+ );
}
function _getTokenRate(address dataFeed, bool stable)
internal
view
- override
+ override(DepositVaultTestBase, ManageableVault)
returns (uint256)
{
- if (_overrideGetTokenRate) {
- return _getTokenRateValue;
- }
+ return DepositVaultTestBase._getTokenRate(dataFeed, stable);
+ }
- return super._getTokenRate(dataFeed, stable);
+ function contractAdminRole()
+ public
+ view
+ override(DepositVaultTestBase, ManageableVault)
+ returns (bytes32)
+ {
+ return DepositVaultTestBase.contractAdminRole();
}
}
diff --git a/contracts/testers/GreenlistableTester.sol b/contracts/testers/GreenlistableTester.sol
index 434fdc0e..1b7cc74b 100644
--- a/contracts/testers/GreenlistableTester.sol
+++ b/contracts/testers/GreenlistableTester.sol
@@ -1,19 +1,11 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "../access/Greenlistable.sol";
contract GreenlistableTester is Greenlistable {
function initialize(address _accessControl) external initializer {
- __Greenlistable_init(_accessControl);
- }
-
- function initializeWithoutInitializer(address _accessControl) external {
- __Greenlistable_init(_accessControl);
- }
-
- function initializeUnchainedWithoutInitializer() external {
- __Greenlistable_init_unchained();
+ __WithMidasAccessControl_init(_accessControl);
}
function onlyGreenlistedTester(address account)
@@ -21,19 +13,19 @@ contract GreenlistableTester is Greenlistable {
onlyGreenlisted(account)
{}
- function onlyGreenlistTogglerTester(address account) external view {
- _onlyGreenlistToggler(account);
+ function _disableInitializers() internal override {}
+
+ function _onlyProxyAdmin() internal view override {}
+
+ function greenlistAdminRole() public view virtual returns (bytes32) {
+ return keccak256("GREENLIST_ADMIN_ROLE");
}
- function _disableInitializers() internal override {}
+ function contractAdminRole() public pure override returns (bytes32) {
+ return _DEFAULT_ADMIN_ROLE;
+ }
- function greenlistTogglerRole()
- public
- view
- virtual
- override
- returns (bytes32)
- {
- return keccak256("GREENLIST_TOGGLER_ROLE");
+ function greenlistedRole() public view virtual override returns (bytes32) {
+ return MidasAuthLibrary.DEFAULT_GREENLISTED_ROLE;
}
}
diff --git a/contracts/testers/ManageableVaultTester.sol b/contracts/testers/ManageableVaultTester.sol
index d9d245bc..fe348eae 100644
--- a/contracts/testers/ManageableVaultTester.sol
+++ b/contracts/testers/ManageableVaultTester.sol
@@ -1,60 +1,87 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "../abstract/ManageableVault.sol";
+import "../libraries/MidasAuthLibrary.sol";
-contract ManageableVaultTester is ManageableVault {
- function _disableInitializers() internal override {}
-
- function initialize(
- address _ac,
- MTokenInitParams calldata _mTokenInitParams,
- ReceiversInitParams calldata _receiversInitParams,
- InstantInitParams calldata _instantInitParams,
- address _sanctionsList,
- uint256 _variationTolerance,
- uint256 _minAmount
+abstract contract ManageableVaultTesterBase is ManageableVault {
+ bytes32 private _contractAdminRoleOverride;
+ bool private _overrideGetTokenRate;
+ uint256 private _getTokenRateValue;
+
+ function _disableInitializers() internal virtual override {}
+
+ function setVaultRole(bytes32 role) external {
+ _contractAdminRoleOverride = role;
+ }
+
+ function setOverrideGetTokenRate(bool _override) external {
+ _overrideGetTokenRate = _override;
+ }
+
+ function tokenTransferFromToTester(
+ address token,
+ address from,
+ address to,
+ uint256 amount,
+ uint256 tokenDecimals
+ ) external {
+ _tokenTransferFromTo(token, from, to, amount, tokenDecimals);
+ }
+
+ function setGetTokenRateValue(uint256 val) external {
+ _getTokenRateValue = val;
+ }
+
+ function _getTokenRate(address dataFeed, bool stable)
+ internal
+ view
+ virtual
+ override
+ returns (uint256)
+ {
+ if (_overrideGetTokenRate) {
+ return _getTokenRateValue;
+ }
+
+ return super._getTokenRate(dataFeed, stable);
+ }
+
+ function initializeExternal(
+ CommonVaultInitParams calldata _commonVaultInitParams
) external initializer {
- __ManageableVault_init(
- _ac,
- _mTokenInitParams,
- _receiversInitParams,
- _instantInitParams,
- _sanctionsList,
- _variationTolerance,
- _minAmount
- );
+ __ManageableVault_init(_commonVaultInitParams);
}
function initializeWithoutInitializer(
- address _ac,
- MTokenInitParams calldata _mTokenInitParams,
- ReceiversInitParams calldata _receiversInitParams,
- InstantInitParams calldata _instantInitParams,
- address _sanctionsList,
- uint256 _variationTolerance,
- uint256 _minAmount
+ CommonVaultInitParams calldata _commonVaultInitParams
) external {
- __ManageableVault_init(
- _ac,
- _mTokenInitParams,
- _receiversInitParams,
- _instantInitParams,
- _sanctionsList,
- _variationTolerance,
- _minAmount
- );
+ __ManageableVault_init(_commonVaultInitParams);
}
- function vaultRole() public view virtual override returns (bytes32) {}
-
- function greenlistTogglerRole()
+ function contractAdminRole()
public
view
virtual
override
returns (bytes32)
{
- return keccak256("GREENLIST_TOGGLER_ROLE");
+ if (_contractAdminRoleOverride != bytes32(0)) {
+ return _contractAdminRoleOverride;
+ }
+
+ return keccak256("VAULT_ADMIN_ROLE");
}
}
+
+contract ManageableVaultTester is ManageableVaultTesterBase {
+ /**
+ * @notice constructor
+ */
+ constructor()
+ ManageableVault(
+ keccak256("VAULT_ADMIN_ROLE"),
+ MidasAuthLibrary.DEFAULT_GREENLISTED_ROLE
+ )
+ {}
+}
diff --git a/contracts/testers/MidasAccessControlTest.sol b/contracts/testers/MidasAccessControlTest.sol
index 59347595..eb3b4949 100644
--- a/contracts/testers/MidasAccessControlTest.sol
+++ b/contracts/testers/MidasAccessControlTest.sol
@@ -1,8 +1,14 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "../access/MidasAccessControl.sol";
contract MidasAccessControlTest is MidasAccessControl {
function _disableInitializers() internal override {}
+
+ function _onlyProxyAdmin() internal view override {}
+
+ function setDefaultDelayTest(uint32 delay) external {
+ defaultDelay = delay;
+ }
}
diff --git a/contracts/testers/MidasAccessControlTimelockControllerTest.sol b/contracts/testers/MidasAccessControlTimelockControllerTest.sol
new file mode 100644
index 00000000..e978f84a
--- /dev/null
+++ b/contracts/testers/MidasAccessControlTimelockControllerTest.sol
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
+
+import "../access/MidasAccessControlTimelockController.sol";
+
+contract MidasAccessControlTimelockControllerTest is
+ MidasAccessControlTimelockController
+{
+ function _disableInitializers() internal override {}
+
+ function _onlyProxyAdmin() internal view override {}
+}
diff --git a/contracts/testers/MidasAxelarVaultExecutableTester.sol b/contracts/testers/MidasAxelarVaultExecutableTester.sol
index df49dd7d..75770d9c 100644
--- a/contracts/testers/MidasAxelarVaultExecutableTester.sol
+++ b/contracts/testers/MidasAxelarVaultExecutableTester.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.21;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "../misc/axelar/MidasAxelarVaultExecutable.sol";
diff --git a/contracts/testers/MidasInitializableTester.sol b/contracts/testers/MidasInitializableTester.sol
new file mode 100644
index 00000000..f6d97c08
--- /dev/null
+++ b/contracts/testers/MidasInitializableTester.sol
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
+
+import {MidasInitializable} from "../abstract/MidasInitializable.sol";
+
+contract MidasInitializableTester is MidasInitializable {
+ uint256 public initializeCallsCount;
+
+ uint256 public reinitCallsCount;
+
+ function initialize() external {
+ _initializeV1();
+ initializeV2();
+ }
+
+ function initializeV2() public reinitializer(2) onlyProxyAdmin {
+ reinitCallsCount++;
+ }
+
+ function _initializeV1() private initializer {
+ initializeCallsCount++;
+ }
+}
diff --git a/contracts/testers/MidasLzVaultComposerSyncTester.sol b/contracts/testers/MidasLzVaultComposerSyncTester.sol
index 5a820b28..bcce73d0 100644
--- a/contracts/testers/MidasLzVaultComposerSyncTester.sol
+++ b/contracts/testers/MidasLzVaultComposerSyncTester.sol
@@ -1,5 +1,5 @@
-// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.21;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "../misc/layerzero/MidasLzVaultComposerSync.sol";
diff --git a/contracts/testers/MidasPauseManagerTest.sol b/contracts/testers/MidasPauseManagerTest.sol
new file mode 100644
index 00000000..4290f1e4
--- /dev/null
+++ b/contracts/testers/MidasPauseManagerTest.sol
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
+
+import "../access/MidasPauseManager.sol";
+
+contract MidasPauseManagerTest is MidasPauseManager {
+ function _disableInitializers() internal override {}
+
+ function _onlyProxyAdmin() internal view override {}
+}
diff --git a/contracts/testers/MidasTimelockManagerTest.sol b/contracts/testers/MidasTimelockManagerTest.sol
new file mode 100644
index 00000000..3b101490
--- /dev/null
+++ b/contracts/testers/MidasTimelockManagerTest.sol
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
+
+import "../access/MidasTimelockManager.sol";
+
+contract MidasTimelockManagerTest is MidasTimelockManager {
+ function _disableInitializers() internal override {}
+
+ function _onlyProxyAdmin() internal view override {}
+}
diff --git a/contracts/testers/PausableTester.sol b/contracts/testers/PausableTester.sol
index 40cf87ba..fb769363 100644
--- a/contracts/testers/PausableTester.sol
+++ b/contracts/testers/PausableTester.sol
@@ -1,20 +1,33 @@
-// SPDX-License-Identifier: UNLICENSEDI
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
-import "../access/Pausable.sol";
+import {WithMidasAccessControl} from "../access/WithMidasAccessControl.sol";
+import {PauseGuardsLibrary} from "../libraries/PauseGuardsLibrary.sol";
+
+contract PausableTester is WithMidasAccessControl {
+ bytes32 private _contractAdminRoleOverride;
+
+ function setContractAdminRole(bytes32 role) external {
+ _contractAdminRoleOverride = role;
+ }
-contract PausableTester is Pausable {
function initialize(address _accessControl) external initializer {
- __Pausable_init(_accessControl);
+ __WithMidasAccessControl_init(_accessControl);
}
- function initializeWithoutInitializer(address _accessControl) external {
- __Pausable_init(_accessControl);
+ function requireFnNotPaused(bytes4 fn) external {
+ PauseGuardsLibrary.requireFnNotPaused(accessControl, fn);
}
- function pauseAdminRole() public view override returns (bytes32) {
- return accessControl.DEFAULT_ADMIN_ROLE();
+ function requireNotPaused(bytes4 fn) external {
+ PauseGuardsLibrary.requireNotPaused(accessControl, fn);
+ }
+
+ function contractAdminRole() public view override returns (bytes32) {
+ return _contractAdminRoleOverride;
}
function _disableInitializers() internal override {}
+
+ function _onlyProxyAdmin() internal view override {}
}
diff --git a/contracts/testers/RateLimitLibraryTester.sol b/contracts/testers/RateLimitLibraryTester.sol
new file mode 100644
index 00000000..c8af8005
--- /dev/null
+++ b/contracts/testers/RateLimitLibraryTester.sol
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
+
+import {EnumerableSetUpgradeable as EnumerableSet} from "@openzeppelin/contracts-upgradeable/utils/structs/EnumerableSetUpgradeable.sol";
+
+import {RateLimitLibrary} from "../libraries/RateLimitLibrary.sol";
+
+/**
+ * @notice Exposes {RateLimitLibrary} internals for unit tests.
+ */
+contract RateLimitLibraryTester {
+ using EnumerableSet for EnumerableSet.UintSet;
+
+ RateLimitLibrary.WindowRateLimits private _limits;
+
+ function setWindowLimitPublic(uint256 window, uint256 limit)
+ external
+ returns (uint256 previousLimit)
+ {
+ return RateLimitLibrary.setWindowLimit(_limits, window, limit);
+ }
+
+ function removeWindowLimitPublic(uint256 window) external {
+ RateLimitLibrary.removeWindowLimit(_limits, window);
+ }
+
+ function consumeLimitPublic(uint256 amount) external {
+ RateLimitLibrary.consumeLimit(_limits, amount);
+ }
+
+ function getWindowStatusesPublic()
+ external
+ view
+ returns (RateLimitLibrary.WindowRateLimitStatus[] memory)
+ {
+ return RateLimitLibrary.getWindowStatuses(_limits);
+ }
+
+ function getWindowConfigPublic(uint256 window)
+ external
+ view
+ returns (
+ uint256 limit,
+ uint256 amountInFlight,
+ uint256 lastUpdated,
+ uint256 windowDuration
+ )
+ {
+ RateLimitLibrary.WindowRateLimitConfig storage cfg = _limits.configs[
+ window
+ ];
+ return (cfg.limit, cfg.amountInFlight, cfg.lastUpdated, cfg.window);
+ }
+
+ function windowCountPublic() external view returns (uint256) {
+ return _limits.windows.length();
+ }
+
+ function hasWindowPublic(uint256 window) external view returns (bool) {
+ return _limits.windows.contains(window);
+ }
+}
diff --git a/contracts/testers/RedemptionVaultTest.sol b/contracts/testers/RedemptionVaultTest.sol
index bdb1a890..cf50d09d 100644
--- a/contracts/testers/RedemptionVaultTest.sol
+++ b/contracts/testers/RedemptionVaultTest.sol
@@ -1,87 +1,110 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
-import "../RedemptionVault.sol";
-
-contract RedemptionVaultTest is RedemptionVault {
- bool private _overrideGetTokenRate;
- uint256 private _getTokenRateValue;
-
- function _disableInitializers() internal override {}
+import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
- function initializeWithoutInitializer(
- address _ac,
- MTokenInitParams calldata _mTokenInitParams,
- ReceiversInitParams calldata _receiversInitParams,
- InstantInitParams calldata _instantInitParams,
- address _sanctionsList,
- uint256 _variationTolerance,
- uint256 _minAmount,
- FiatRedeptionInitParams calldata _fiatRedemptionInitParams,
- address _requestRedeemer
- ) external {
- __RedemptionVault_init(
- _ac,
- _mTokenInitParams,
- _receiversInitParams,
- _instantInitParams,
- _sanctionsList,
- _variationTolerance,
- _minAmount,
- _fiatRedemptionInitParams,
- _requestRedeemer
- );
- }
-
- function setOverrideGetTokenRate(bool val) external {
- _overrideGetTokenRate = val;
- }
+import "../RedemptionVault.sol";
+import "../libraries/MidasAuthLibrary.sol";
+import {ManageableVaultTesterBase} from "./ManageableVaultTester.sol";
- function setGetTokenRateValue(uint256 val) external {
- _getTokenRateValue = val;
- }
+abstract contract RedemptionVaultTestBase is
+ RedemptionVault,
+ ManageableVaultTesterBase
+{
+ function _disableInitializers()
+ internal
+ virtual
+ override(Initializable, ManageableVaultTesterBase)
+ {}
function calcAndValidateRedeemTest(
address user,
address tokenOut,
uint256 amountMTokenIn,
- bool isInstant,
- bool isFiat
+ uint256 overrideMTokenRate,
+ uint256 overrideTokenOutRate,
+ bool shouldOverrideFeePercent,
+ uint256 overrideFeePercent,
+ bool isInstant
) external returns (CalcAndValidateRedeemResult memory calcResult) {
return
_calcAndValidateRedeem(
user,
tokenOut,
amountMTokenIn,
- isInstant,
- isFiat
+ overrideMTokenRate,
+ overrideTokenOutRate,
+ shouldOverrideFeePercent,
+ overrideFeePercent,
+ isInstant
);
}
- function convertUsdToTokenTest(uint256 amountUsd, address tokenOut)
- external
- returns (uint256 amountToken, uint256 tokenRate)
- {
- return _convertUsdToToken(amountUsd, tokenOut);
+ function calculateHoldbackPartRateFromAvgTest(
+ uint256 amountMToken,
+ uint256 amountMTokenInstant,
+ uint256 mTokenRate,
+ uint256 avgMTokenRate
+ ) external pure returns (uint256) {
+ return
+ _calculateHoldbackPartRateFromAvg(
+ Request({
+ amountMToken: amountMToken,
+ amountMTokenInstant: amountMTokenInstant,
+ mTokenRate: mTokenRate,
+ tokenOut: address(0),
+ tokenOutRate: 0,
+ feePercent: 0,
+ recipient: address(0),
+ status: RequestStatus.Pending,
+ approvedMTokenRate: 0,
+ amountTokenOut: 0
+ }),
+ avgMTokenRate
+ );
}
- function convertMTokenToUsdTest(uint256 amountMToken)
- external
- returns (uint256 amountUsd, uint256 mTokenRate)
- {
- return _convertMTokenToUsd(amountMToken);
+ function convertUsdToTokenTest(
+ uint256 amountUsd,
+ address tokenOut,
+ uint256 overrideTokenOutRate
+ ) external view returns (uint256 amountToken, uint256 tokenRate) {
+ return _convertUsdToToken(amountUsd, tokenOut, overrideTokenOutRate);
+ }
+
+ function convertMTokenToUsdTest(
+ uint256 amountMToken,
+ uint256 overrideMTokenRate
+ ) external view returns (uint256 amountUsd, uint256 mTokenRate) {
+ return _convertMTokenToUsd(amountMToken, overrideMTokenRate);
}
function _getTokenRate(address dataFeed, bool stable)
internal
view
- override
+ virtual
+ override(ManageableVaultTesterBase, ManageableVault)
returns (uint256)
{
- if (_overrideGetTokenRate) {
- return _getTokenRateValue;
- }
+ return ManageableVaultTesterBase._getTokenRate(dataFeed, stable);
+ }
- return super._getTokenRate(dataFeed, stable);
+ function contractAdminRole()
+ public
+ view
+ virtual
+ override(ManageableVaultTesterBase, ManageableVault)
+ returns (bytes32)
+ {
+ return ManageableVault.contractAdminRole();
}
}
+
+contract RedemptionVaultTest is RedemptionVaultTestBase {
+ constructor()
+ RedemptionVault(
+ keccak256("REDEMPTION_VAULT_ADMIN_ROLE"),
+ MidasAuthLibrary.DEFAULT_GREENLISTED_ROLE
+ )
+ {}
+}
diff --git a/contracts/testers/RedemptionVaultWithAaveTest.sol b/contracts/testers/RedemptionVaultWithAaveTest.sol
index 2a044421..6b7811b5 100644
--- a/contracts/testers/RedemptionVaultWithAaveTest.sol
+++ b/contracts/testers/RedemptionVaultWithAaveTest.sol
@@ -1,12 +1,96 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
+import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "../RedemptionVaultWithAave.sol";
+import "./RedemptionVaultTest.sol";
-contract RedemptionVaultWithAaveTest is RedemptionVaultWithAave {
- function _disableInitializers() internal override {}
+contract RedemptionVaultWithAaveTest is
+ RedemptionVaultTestBase,
+ RedemptionVaultWithAave
+{
+ constructor()
+ RedemptionVaultWithAave(
+ keccak256("REDEMPTION_VAULT_ADMIN_ROLE"),
+ MidasAuthLibrary.DEFAULT_GREENLISTED_ROLE
+ )
+ {}
- function checkAndRedeemAave(address token, uint256 amount) external {
- _checkAndRedeemAave(token, amount);
+ function _disableInitializers()
+ internal
+ virtual
+ override(Initializable, RedemptionVaultTestBase)
+ {
+ RedemptionVaultTestBase._disableInitializers();
+ }
+
+ function checkAndRedeemAave(address token, uint256 amount)
+ external
+ returns (
+ uint256 /* obtainedLiquidityBase18 */
+ )
+ {
+ uint256 tokenDecimals = _tokenDecimals(token);
+ uint256 balance = DecimalsCorrectionLibrary.convertToBase18(
+ IERC20(token).balanceOf(address(this)),
+ tokenDecimals
+ );
+ uint256 amountBase18 = DecimalsCorrectionLibrary.convertToBase18(
+ amount,
+ tokenDecimals
+ );
+ uint256 missingAmount = amountBase18 > balance
+ ? amountBase18 - balance
+ : 0;
+
+ return
+ _obtainVaultLiquidity(
+ token,
+ missingAmount,
+ 0,
+ balance,
+ tokenDecimals
+ );
+ }
+
+ function _obtainVaultLiquidity(
+ address tokenOut,
+ uint256 amountTokenOutBase18,
+ uint256 tokenOutRate,
+ uint256 currentTokenOutBalanceBase18,
+ uint256 tokenOutDecimals
+ )
+ internal
+ override(RedemptionVaultWithAave, RedemptionVault)
+ returns (
+ uint256 /* obtainedLiquidityBase18 */
+ )
+ {
+ return
+ RedemptionVaultWithAave._obtainVaultLiquidity(
+ tokenOut,
+ amountTokenOutBase18,
+ tokenOutRate,
+ currentTokenOutBalanceBase18,
+ tokenOutDecimals
+ );
+ }
+
+ function _getTokenRate(address dataFeed, bool stable)
+ internal
+ view
+ override(RedemptionVaultTestBase, ManageableVault)
+ returns (uint256)
+ {
+ return RedemptionVaultTestBase._getTokenRate(dataFeed, stable);
+ }
+
+ function contractAdminRole()
+ public
+ view
+ override(RedemptionVaultTestBase, ManageableVault)
+ returns (bytes32)
+ {
+ return RedemptionVaultTestBase.contractAdminRole();
}
}
diff --git a/contracts/testers/RedemptionVaultWithBUIDLTest.sol b/contracts/testers/RedemptionVaultWithBUIDLTest.sol
deleted file mode 100644
index bf4b89fb..00000000
--- a/contracts/testers/RedemptionVaultWithBUIDLTest.sol
+++ /dev/null
@@ -1,8 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../RedemptionVaultWithBUIDL.sol";
-
-contract RedemptionVaultWithBUIDLTest is RedemptionVaultWIthBUIDL {
- function _disableInitializers() internal override {}
-}
diff --git a/contracts/testers/RedemptionVaultWithMTokenTest.sol b/contracts/testers/RedemptionVaultWithMTokenTest.sol
index 118a32dc..4b7645c1 100644
--- a/contracts/testers/RedemptionVaultWithMTokenTest.sol
+++ b/contracts/testers/RedemptionVaultWithMTokenTest.sol
@@ -1,16 +1,100 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
+import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "../RedemptionVaultWithMToken.sol";
+import "./RedemptionVaultTest.sol";
-contract RedemptionVaultWithMTokenTest is RedemptionVaultWithMToken {
- function _disableInitializers() internal override {}
+contract RedemptionVaultWithMTokenTest is
+ RedemptionVaultTestBase,
+ RedemptionVaultWithMToken
+{
+ constructor()
+ RedemptionVaultWithMToken(
+ keccak256("REDEMPTION_VAULT_ADMIN_ROLE"),
+ MidasAuthLibrary.DEFAULT_GREENLISTED_ROLE
+ )
+ {}
+
+ function _disableInitializers()
+ internal
+ virtual
+ override(Initializable, RedemptionVaultTestBase)
+ {
+ RedemptionVaultTestBase._disableInitializers();
+ }
function checkAndRedeemMToken(
address token,
uint256 amount,
uint256 rate
- ) external {
- _checkAndRedeemMToken(token, amount, rate);
+ )
+ external
+ returns (
+ uint256 /* obtainedLiquidityBase18 */
+ )
+ {
+ uint256 tokenDecimals = _tokenDecimals(token);
+ uint256 balance = DecimalsCorrectionLibrary.convertToBase18(
+ IERC20(token).balanceOf(address(this)),
+ tokenDecimals
+ );
+ uint256 amountBase18 = DecimalsCorrectionLibrary.convertToBase18(
+ amount,
+ tokenDecimals
+ );
+ uint256 missingAmount = amountBase18 > balance
+ ? amountBase18 - balance
+ : 0;
+
+ return
+ _obtainVaultLiquidity(
+ token,
+ missingAmount,
+ rate,
+ balance,
+ tokenDecimals
+ );
+ }
+
+ function _obtainVaultLiquidity(
+ address token,
+ uint256 amountTokenOutBase18,
+ uint256 tokenOutRate,
+ uint256 currentTokenOutBalanceBase18,
+ uint256 tokenOutDecimals
+ )
+ internal
+ override(RedemptionVaultWithMToken, RedemptionVault)
+ returns (
+ uint256 /* obtainedLiquidityBase18 */
+ )
+ {
+ return
+ RedemptionVaultWithMToken._obtainVaultLiquidity(
+ token,
+ amountTokenOutBase18,
+ tokenOutRate,
+ currentTokenOutBalanceBase18,
+ tokenOutDecimals
+ );
+ }
+
+ function _getTokenRate(address dataFeed, bool stable)
+ internal
+ view
+ override(RedemptionVaultTestBase, ManageableVault)
+ returns (uint256)
+ {
+ return RedemptionVaultTestBase._getTokenRate(dataFeed, stable);
+ }
+
+ function contractAdminRole()
+ public
+ view
+ override(RedemptionVaultTestBase, ManageableVault)
+ returns (bytes32)
+ {
+ return RedemptionVaultTestBase.contractAdminRole();
}
}
diff --git a/contracts/testers/RedemptionVaultWithMorphoTest.sol b/contracts/testers/RedemptionVaultWithMorphoTest.sol
index 58f17791..f70a599d 100644
--- a/contracts/testers/RedemptionVaultWithMorphoTest.sol
+++ b/contracts/testers/RedemptionVaultWithMorphoTest.sol
@@ -1,12 +1,96 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
+import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "../RedemptionVaultWithMorpho.sol";
+import "./RedemptionVaultTest.sol";
-contract RedemptionVaultWithMorphoTest is RedemptionVaultWithMorpho {
- function _disableInitializers() internal override {}
+contract RedemptionVaultWithMorphoTest is
+ RedemptionVaultTestBase,
+ RedemptionVaultWithMorpho
+{
+ constructor()
+ RedemptionVaultWithMorpho(
+ keccak256("REDEMPTION_VAULT_ADMIN_ROLE"),
+ MidasAuthLibrary.DEFAULT_GREENLISTED_ROLE
+ )
+ {}
- function checkAndRedeemMorpho(address token, uint256 amount) external {
- _checkAndRedeemMorpho(token, amount);
+ function _disableInitializers()
+ internal
+ virtual
+ override(Initializable, RedemptionVaultTestBase)
+ {
+ RedemptionVaultTestBase._disableInitializers();
+ }
+
+ function checkAndRedeemMorpho(address token, uint256 amount)
+ external
+ returns (
+ uint256 /* obtainedLiquidityBase18 */
+ )
+ {
+ uint256 tokenDecimals = _tokenDecimals(token);
+ uint256 balance = DecimalsCorrectionLibrary.convertToBase18(
+ IERC20(token).balanceOf(address(this)),
+ tokenDecimals
+ );
+ uint256 amountBase18 = DecimalsCorrectionLibrary.convertToBase18(
+ amount,
+ tokenDecimals
+ );
+ uint256 missingAmount = amountBase18 > balance
+ ? amountBase18 - balance
+ : 0;
+
+ return
+ _obtainVaultLiquidity(
+ token,
+ missingAmount,
+ 0,
+ balance,
+ tokenDecimals
+ );
+ }
+
+ function _obtainVaultLiquidity(
+ address token,
+ uint256 amountTokenOutBase18,
+ uint256 tokenOutRate,
+ uint256 currentTokenOutBalanceBase18,
+ uint256 tokenOutDecimals
+ )
+ internal
+ override(RedemptionVaultWithMorpho, RedemptionVault)
+ returns (
+ uint256 /* obtainedLiquidityBase18 */
+ )
+ {
+ return
+ RedemptionVaultWithMorpho._obtainVaultLiquidity(
+ token,
+ amountTokenOutBase18,
+ tokenOutRate,
+ currentTokenOutBalanceBase18,
+ tokenOutDecimals
+ );
+ }
+
+ function _getTokenRate(address dataFeed, bool stable)
+ internal
+ view
+ override(RedemptionVaultTestBase, ManageableVault)
+ returns (uint256)
+ {
+ return RedemptionVaultTestBase._getTokenRate(dataFeed, stable);
+ }
+
+ function contractAdminRole()
+ public
+ view
+ override(RedemptionVaultTestBase, ManageableVault)
+ returns (bytes32)
+ {
+ return RedemptionVaultTestBase.contractAdminRole();
}
}
diff --git a/contracts/testers/RedemptionVaultWithSwapperTest.sol b/contracts/testers/RedemptionVaultWithSwapperTest.sol
deleted file mode 100644
index 9b61cadb..00000000
--- a/contracts/testers/RedemptionVaultWithSwapperTest.sol
+++ /dev/null
@@ -1,8 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../RedemptionVaultWithSwapper.sol";
-
-contract RedemptionVaultWithSwapperTest is RedemptionVaultWithSwapper {
- function _disableInitializers() internal override {}
-}
diff --git a/contracts/testers/RedemptionVaultWithUSTBTest.sol b/contracts/testers/RedemptionVaultWithUSTBTest.sol
index 2efd1dba..03f2dd33 100644
--- a/contracts/testers/RedemptionVaultWithUSTBTest.sol
+++ b/contracts/testers/RedemptionVaultWithUSTBTest.sol
@@ -1,12 +1,96 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
+import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "../RedemptionVaultWithUSTB.sol";
+import "./RedemptionVaultTest.sol";
-contract RedemptionVaultWithUSTBTest is RedemptionVaultWithUSTB {
- function _disableInitializers() internal override {}
+contract RedemptionVaultWithUSTBTest is
+ RedemptionVaultTestBase,
+ RedemptionVaultWithUSTB
+{
+ constructor()
+ RedemptionVaultWithUSTB(
+ keccak256("REDEMPTION_VAULT_ADMIN_ROLE"),
+ MidasAuthLibrary.DEFAULT_GREENLISTED_ROLE
+ )
+ {}
- function checkAndRedeemUSTB(address token, uint256 amount) external {
- _checkAndRedeemUSTB(token, amount);
+ function _disableInitializers()
+ internal
+ virtual
+ override(Initializable, RedemptionVaultTestBase)
+ {
+ RedemptionVaultTestBase._disableInitializers();
+ }
+
+ function checkAndRedeemUSTB(address token, uint256 amount)
+ external
+ returns (
+ uint256 /* obtainedLiquidityBase18 */
+ )
+ {
+ uint256 tokenDecimals = _tokenDecimals(token);
+ uint256 balance = DecimalsCorrectionLibrary.convertToBase18(
+ IERC20(token).balanceOf(address(this)),
+ tokenDecimals
+ );
+ uint256 amountBase18 = DecimalsCorrectionLibrary.convertToBase18(
+ amount,
+ tokenDecimals
+ );
+ uint256 missingAmount = amountBase18 > balance
+ ? amountBase18 - balance
+ : 0;
+
+ return
+ _obtainVaultLiquidity(
+ token,
+ missingAmount,
+ 0,
+ balance,
+ tokenDecimals
+ );
+ }
+
+ function _obtainVaultLiquidity(
+ address tokenOut,
+ uint256 amountTokenOutBase18,
+ uint256 tokenOutRate,
+ uint256 currentTokenOutBalanceBase18,
+ uint256 tokenOutDecimals
+ )
+ internal
+ override(RedemptionVaultWithUSTB, RedemptionVault)
+ returns (
+ uint256 /* obtainedLiquidityBase18 */
+ )
+ {
+ return
+ RedemptionVaultWithUSTB._obtainVaultLiquidity(
+ tokenOut,
+ amountTokenOutBase18,
+ tokenOutRate,
+ currentTokenOutBalanceBase18,
+ tokenOutDecimals
+ );
+ }
+
+ function _getTokenRate(address dataFeed, bool stable)
+ internal
+ view
+ override(RedemptionVaultTestBase, ManageableVault)
+ returns (uint256)
+ {
+ return RedemptionVaultTestBase._getTokenRate(dataFeed, stable);
+ }
+
+ function contractAdminRole()
+ public
+ view
+ override(RedemptionVaultTestBase, ManageableVault)
+ returns (bytes32)
+ {
+ return RedemptionVaultTestBase.contractAdminRole();
}
}
diff --git a/contracts/testers/WithMidasAccessControlTester.sol b/contracts/testers/WithMidasAccessControlTester.sol
index 1105d61b..490be609 100644
--- a/contracts/testers/WithMidasAccessControlTester.sol
+++ b/contracts/testers/WithMidasAccessControlTester.sol
@@ -1,9 +1,25 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "../access/WithMidasAccessControl.sol";
contract WithMidasAccessControlTester is WithMidasAccessControl {
+ bytes32 private _contractAdminRoleOverride;
+
+ /**
+ * @notice copy of `RolePreflightSucceeded` with a different name for testing
+ */
+ error WrongRolePreflightSucceeded(
+ bytes32 role,
+ uint32 overrideDelay,
+ bool roleIsFunctionOperator,
+ bool validateFunctionRole
+ );
+
+ function setContractAdminRole(bytes32 role) external {
+ _contractAdminRoleOverride = role;
+ }
+
function initialize(address _accessControl) external initializer {
__WithMidasAccessControl_init(_accessControl);
}
@@ -12,23 +28,39 @@ contract WithMidasAccessControlTester is WithMidasAccessControl {
__WithMidasAccessControl_init(_accessControl);
}
- function grantRoleTester(bytes32 role, address account) external {
- accessControl.grantRole(role, account);
- }
-
- function revokeRoleTester(bytes32 role, address account) external {
- accessControl.revokeRole(role, account);
- }
-
- function withOnlyRole(bytes32 role, address account)
+ function withOnlyRole(bytes32 role, bool validateFunctionRole)
external
- onlyRole(role, account)
+ onlyRole(role, validateFunctionRole)
{}
- function withOnlyNotRole(bytes32 role, address account)
+ function withOnlyRoleNoTimelock(bytes32 role, bool validateFunctionRole)
external
- onlyNotRole(role, account)
+ onlyRoleNoTimelock(role, validateFunctionRole)
{}
+ function withOnlyContractAdmin() external onlyContractAdmin {}
+
+ function withUnprotected() external {}
+
+ function withWrongRolePreflight(
+ bytes32 role,
+ uint32 overrideDelay,
+ bool roleIsFunctionOperator,
+ bool validateFunctionRole
+ ) external pure {
+ revert WrongRolePreflightSucceeded(
+ role,
+ overrideDelay,
+ roleIsFunctionOperator,
+ validateFunctionRole
+ );
+ }
+
+ function contractAdminRole() public view override returns (bytes32) {
+ return _contractAdminRoleOverride;
+ }
+
function _disableInitializers() internal override {}
+
+ function _onlyProxyAdmin() internal view override {}
}
diff --git a/contracts/testers/WithSanctionsListTester.sol b/contracts/testers/WithSanctionsListTester.sol
index 45052ef1..9cb2eed0 100644
--- a/contracts/testers/WithSanctionsListTester.sol
+++ b/contracts/testers/WithSanctionsListTester.sol
@@ -1,22 +1,15 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
import "../abstract/WithSanctionsList.sol";
-// TODO: add natspec
contract WithSanctionsListTester is WithSanctionsList {
function initialize(address _accessControl, address _sanctionsList)
external
initializer
{
- __WithSanctionsList_init(_accessControl, _sanctionsList);
- }
-
- function initializeWithoutInitializer(
- address _accessControl,
- address _sanctionsList
- ) external {
- __WithSanctionsList_init(_accessControl, _sanctionsList);
+ __WithMidasAccessControl_init(_accessControl);
+ __WithSanctionsList_init_unchained(_sanctionsList);
}
function initializeUnchainedWithoutInitializer(address _sanctionsList)
@@ -30,9 +23,15 @@ contract WithSanctionsListTester is WithSanctionsList {
onlyNotSanctioned(user)
{}
- function sanctionsListAdminRole() public pure override returns (bytes32) {
+ function sanctionsListAdminRole() public pure returns (bytes32) {
return keccak256("TESTER_SANCTIONS_LIST_ADMIN_ROLE");
}
+ function contractAdminRole() public pure override returns (bytes32) {
+ return _DEFAULT_ADMIN_ROLE;
+ }
+
function _disableInitializers() internal override {}
+
+ function _onlyProxyAdmin() internal view override {}
}
diff --git a/contracts/testers/mTBILLTest.sol b/contracts/testers/mTBILLTest.sol
deleted file mode 100644
index 99fcc8c5..00000000
--- a/contracts/testers/mTBILLTest.sol
+++ /dev/null
@@ -1,9 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../products/mTBILL/mTBILL.sol";
-
-//solhint-disable contract-name-camelcase
-contract mTBILLTest is mTBILL {
- function _disableInitializers() internal override {}
-}
diff --git a/contracts/testers/mTokenPermissionedTest.sol b/contracts/testers/mTokenPermissionedTest.sol
deleted file mode 100644
index 9abc7d0e..00000000
--- a/contracts/testers/mTokenPermissionedTest.sol
+++ /dev/null
@@ -1,46 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.9;
-
-import "../mTokenPermissioned.sol";
-
-//solhint-disable contract-name-camelcase
-contract mTokenPermissionedTest is mTokenPermissioned {
- 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");
-
- function _disableInitializers() internal override {}
-
- function _getNameSymbol()
- internal
- pure
- override
- returns (string memory, string memory)
- {
- return ("mTokenPermissionedTest", "mTokenPermissionedTest");
- }
-
- 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;
- }
-}
diff --git a/contracts/testers/mTokenTest.sol b/contracts/testers/mTokenTest.sol
new file mode 100644
index 00000000..7a843835
--- /dev/null
+++ b/contracts/testers/mTokenTest.sol
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity 0.8.34;
+
+import "../mToken.sol";
+
+//solhint-disable contract-name-camelcase
+contract mTokenTest is mToken {
+ constructor(
+ bytes32 _managerRole,
+ bytes32 _mintOperatorRole,
+ bytes32 _burnOperatorRole,
+ bytes32 _greenlistedRole,
+ bytes32 _minBalanceExemptRole
+ )
+ mToken(
+ _managerRole,
+ _mintOperatorRole,
+ _burnOperatorRole,
+ _greenlistedRole,
+ _minBalanceExemptRole
+ )
+ {}
+
+ function _disableInitializers() internal override {}
+
+ function _onlyProxyAdmin() internal view override {}
+}
diff --git a/docgen/index.md b/docgen/index.md
index dda34bdf..7558cce8 100644
--- a/docgen/index.md
+++ b/docgen/index.md
@@ -1,515 +1,533 @@
# Solidity API
-## RedemptionVault
+## MidasInitializable
-Smart contract that handles mTBILL redemptions
+Base Initializable contract that implements constructor
+that calls _disableInitializers() to prevent
+initialization of implementation contract
-### CalcAndValidateRedeemResult
+### SenderNotProxyAdmin
```solidity
-struct CalcAndValidateRedeemResult {
- uint256 feeAmount;
- uint256 amountMTokenWithoutFee;
-}
+error SenderNotProxyAdmin()
```
-### minFiatRedeemAmount
+error when the sender is not the proxy admin
+
+### InvalidAddress
```solidity
-uint256 minFiatRedeemAmount
+error InvalidAddress(address addr)
```
-min amount for fiat requests
+error when the address is invalid
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| addr | address | address |
-### fiatAdditionalFee
+### onlyProxyAdmin
```solidity
-uint256 fiatAdditionalFee
+modifier onlyProxyAdmin()
```
-fee percent for fiat requests
+modifier to check if the sender is the proxy admin
+
+### constructor
+
+```solidity
+constructor() internal
+```
-### fiatFlatFee
+### _onlyProxyAdmin
```solidity
-uint256 fiatFlatFee
+function _onlyProxyAdmin() internal view virtual
```
-static fee in mToken for fiat requests
+function to check if the sender is the proxy admin
-### redeemRequests
+## WithMidasAccessControl
+
+Base contract that consumes MidasAccessControl
+
+### _DEFAULT_ADMIN_ROLE
```solidity
-mapping(uint256 => struct Request) redeemRequests
+bytes32 _DEFAULT_ADMIN_ROLE
```
-mapping, requestId to request data
+admin role
-### requestRedeemer
+### accessControl
```solidity
-address requestRedeemer
+contract IMidasAccessControl accessControl
```
-address is designated for standard redemptions, allowing tokens to be pulled from this address
+MidasAccessControl contract address
-### initialize
+### SameBoolValue
```solidity
-function initialize(address _ac, struct MTokenInitParams _mTokenInitParams, struct ReceiversInitParams _receiversInitParams, struct InstantInitParams _instantInitParams, address _sanctionsList, uint256 _variationTolerance, uint256 _minAmount, struct FiatRedeptionInitParams _fiatRedemptionInitParams, address _requestRedeemer) external
+error SameBoolValue(bool value)
```
-upgradeable pattern contract`s initializer
+error when the value is the same as the previous value
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| _ac | address | address of MidasAccessControll contract |
-| _mTokenInitParams | struct MTokenInitParams | init params for mToken |
-| _receiversInitParams | struct ReceiversInitParams | init params for receivers |
-| _instantInitParams | struct InstantInitParams | init params for instant operations |
-| _sanctionsList | address | address of sanctionsList contract |
-| _variationTolerance | uint256 | percent of prices diviation 1% = 100 |
-| _minAmount | uint256 | basic min amount for operations |
-| _fiatRedemptionInitParams | struct FiatRedeptionInitParams | params fiatAdditionalFee, fiatFlatFee, minFiatRedeemAmount |
-| _requestRedeemer | address | address is designated for standard redemptions, allowing tokens to be pulled from this address |
-
-### __RedemptionVault_init
-
-```solidity
-function __RedemptionVault_init(address _ac, struct MTokenInitParams _mTokenInitParams, struct ReceiversInitParams _receiversInitParams, struct InstantInitParams _instantInitParams, address _sanctionsList, uint256 _variationTolerance, uint256 _minAmount, struct FiatRedeptionInitParams _fiatRedemptionInitParams, address _requestRedeemer) internal
-```
+| value | bool | value |
-### redeemInstant
+### onlyRole
```solidity
-function redeemInstant(address tokenOut, uint256 amountMTokenIn, uint256 minReceiveAmount) external
+modifier onlyRole(bytes32 role, bool validateFunctionRole)
```
-redeem mToken to tokenOut if daily limit and allowance not exceeded
-Burns mTBILL from the user.
-Transfers fee in mToken to feeReceiver
-Transfers tokenOut to user.
+_validates that the caller has the function role with timelock_
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| tokenOut | address | stable coin token address to redeem to |
-| amountMTokenIn | uint256 | amount of mTBILL to redeem (decimals 18) |
-| minReceiveAmount | uint256 | minimum expected amount of tokenOut to receive (decimals 18) |
+| role | bytes32 | base role to validate |
+| validateFunctionRole | bool | whether to validate the function role |
-### redeemInstant
+### onlyRoleDelayOverride
```solidity
-function redeemInstant(address tokenOut, uint256 amountMTokenIn, uint256 minReceiveAmount, address recipient) external
+modifier onlyRoleDelayOverride(bytes32 role, uint32 overrideDelay, bool validateFunctionRole)
```
-Does the same as `redeemInstant` but allows specifying a custom tokensReceiver address.
+_validates that the caller has the function role with timelock_
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| tokenOut | address | stable coin token address to redeem to |
-| amountMTokenIn | uint256 | amount of mTBILL to redeem (decimals 18) |
-| minReceiveAmount | uint256 | minimum expected amount of tokenOut to receive (decimals 18) |
-| recipient | address | address that receives tokens |
+| role | bytes32 | base role to validate |
+| overrideDelay | uint32 | override delay for the invocation |
+| validateFunctionRole | bool | whether to validate the function role |
-### redeemRequest
+### onlyRoleNoTimelock
```solidity
-function redeemRequest(address tokenOut, uint256 amountMTokenIn) external returns (uint256)
+modifier onlyRoleNoTimelock(bytes32 role, bool validateFunctionRole)
```
-creating redeem request if tokenOut not fiat
-Transfers amount in mToken to contract
-Transfers fee in mToken to feeReceiver
+_validates that the caller has the function role without timelock_
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| tokenOut | address | stable coin token address to redeem to |
-| amountMTokenIn | uint256 | amount of mToken to redeem (decimals 18) |
+| role | bytes32 | base role to validate |
+| validateFunctionRole | bool | |
-#### Return Values
+### onlyContractAdmin
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint256 | request id |
+```solidity
+modifier onlyContractAdmin()
+```
-### redeemRequest
+_validates that the caller has the contract admin role or function operator role_
+
+### __WithMidasAccessControl_init
```solidity
-function redeemRequest(address tokenOut, uint256 amountMTokenIn, address recipient) external returns (uint256)
+function __WithMidasAccessControl_init(address _accessControl) internal
```
-Does the same as `redeemRequest` but allows specifying a custom tokensReceiver address.
+_upgradeable pattern contract`s initializer_
-#### Parameters
+### _validateFunctionAccessWithTimelock
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| tokenOut | address | stable coin token address to redeem to |
-| amountMTokenIn | uint256 | amount of mToken to redeem (decimals 18) |
-| recipient | address | address that receives tokens |
+```solidity
+function _validateFunctionAccessWithTimelock(bytes32 role, uint32 overrideDelay, bool roleIsFunctionOperator, address account, bool validateFunctionRole) internal view virtual
+```
-#### Return Values
+_validates that the function access is valid with timelock_
+
+#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | uint256 | request id |
+| role | bytes32 | base role to validate |
+| overrideDelay | uint32 | override delay for the invocation |
+| roleIsFunctionOperator | bool | whether the role is a function operator |
+| account | address | account to validate |
+| validateFunctionRole | bool | whether to validate the function role |
-### redeemFiatRequest
+### _validateFunctionAccessWithoutTimelock
```solidity
-function redeemFiatRequest(uint256 amountMTokenIn) external returns (uint256)
+function _validateFunctionAccessWithoutTimelock(bytes32 role, bool roleIsFunctionOperator, address account, bool validateFunctionRole) internal view
```
-creating redeem request if tokenOut is fiat
-Transfers amount in mToken to contract
-Transfers fee in mToken to feeReceiver
+_validates that the function access is valid without timelock_
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| amountMTokenIn | uint256 | amount of mToken to redeem (decimals 18) |
+| role | bytes32 | base role to validate |
+| roleIsFunctionOperator | bool | whether the role is a function operator |
+| account | address | account to validate |
+| validateFunctionRole | bool | whether to validate the function role |
-#### Return Values
+### contractAdminRole
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint256 | request id |
+```solidity
+function contractAdminRole() public view virtual returns (bytes32)
+```
-### approveRequest
+_main admin role for the contract_
+
+## CompositeDataFeed
+
+A data feed contract that derives its price by computing the ratio
+of two underlying data feeds (numerator ÷ denominator).
+
+_Designed for cases where a synthetic or relative price is needed,
+such as deriving cbBTC/BTC from cbBTC/USD and BTC/USD feeds._
+
+### ChangeNumeratorFeed
```solidity
-function approveRequest(uint256 requestId, uint256 newMTokenRate) external
+event ChangeNumeratorFeed(address _numeratorFeed)
```
-approving redeem request if not exceed tokenOut allowance
-Burns amount mToken from contract
-Transfers tokenOut to user
-Sets flag Processed
-
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| requestId | uint256 | request id |
-| newMTokenRate | uint256 | new mToken rate inputted by vault admin |
+| _numeratorFeed | address | new IDataFeed contract address |
-### safeApproveRequest
+### ChangeDenominatorFeed
```solidity
-function safeApproveRequest(uint256 requestId, uint256 newMTokenRate) external
+event ChangeDenominatorFeed(address _denominatorFeed)
```
-approving request if inputted token rate fit price diviation percent
-Burns amount mToken from contract
-Transfers tokenOut to user
-Sets flag Processed
-
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| requestId | uint256 | request id |
-| newMTokenRate | uint256 | new mToken rate inputted by vault admin |
+| _denominatorFeed | address | new IDataFeed contract address |
-### rejectRequest
+### SetMinMaxExpectedAnswer
```solidity
-function rejectRequest(uint256 requestId) external
+event SetMinMaxExpectedAnswer(uint256 _maxExpectedAnswer, uint256 _minExpectedAnswer)
```
-rejecting request
-Sets request flag to Canceled.
-
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| requestId | uint256 | request id |
+| _maxExpectedAnswer | uint256 | new max expected answer |
+| _minExpectedAnswer | uint256 | new min expected answer |
-### setMinFiatRedeemAmount
+### numeratorFeed
```solidity
-function setMinFiatRedeemAmount(uint256 newValue) external
+contract IDataFeed numeratorFeed
```
-set new min amount for fiat requests
+price feed used as the numerator in the ratio calculation.
-#### Parameters
+_typically represents the asset of interest (e.g., cbBTC/USD)._
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| newValue | uint256 | new min amount |
+### denominatorFeed
+
+```solidity
+contract IDataFeed denominatorFeed
+```
-### setFiatFlatFee
+price feed used as the denominator in the ratio calculation.
+
+_typically represents the reference asset (e.g., BTC/USD)._
+
+### minExpectedAnswer
```solidity
-function setFiatFlatFee(uint256 feeInMToken) external
+uint256 minExpectedAnswer
```
-set fee amount in mToken for fiat requests
+_minimal answer expected to receive from getDataInBase18_
-#### Parameters
+### maxExpectedAnswer
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| feeInMToken | uint256 | fee amount in mToken |
+```solidity
+uint256 maxExpectedAnswer
+```
+
+_maximal answer expected to receive from getDataInBase18_
-### setFiatAdditionalFee
+### constructor
```solidity
-function setFiatAdditionalFee(uint256 newFee) external
+constructor(bytes32 _contractAdminRole) public
```
-set new fee percent for fiat requests
+constructor
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| newFee | uint256 | new fee percent 1% = 100 |
+| _contractAdminRole | bytes32 | contract admin role |
-### setRequestRedeemer
+### initialize
```solidity
-function setRequestRedeemer(address redeemer) external
+function initialize(address _ac, address _numeratorFeed, address _denominatorFeed, uint256 _minExpectedAnswer, uint256 _maxExpectedAnswer) external
```
-set address which is designated for standard redemptions, allowing tokens to be pulled from this address
+upgradeable pattern contract`s initializer
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| redeemer | address | new address of request redeemer |
+| _ac | address | MidasAccessControl contract address |
+| _numeratorFeed | address | numerator feed address |
+| _denominatorFeed | address | denominator feed address |
+| _minExpectedAnswer | uint256 | min. expected answer value from data feed |
+| _maxExpectedAnswer | uint256 | max. expected answer value from data feed |
-### vaultRole
+### changeNumeratorFeed
```solidity
-function vaultRole() public pure virtual returns (bytes32)
+function changeNumeratorFeed(address _numeratorFeed) external
```
-AC role of vault administrator
+updates `numeratorFeed` address
-#### Return Values
+_can only be called by the feed admin_
+
+#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | bytes32 | role bytes32 role |
+| _numeratorFeed | address | new numerator feed address |
-### _approveRequest
+### changeDenominatorFeed
```solidity
-function _approveRequest(uint256 requestId, uint256 newMTokenRate, bool isSafe) internal
+function changeDenominatorFeed(address _denominatorFeed) external
```
-validates approve
-burns amount from contract
-transfer tokenOut to user if not fiat
-sets flag Processed
+updates `denominatorFeed` address
+
+_can only be called by the feed admin_
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| requestId | uint256 | request id |
-| newMTokenRate | uint256 | new mToken rate |
-| isSafe | bool | new mToken rate |
+| _denominatorFeed | address | new denominator feed address |
-### _validateRequest
+### setMinMaxExpectedAnswer
```solidity
-function _validateRequest(address sender, enum RequestStatus status) internal pure
+function setMinMaxExpectedAnswer(uint256 _maxExpectedAnswer, uint256 _minExpectedAnswer) external
```
-validates request
-if exist
-if not processed
+updates `minExpectedAnswer` and `maxExpectedAnswer` values
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| sender | address | sender address |
-| status | enum RequestStatus | request status |
+| _maxExpectedAnswer | uint256 | new max expected answer |
+| _minExpectedAnswer | uint256 | new min expected answer |
-### _redeemInstant
+### getDataInBase18
```solidity
-function _redeemInstant(address tokenOut, uint256 amountMTokenIn, uint256 minReceiveAmount, address recipient) internal virtual returns (struct RedemptionVault.CalcAndValidateRedeemResult calcResult, uint256 amountTokenOutWithoutFee)
+function getDataInBase18() external view returns (uint256 answer)
```
-_internal redeem instant logic_
+_fetches answer from numerator and denominator feeds
+and returns calculated answer (numerator / denominator)_
-#### Parameters
+#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| tokenOut | address | tokenOut address |
-| amountMTokenIn | uint256 | amount of mToken (decimals 18) |
-| minReceiveAmount | uint256 | min amount of tokenOut to receive (decimals 18) |
-| recipient | address | recipient address |
+| answer | uint256 | calculated answer in base18 |
-#### Return Values
+### contractAdminRole
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| calcResult | struct RedemptionVault.CalcAndValidateRedeemResult | calculated redeem result |
-| amountTokenOutWithoutFee | uint256 | amount of tokenOut without fee |
+```solidity
+function contractAdminRole() public view returns (bytes32)
+```
-### _redeemRequest
+_main admin role for the contract_
+
+### _computeCompositePrice
```solidity
-function _redeemRequest(address tokenOut, uint256 amountMTokenIn, bool isFiat, address recipient) internal returns (uint256 requestId, struct RedemptionVault.CalcAndValidateRedeemResult calcResult)
+function _computeCompositePrice(uint256 numerator, uint256 denominator) internal pure virtual returns (uint256 answer)
```
-internal redeem request logic
+_computes the composite price by dividing numerator by denominator_
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| tokenOut | address | tokenOut address |
-| amountMTokenIn | uint256 | amount of mToken (decimals 18) |
-| isFiat | bool | |
-| recipient | address | |
+| numerator | uint256 | numerator value from the first feed |
+| denominator | uint256 | denominator value from the second feed |
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| requestId | uint256 | request id |
-| calcResult | struct RedemptionVault.CalcAndValidateRedeemResult | calc result |
+| answer | uint256 | computed composite price in base18 |
-### _convertUsdToToken
+## CompositeDataFeedMultiply
+
+A data feed contract that derives its price by computing the product
+of two underlying data feeds (numerator × denominator).
+
+_Inherits from CompositeDataFeed and overrides only the calculation logic
+to multiply instead of divide. Designed for cases where a synthetic or combined
+price is needed, such as deriving mXRP/USD from mXRP/XRP and XRP/USD feeds._
+
+### constructor
```solidity
-function _convertUsdToToken(uint256 amountUsd, address tokenOut) internal view returns (uint256 amountToken, uint256 tokenRate)
+constructor(bytes32 _contractAdminRole) public
```
-_calculates tokenOut amount from USD amount_
+constructor
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| amountUsd | uint256 | amount of USD (decimals 18) |
-| tokenOut | address | tokenOut address |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| amountToken | uint256 | converted USD to tokenOut |
-| tokenRate | uint256 | conversion rate |
+| _contractAdminRole | bytes32 | contract admin role |
-### _convertMTokenToUsd
+### _computeCompositePrice
```solidity
-function _convertMTokenToUsd(uint256 amountMToken) internal view returns (uint256 amountUsd, uint256 mTokenRate)
+function _computeCompositePrice(uint256 firstFeedValue, uint256 secondFeedValue) internal pure returns (uint256 answer)
```
-_calculates USD amount from mToken amount_
+_computes the composite price by multiplying the two feed values_
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| amountMToken | uint256 | amount of mToken (decimals 18) |
+| firstFeedValue | uint256 | value from the first feed |
+| secondFeedValue | uint256 | value from the second feed |
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| amountUsd | uint256 | converted amount to USD |
-| mTokenRate | uint256 | conversion rate |
+| answer | uint256 | computed composite price in base18 |
-### _calcAndValidateRedeem
+## DataFeed
+
+Wrapper of ChainLink`s AggregatorV3 data feeds
+
+### ChangeAggregator
```solidity
-function _calcAndValidateRedeem(address user, address tokenOut, uint256 amountMTokenIn, bool isInstant, bool isFiat) internal view returns (struct RedemptionVault.CalcAndValidateRedeemResult result)
+event ChangeAggregator(address _aggregator)
```
-_validate redeem and calculate fee_
-
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| user | address | user address |
-| tokenOut | address | tokenOut address |
-| amountMTokenIn | uint256 | mToken amount (decimals 18) |
-| isInstant | bool | is instant operation |
-| isFiat | bool | is fiat operation |
+| _aggregator | address | new AggregatorV3Interface contract address |
-#### Return Values
+### SetHealthyDiff
+
+```solidity
+event SetHealthyDiff(uint256 _healthyDiff)
+```
+
+#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| result | struct RedemptionVault.CalcAndValidateRedeemResult | calc result |
+| _healthyDiff | uint256 | new healthy diff value |
+
+### SetMinMaxExpectedAnswer
+
+```solidity
+event SetMinMaxExpectedAnswer(int256 _maxExpectedAnswer, int256 _minExpectedAnswer)
+```
-## RedemptionVaultWIthBUIDL
+#### Parameters
-Smart contract that handles mTBILL redemptions
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _maxExpectedAnswer | int256 | new max expected answer |
+| _minExpectedAnswer | int256 | new min expected answer |
-### minBuidlToRedeem
+### aggregator
```solidity
-uint256 minBuidlToRedeem
+contract AggregatorV3Interface aggregator
```
-minimum amount of BUIDL to redeem. Will redeem at least this amount of BUIDL.
+AggregatorV3Interface contract address
-### minBuidlBalance
+### healthyDiff
```solidity
-uint256 minBuidlBalance
+uint256 healthyDiff
```
-### buidlRedemption
+_healty difference between `block.timestamp` and `updatedAt` timestamps_
+
+### minExpectedAnswer
```solidity
-contract IRedemption buidlRedemption
+int256 minExpectedAnswer
```
-### SetMinBuidlToRedeem
+_minimal answer expected to receive from the `aggregator`_
+
+### maxExpectedAnswer
```solidity
-event SetMinBuidlToRedeem(uint256 minBuidlToRedeem, address sender)
+int256 maxExpectedAnswer
```
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| minBuidlToRedeem | uint256 | new min amount of BUIDL to redeem |
-| sender | address | address who set new min amount of BUIDL to redeem |
+_maximal answer expected to receive from the `aggregator`_
-### SetMinBuidlBalance
+### constructor
```solidity
-event SetMinBuidlBalance(uint256 minBuidlBalance, address sender)
+constructor(bytes32 _contractAdminRole) public
```
+constructor
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| minBuidlBalance | uint256 | new `minBuidlBalance` value |
-| sender | address | address who set new `minBuidlBalance` |
+| _contractAdminRole | bytes32 | contract admin role |
### initialize
```solidity
-function initialize(address _ac, struct MTokenInitParams _mTokenInitParams, struct ReceiversInitParams _receiversInitParams, struct InstantInitParams _instantInitParams, address _sanctionsList, uint256 _variationTolerance, uint256 _minAmount, struct FiatRedeptionInitParams _fiatRedemptionInitParams, address _requestRedeemer, address _buidlRedemption, uint256 _minBuidlToRedeem, uint256 _minBuidlBalance) external
+function initialize(address _ac, address _aggregator, uint256 _healthyDiff, int256 _minExpectedAnswer, int256 _maxExpectedAnswer) external
```
upgradeable pattern contract`s initializer
@@ -518,5545 +536,13322 @@ upgradeable pattern contract`s initializer
| Name | Type | Description |
| ---- | ---- | ----------- |
-| _ac | address | address of MidasAccessControll contract |
-| _mTokenInitParams | struct MTokenInitParams | init params for mToken |
-| _receiversInitParams | struct ReceiversInitParams | init params for receivers |
-| _instantInitParams | struct InstantInitParams | init params for instant operations |
-| _sanctionsList | address | address of sanctionsList contract |
-| _variationTolerance | uint256 | percent of prices diviation 1% = 100 |
-| _minAmount | uint256 | basic min amount for operations |
-| _fiatRedemptionInitParams | struct FiatRedeptionInitParams | params fiatAdditionalFee, fiatFlatFee, minFiatRedeemAmount |
-| _requestRedeemer | address | address is designated for standard redemptions, allowing tokens to be pulled from this address |
-| _buidlRedemption | address | BUIDL redemption contract address |
-| _minBuidlToRedeem | uint256 | |
-| _minBuidlBalance | uint256 | |
+| _ac | address | MidasAccessControl contract address |
+| _aggregator | address | AggregatorV3Interface contract address |
+| _healthyDiff | uint256 | max. staleness time for data feed answers |
+| _minExpectedAnswer | int256 | min.expected answer value from data feed |
+| _maxExpectedAnswer | int256 | max.expected answer value from data feed |
-### setMinBuidlToRedeem
+### changeAggregator
```solidity
-function setMinBuidlToRedeem(uint256 _minBuidlToRedeem) external
+function changeAggregator(address _aggregator) external
```
-set min amount of BUIDL to redeem.
+updates `aggregator` address
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| _minBuidlToRedeem | uint256 | min amount of BUIDL to redeem |
+| _aggregator | address | new AggregatorV3Interface contract address |
-### setMinBuidlBalance
+### setHealthyDiff
```solidity
-function setMinBuidlBalance(uint256 _minBuidlBalance) external
+function setHealthyDiff(uint256 _healthyDiff) external
```
-set new `minBuidlBalance` value.
+_updates `healthyDiff` value_
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| _minBuidlBalance | uint256 | new `minBuidlBalance` value |
+| _healthyDiff | uint256 | new value |
-### _redeemInstant
+### setMinMaxExpectedAnswer
```solidity
-function _redeemInstant(address tokenOut, uint256 amountMTokenIn, uint256 minReceiveAmount, address recipient) internal returns (struct RedemptionVault.CalcAndValidateRedeemResult calcResult, uint256 amountTokenOutWithoutFee)
+function setMinMaxExpectedAnswer(int256 _maxExpectedAnswer, int256 _minExpectedAnswer) external
```
-_redeem mToken to USDC if daily limit and allowance not exceeded
-If contract don't have enough USDC, BUIDL redemption flow will be triggered
-Burns mToken from the user.
-Transfers fee in mToken to feeReceiver
-Transfers tokenOut to user._
+updates `minExpectedAnswer` and `maxExpectedAnswer` values
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| tokenOut | address | token out address, always ignore |
-| amountMTokenIn | uint256 | amount of mToken to redeem |
-| minReceiveAmount | uint256 | minimum expected amount of tokenOut to receive (decimals 18) |
-| recipient | address | |
+| _maxExpectedAnswer | int256 | new max expected answer |
+| _minExpectedAnswer | int256 | new min expected answer |
-### _checkAndRedeemBUIDL
+### getDataInBase18
```solidity
-function _checkAndRedeemBUIDL(address tokenOut, uint256 amountTokenOut) internal
+function getDataInBase18() external view returns (uint256 answer)
```
-Check if contract have enough USDC balance for redeem
-if don't have trigger BUIDL redemption flow
+fetches answer from aggregator
+and converts it to the base18 precision
-#### Parameters
+#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| tokenOut | address | tokenOut address |
-| amountTokenOut | uint256 | amount of tokenOut |
+| answer | uint256 | fetched aggregator answer |
-## RedemptionVaultWithSwapper
+### contractAdminRole
-Smart contract that handles mToken redemption.
-In case of insufficient liquidity it uses a RV from a different
-Midas product to fulfill instant redemption.
+```solidity
+function contractAdminRole() public view returns (bytes32)
+```
-_mToken1 - is a main mToken of this vault
-mToken2 - is a token of a second vault that is triggered when
-current vault don`t have enough liquidity_
+_main admin role for the contract_
+
+## IDataFeed
-### mTbillRedemptionVault
+### getDataInBase18
```solidity
-contract IRedemptionVault mTbillRedemptionVault
+function getDataInBase18() external view returns (uint256 answer)
```
-mToken1 redemption vault
+fetches answer from aggregator
+and converts it to the base18 precision
+
+#### Return Values
-_The naming was not altered to maintain
-compatibility with the currently deployed contracts._
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| answer | uint256 | fetched aggregator answer |
-### liquidityProvider
+## IMidasAccessControl
-```solidity
-address liquidityProvider
-```
+### SetUserFacingRoleParams
-### initialize
+Set user facing role params
```solidity
-function initialize(address _ac, struct MTokenInitParams _mTokenInitParams, struct ReceiversInitParams _receiversInitParams, struct InstantInitParams _instantInitParams, address _sanctionsList, uint256 _variationTolerance, uint256 _minAmount, struct FiatRedeptionInitParams _fiatRedemptionInitParams, address _requestRedeemer, address _mTbillRedemptionVault, address _liquidityProvider) external
+struct SetUserFacingRoleParams {
+ bytes32 role;
+ bool enabled;
+}
```
-upgradeable pattern contract`s initializer
+### SetGrantOperatorRoleParams
-#### Parameters
+Set function access grant operator params
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _ac | address | address of MidasAccessControll contract |
-| _mTokenInitParams | struct MTokenInitParams | init params for mToken1 |
-| _receiversInitParams | struct ReceiversInitParams | init params for receivers |
-| _instantInitParams | struct InstantInitParams | init params for instant operations |
-| _sanctionsList | address | address of sanctionsList contract |
-| _variationTolerance | uint256 | percent of prices diviation 1% = 100 |
-| _minAmount | uint256 | basic min amount for operations |
-| _fiatRedemptionInitParams | struct FiatRedeptionInitParams | params fiatAdditionalFee, fiatFlatFee, minFiatRedeemAmount |
-| _requestRedeemer | address | address is designated for standard redemptions, allowing tokens to be pulled from this address |
-| _mTbillRedemptionVault | address | mToken2 redemptionVault address |
-| _liquidityProvider | address | liquidity provider for pull mToken2 |
+```solidity
+struct SetGrantOperatorRoleParams {
+ uint32 delay;
+ bytes4 functionSelector;
+ address operator;
+ bool enabled;
+}
+```
-### _redeemInstant
+### SetPermissionRoleParams
+
+Set function permission params
```solidity
-function _redeemInstant(address tokenOut, uint256 amountMTokenIn, uint256 minReceiveAmount, address recipient) internal returns (struct RedemptionVault.CalcAndValidateRedeemResult calcResult, uint256 amountTokenOutWithoutFee)
+struct SetPermissionRoleParams {
+ address account;
+ bool enabled;
+}
```
-_redeem mToken1 to tokenOut if daily limit and allowance not exceeded
-If contract don't have enough tokenOut, mToken1 will swap to mToken2 and redeem on mToken2 vault
-Burns mToken1 from the user, if swap need mToken1 just tranfers to contract.
-Transfers fee in mToken1 to feeReceiver
-Transfers tokenOut to user._
+### GrantRoleMultParams
-#### Parameters
+Grant role params
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| tokenOut | address | token out address |
-| amountMTokenIn | uint256 | amount of mToken1 to redeem |
-| minReceiveAmount | uint256 | minimum expected amount of tokenOut to receive (decimals 18) |
-| recipient | address | |
+```solidity
+struct GrantRoleMultParams {
+ bytes32 role;
+ address account;
+ uint32 delay;
+}
+```
-### setLiquidityProvider
+### RevokeRoleMultParams
+
+Revoke role params
```solidity
-function setLiquidityProvider(address provider) external
+struct RevokeRoleMultParams {
+ bytes32 role;
+ address account;
+}
```
-sets new liquidity provider address
+### SetRoleDelayParams
-#### Parameters
+Set role delay params
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| provider | address | new liquidity provider address |
+```solidity
+struct SetRoleDelayParams {
+ bytes32 role;
+ uint32 delay;
+}
+```
-### setSwapperVault
+### SetUserFacingRole
```solidity
-function setSwapperVault(address newVault) external
+event SetUserFacingRole(bytes32 role, bool enabled)
```
-sets new underlying vault for swapper
-
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| newVault | address | |
+| role | bytes32 | OZ role for the scope |
+| enabled | bool | whether that role is user facing |
-### _swapMToken1ToMToken2
+### SetGrantOperatorRole
```solidity
-function _swapMToken1ToMToken2(uint256 mToken1Amount) internal returns (uint256 mTokenAmount)
+event SetGrantOperatorRole(bytes32 masterRole, address targetContract, address operator, bytes4 functionSelector, bool enabled)
```
-Transfers mToken1 to liquidity provider
-Transfers mToken2 from liquidity provider to contract
-Returns amount on mToken2 using exchange rates
-
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| mToken1Amount | uint256 | mToken1 token amount (decimals 18) |
-
-## RedemptionVaultWithUSTB
-
-Smart contract that handles redemptions using USTB
+| masterRole | bytes32 | OZ role for the scope |
+| targetContract | address | contract whose function is scoped. |
+| operator | address | address that may call `setFunctionPermission` for this scope. |
+| functionSelector | bytes4 | selector of the scoped function. |
+| enabled | bool | grant or revoke grant-operator status. |
-### ustbRedemption
+### SetPermissionRole
```solidity
-contract IUSTBRedemption ustbRedemption
+event SetPermissionRole(bytes32 masterRole, address targetContract, address account, bytes4 functionSelector, bool enabled)
```
-USTB redemption contract address
+#### Parameters
-_Used to handle USTB redemptions when vault has insufficient USDC_
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| masterRole | bytes32 | OZ role for the scope |
+| targetContract | address | contract whose function is scoped. |
+| account | address | address receiving or losing permission |
+| functionSelector | bytes4 | selector of the scoped function. |
+| enabled | bool | grant or revoke |
-### initialize
+### SetDefaultDelay
```solidity
-function initialize(address _ac, struct MTokenInitParams _mTokenInitParams, struct ReceiversInitParams _receiversInitParams, struct InstantInitParams _instantInitParams, address _sanctionsList, uint256 _variationTolerance, uint256 _minAmount, struct FiatRedeptionInitParams _fiatRedemptionInitParams, address _requestRedeemer, address _ustbRedemption) external
+event SetDefaultDelay(uint32 defaultDelay)
```
-upgradeable pattern contract`s initializer
-
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| _ac | address | address of MidasAccessControll contract |
-| _mTokenInitParams | struct MTokenInitParams | init params for mToken |
-| _receiversInitParams | struct ReceiversInitParams | init params for receivers |
-| _instantInitParams | struct InstantInitParams | init params for instant operations |
-| _sanctionsList | address | address of sanctionsList contract |
-| _variationTolerance | uint256 | percent of prices diviation 1% = 100 |
-| _minAmount | uint256 | basic min amount for operations |
-| _fiatRedemptionInitParams | struct FiatRedeptionInitParams | params fiatAdditionalFee, fiatFlatFee, minFiatRedeemAmount |
-| _requestRedeemer | address | address is designated for standard redemptions, allowing tokens to be pulled from this address |
-| _ustbRedemption | address | USTB redemption contract address |
+| defaultDelay | uint32 | new default delay |
-### _redeemInstant
+### SetRoleDelays
```solidity
-function _redeemInstant(address tokenOut, uint256 amountMTokenIn, uint256 minReceiveAmount, address recipient) internal returns (struct RedemptionVault.CalcAndValidateRedeemResult calcResult, uint256 amountTokenOutWithoutFee)
+event SetRoleDelays(struct IMidasAccessControl.SetRoleDelayParams[] params)
```
-_Redeem mToken to the selected payment token if daily limit and allowance are not exceeded.
-If USDC is the payment token and the contract doesn't have enough USDC, the USTB redemption flow will be triggered for the missing amount.
-Burns mToken from the user.
-Transfers fee in mToken to feeReceiver.
-Transfers tokenOut to user._
-
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| tokenOut | address | token out address |
-| amountMTokenIn | uint256 | amount of mToken to redeem |
-| minReceiveAmount | uint256 | minimum expected amount of tokenOut to receive (decimals 18) |
-| recipient | address | |
+| params | struct IMidasAccessControl.SetRoleDelayParams[] | array of SetRoleDelayParams |
-### _checkAndRedeemUSTB
+### SetRoleDelay
```solidity
-function _checkAndRedeemUSTB(address tokenOut, uint256 amountTokenOut) internal
+event SetRoleDelay(bytes32 role, uint32 delay)
```
-Check if contract has enough USDC balance for redeem
-if not, trigger USTB redemption flow to redeem exactly the missing amount
-
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| tokenOut | address | tokenOut address |
-| amountTokenOut | uint256 | amount of tokenOut needed |
-
-## ManageableVault
-
-Contract with base Vault methods
+| role | bytes32 | role id |
+| delay | uint32 | delay value |
-### MANUAL_FULLFILMENT_TOKEN
+### EmptyArray
```solidity
-address MANUAL_FULLFILMENT_TOKEN
+error EmptyArray()
```
-address that represents off-chain USD bank transfer
+when the array is empty
-### STABLECOIN_RATE
+### MismatchArrays
```solidity
-uint256 STABLECOIN_RATE
+error MismatchArrays(uint256 length1, uint256 length2)
```
-stable coin static rate 1:1 USD in 18 decimals
-
-### currentRequestId
+when the arrays have different lengths
-```solidity
-struct Counters.Counter currentRequestId
-```
+#### Parameters
-last request id
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| length1 | uint256 | length of the first array |
+| length2 | uint256 | length of the second array |
-### ONE_HUNDRED_PERCENT
+### Forbidden
```solidity
-uint256 ONE_HUNDRED_PERCENT
+error Forbidden()
```
-100 percent with base 100
-
-_for example, 10% will be (10 * 100)%_
+error when the function is forbidden
-### MAX_UINT
+### CannotRevokeFromSelf
```solidity
-uint256 MAX_UINT
+error CannotRevokeFromSelf(bytes32 role, address account)
```
-### mToken
+when the role is being revoked from the self
-```solidity
-contract IMTbill mToken
-```
+#### Parameters
-mToken token
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| role | bytes32 | role to be revoked |
+| account | address | account to be revoked |
-### mTokenDataFeed
+### DelayIsAlreadySet
```solidity
-contract IDataFeed mTokenDataFeed
+error DelayIsAlreadySet()
```
-mToken data feed contract
+when the delay is already set
-### tokensReceiver
+### RoleAdminMismatch
```solidity
-address tokensReceiver
+error RoleAdminMismatch(bytes32 role, bytes32 adminRole)
```
-address to which tokens and mTokens will be sent
-
-### instantFee
+when the role admin mismatch
-```solidity
-uint256 instantFee
-```
+#### Parameters
-_fee for initial operations 1% = 100_
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| role | bytes32 | role to be revoked |
+| adminRole | bytes32 | admin role |
-### instantDailyLimit
+### setUserFacingRoleMult
```solidity
-uint256 instantDailyLimit
+function setUserFacingRoleMult(struct IMidasAccessControl.SetUserFacingRoleParams[] params) external
```
-_daily limit for initial operations
-if user exceed this limit he will need
-to create requests_
+Enable or disable which OZ role may administer function-access scopes for that role.
-### dailyLimits
+_Only `DEFAULT_ADMIN_ROLE` can call this function.
+Prevents unrelated role admins from spamming access mappings._
-```solidity
-mapping(uint256 => uint256) dailyLimits
-```
+#### Parameters
-_mapping days (number from 1970) to limit amount_
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| params | struct IMidasAccessControl.SetUserFacingRoleParams[] | array of SetUserFacingRoleParams |
-### feeReceiver
+### setGrantOperatorRoleMult
```solidity
-address feeReceiver
+function setGrantOperatorRoleMult(address targetContract, struct IMidasAccessControl.SetGrantOperatorRoleParams[] params) external
```
-address to which fees will be sent
+Add or remove a grant operator for a specific contract function scope.
-### variationTolerance
+_`targetContract` must implement `IMidasAccessControlManaged` interface;
+Caller must hold `contractAdminRole` of a target contract;_
-```solidity
-uint256 variationTolerance
-```
+#### Parameters
-variation tolerance of tokenOut rates for "safe" requests approve
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| targetContract | address | scoped contract |
+| params | struct IMidasAccessControl.SetGrantOperatorRoleParams[] | array of SetGrantOperatorRoleParams |
-### waivedFeeRestriction
+### setPermissionRoleMult
```solidity
-mapping(address => bool) waivedFeeRestriction
+function setPermissionRoleMult(address targetContract, bytes4 functionSelector, uint32 delay, struct IMidasAccessControl.SetPermissionRoleParams[] params) external
```
-address restriction with zero fees
+Grant or revoke function access for an account
-### _paymentTokens
+_caller must be a grant operator for the scope or have the master role
+target contract must implement `IMidasAccessControlManaged` interface;_
-```solidity
-struct EnumerableSetUpgradeable.AddressSet _paymentTokens
-```
+#### Parameters
-_tokens that can be used as USD representation_
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| targetContract | address | scoped contract |
+| functionSelector | bytes4 | scoped function |
+| delay | uint32 | delay value |
+| params | struct IMidasAccessControl.SetPermissionRoleParams[] | array of SetPermissionRoleParams |
-### tokensConfig
+### setPermissionRoleMult
```solidity
-mapping(address => struct TokenConfig) tokensConfig
+function setPermissionRoleMult(bytes32 masterRole, address targetContract, bytes4 functionSelector, uint32 delay, struct IMidasAccessControl.SetPermissionRoleParams[] params) external
```
-mapping, token address to token config
+Grant or revoke function access for an account
-### minAmount
+_caller must be a grant operator for the scope or have the master role_
-```solidity
-uint256 minAmount
-```
+#### Parameters
-basic min operations amount
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| masterRole | bytes32 | OZ role for the scope |
+| targetContract | address | scoped contract |
+| functionSelector | bytes4 | scoped function |
+| delay | uint32 | delay value |
+| params | struct IMidasAccessControl.SetPermissionRoleParams[] | array of SetPermissionRoleParams |
-### isFreeFromMinAmount
+### grantRole
```solidity
-mapping(address => bool) isFreeFromMinAmount
+function grantRole(bytes32 role, address account, uint32 delay) external
```
-mapping, user address => is free frmo min amounts
-
-### onlyVaultAdmin
+Grant a role to an account with a delay
-```solidity
-modifier onlyVaultAdmin()
-```
+#### Parameters
-_checks that msg.sender do have a vaultRole() role_
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| role | bytes32 | role id |
+| account | address | account to grant the role to |
+| delay | uint32 | delay value |
-### __ManageableVault_init
+### grantRoleMult
```solidity
-function __ManageableVault_init(address _ac, struct MTokenInitParams _mTokenInitParams, struct ReceiversInitParams _receiversInitParams, struct InstantInitParams _instantInitParams, address _sanctionsList, uint256 _variationTolerance, uint256 _minAmount) internal
+function grantRoleMult(struct IMidasAccessControl.GrantRoleMultParams[] params) external
```
-_upgradeable pattern contract`s initializer_
+grant multiple roles to multiple users in one transaction
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| _ac | address | address of MidasAccessControll contract |
-| _mTokenInitParams | struct MTokenInitParams | init params for mToken |
-| _receiversInitParams | struct ReceiversInitParams | init params for receivers |
-| _instantInitParams | struct InstantInitParams | init params for instant operations |
-| _sanctionsList | address | address of sanctionsList contract |
-| _variationTolerance | uint256 | percent of prices diviation 1% = 100 |
-| _minAmount | uint256 | basic min amount for operations |
+| params | struct IMidasAccessControl.GrantRoleMultParams[] | array of GrantRoleMultParams |
-### withdrawToken
+### revokeRoleMult
```solidity
-function withdrawToken(address token, uint256 amount, address withdrawTo) external
+function revokeRoleMult(struct IMidasAccessControl.RevokeRoleMultParams[] params) external
```
-withdraws `amount` of a given `token` from the contract.
-can be called only from permissioned actor.
+revoke multiple roles from multiple users in one transaction
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| token | address | token address |
-| amount | uint256 | token amount |
-| withdrawTo | address | withdraw destination address |
+| params | struct IMidasAccessControl.RevokeRoleMultParams[] | array of RevokeRoleMultParams |
-### addPaymentToken
+### setDefaultDelay
```solidity
-function addPaymentToken(address token, address dataFeed, uint256 tokenFee, bool stable) external
+function setDefaultDelay(uint32 _defaultDelay) external
```
-adds a token to the stablecoins list.
-can be called only from permissioned actor.
-
-_reverts if token is already added_
+Sets the default delay
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| token | address | token address |
-| dataFeed | address | dataFeed address |
-| tokenFee | uint256 | |
-| stable | bool | is stablecoin flag |
+| _defaultDelay | uint32 | default delay in seconds |
-### removePaymentToken
+### setRoleDelayMult
```solidity
-function removePaymentToken(address token) external
+function setRoleDelayMult(struct IMidasAccessControl.SetRoleDelayParams[] params) external
```
-removes a token from stablecoins list.
-can be called only from permissioned actor.
-
-_reverts if token is not presented_
+Sets timelock delay per role
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| token | address | token address |
+| params | struct IMidasAccessControl.SetRoleDelayParams[] | array of SetRoleDelayParams |
-### changeTokenAllowance
+### setRoleAdmin
```solidity
-function changeTokenAllowance(address token, uint256 allowance) external
+function setRoleAdmin(bytes32 role, bytes32 newAdminRole) external
```
-set new token allowance.
-if MAX_UINT = infinite allowance
-prev allowance rewrites by new
-can be called only from permissioned actor.
+set the admin role for a specific role
-_reverts if new allowance zero_
+_can be called only by the address that holds `DEFAULT_ADMIN_ROLE`_
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| token | address | token address |
-| allowance | uint256 | new allowance (decimals 18) |
+| role | bytes32 | the role to set the admin role for |
+| newAdminRole | bytes32 | the new admin role |
-### changeTokenFee
+### isUserFacingRole
```solidity
-function changeTokenFee(address token, uint256 fee) external
+function isUserFacingRole(bytes32 role) external view returns (bool)
```
-set new token fee.
-can be called only from permissioned actor.
-
-_reverts if new fee > 100%_
+Whether `role` is user facing.
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| token | address | token address |
-| fee | uint256 | new fee percent 1% = 100 |
+| role | bytes32 | OZ role for the scope |
-### setVariationTolerance
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bool | enabled whether `role` is user facing |
+
+### isFunctionAccessGrantOperator
```solidity
-function setVariationTolerance(uint256 tolerance) external
+function isFunctionAccessGrantOperator(bytes32 masterRole, address targetContract, bytes4 functionSelector, address operator) external view returns (bool)
```
-set new prices diviation percent.
-can be called only from permissioned actor.
-
-_reverts if new tolerance zero_
+Whether `operator` may call `setFunctionPermission` for the function scope
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| tolerance | uint256 | new prices diviation percent 1% = 100 |
+| masterRole | bytes32 | OZ role for the scope |
+| targetContract | address | scoped contract |
+| functionSelector | bytes4 | scoped function |
+| operator | address | address checked for grant-operator status |
-### setMinAmount
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bool | allowed whether `operator` is a grant operator for the scope |
+
+### isFunctionAccessGrantOperator
```solidity
-function setMinAmount(uint256 newAmount) external
+function isFunctionAccessGrantOperator(bytes32 key, address operator) external view returns (bool)
```
-set new min amount.
-can be called only from permissioned actor.
+Whether `operator` may call `setFunctionPermission` for the function scope
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| newAmount | uint256 | min amount for operations in mToken |
+| key | bytes32 | operator permission key |
+| operator | address | address checked for grant-operator status |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bool | allowed whether `operator` is a grant operator for the scope |
-### addWaivedFeeAccount
+### hasFunctionPermission
```solidity
-function addWaivedFeeAccount(address account) external
+function hasFunctionPermission(bytes32 masterRole, address targetContract, bytes4 functionSelector, address account) external view returns (bool)
```
-adds a account to waived fee restriction.
-can be called only from permissioned actor.
-
-_reverts if account is already added_
+Whether `account` may call the scoped function on `targetContract`.
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| account | address | user address |
+| masterRole | bytes32 | OZ role for the scope |
+| targetContract | address | scoped contract |
+| functionSelector | bytes4 | scoped function |
+| account | address | address checked for permissio. |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bool | allowed whether `account` has function access for the scope |
-### removeWaivedFeeAccount
+### hasFunctionPermission
```solidity
-function removeWaivedFeeAccount(address account) external
+function hasFunctionPermission(bytes32 key, address account) external view returns (bool)
```
-removes a account from waived fee restriction.
-can be called only from permissioned actor.
-
-_reverts if account is already removed_
+Whether `account` has function access for the scope.
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| account | address | user address |
+| key | bytes32 | the base key for function permission mappings |
+| account | address | address checked for permission |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bool | allowed whether `account` has function access for the scope |
-### setFeeReceiver
+### permissionRoleKey
```solidity
-function setFeeReceiver(address receiver) external
+function permissionRoleKey(bytes32 masterRole, address targetContract, bytes4 functionSelector) external pure returns (bytes32)
```
-set new reciever for fees.
-can be called only from permissioned actor.
-
-_reverts address zero or equal address(this)_
+calculates the base key for function permission mappings
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| receiver | address | |
+| masterRole | bytes32 | OZ role |
+| targetContract | address | scoped contract |
+| functionSelector | bytes4 | scoped function of a `targetContract` |
-### setTokensReceiver
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bytes32 | key the base key for function permission mappings |
+
+### grantOperatorRoleKey
```solidity
-function setTokensReceiver(address receiver) external
+function grantOperatorRoleKey(bytes32 masterRole, address targetContract, bytes4 functionSelector) external pure returns (bytes32)
```
-set new reciever for tokens.
-can be called only from permissioned actor.
-
-_reverts address zero or equal address(this)_
+calculates the base key for function permission mappings
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| receiver | address | |
+| masterRole | bytes32 | OZ role |
+| targetContract | address | scoped contract |
+| functionSelector | bytes4 | scoped function of a `targetContract` |
-### setInstantFee
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bytes32 | key the base key for function permission mappings |
+
+### getRoleTimelockDelay
```solidity
-function setInstantFee(uint256 newInstantFee) external
+function getRoleTimelockDelay(bytes32 role, uint32 overrideDelay) external view returns (uint32 delay, bool isDefault)
```
-set operation fee percent.
-can be called only from permissioned actor.
+Returns timelock delay for a role
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| newInstantFee | uint256 | new instant operations fee percent 1& = 100 |
+| role | bytes32 | role id |
+| overrideDelay | uint32 | override delay for the invocation |
-### setInstantDailyLimit
-
-```solidity
-function setInstantDailyLimit(uint256 newInstantDailyLimit) external
-```
-
-set operation daily limit.
-can be called only from permissioned actor.
-
-#### Parameters
+#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| newInstantDailyLimit | uint256 | new operation daily limit (decimals 18) |
+| delay | uint32 | effective delay in seconds |
+| isDefault | bool | true if role uses default delay |
-### freeFromMinAmount
+### defaultDelay
```solidity
-function freeFromMinAmount(address user, bool enable) external
+function defaultDelay() external view returns (uint32 delay)
```
-frees given `user` from the minimal deposit
-amount validation in `initiateDepositRequest`
+Default timelock delay when role delay is not set
-#### Parameters
+#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| user | address | address of user |
-| enable | bool | |
+| delay | uint32 | delay in seconds |
-### getPaymentTokens
+### timelockManager
```solidity
-function getPaymentTokens() external view returns (address[])
+function timelockManager() external view returns (address)
```
-returns array of stablecoins supported by the vault
-can be called only from permissioned actor.
+address of the timelock manager
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | address[] | paymentTokens array of payment tokens |
+| [0] | address | timelockManager address of the timelock manager |
-### vaultRole
+### pauseManager
```solidity
-function vaultRole() public view virtual returns (bytes32)
+function pauseManager() external view returns (address)
```
-AC role of vault administrator
+address of the pause manager
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | bytes32 | role bytes32 role |
+| [0] | address | pauseManager address of the pause manager |
-### sanctionsListAdminRole
+## IMidasAccessControlManaged
+
+Interface for contracts that are managed by the MidasAccessControl
+
+### contractAdminRole
```solidity
-function sanctionsListAdminRole() public view virtual returns (bytes32)
+function contractAdminRole() external view returns (bytes32)
```
-AC role of sanctions list admin
-
-_address that have this role can use `setSanctionsList`_
+returns the role that can pause the contract
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | bytes32 | role bytes32 role |
+| [0] | bytes32 | role role descriptor |
-### pauseAdminRole
+## TimelockOperationStatus
+
+Timelock operation status
+
+_Computed status may differ from stored status (expiry, dispute period)._
```solidity
-function pauseAdminRole() public view returns (bytes32)
+enum TimelockOperationStatus {
+ NotExist,
+ Pending,
+ Paused,
+ ApprovedExecution,
+ ReadyToExecute,
+ ReadyToAbort,
+ Expired,
+ Aborted,
+ Executed,
+ ExecutedWithFailure
+}
```
-_virtual function to determine pauseAdmin role_
+## GetOperationStatusResult
-### _tokenTransferFromUser
+Operation details returned by `getOperationDetails`
```solidity
-function _tokenTransferFromUser(address token, address to, uint256 amount, uint256 tokenDecimals) internal
+struct GetOperationStatusResult {
+ enum TimelockOperationStatus status;
+ uint32 createdAt;
+ uint32 executionApprovedAt;
+ uint8 pauseReasonCode;
+ uint256 councilVersion;
+ address operationProposer;
+ address pauser;
+ bytes32 dataHash;
+ uint8 votesForExecution;
+ uint8 votesForVeto;
+ bool isSetCouncilOperation;
+}
```
-_do safeTransferFrom on a given token
-and converts `amount` from base18
-to amount with a correct precision. Sends tokens
-from `msg.sender` to `tokensReceiver`_
+## IMidasTimelockManager
+
+Interface for the MidasTimelockManager
+
+### ScheduleTimelockOperationParams
+
+Parameters for scheduling a timelock operation
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| token | address | address of token |
-| to | address | address of user |
-| amount | uint256 | amount of `token` to transfer from `user` (decimals 18) |
-| tokenDecimals | uint256 | token decimals |
-
-### _tokenTransferFromTo
```solidity
-function _tokenTransferFromTo(address token, address from, address to, uint256 amount, uint256 tokenDecimals) internal
+struct ScheduleTimelockOperationParams {
+ address target;
+ bytes data;
+}
```
-_do safeTransferFrom on a given token
-and converts `amount` from base18
-to amount with a correct precision._
+### SetMaxPendingOperationsPerProposer
+
+```solidity
+event SetMaxPendingOperationsPerProposer(uint256 maxPendingOperationsPerProposer)
+```
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| token | address | address of token |
-| from | address | address |
-| to | address | address |
-| amount | uint256 | amount of `token` to transfer from `user` |
-| tokenDecimals | uint256 | token decimals |
+| maxPendingOperationsPerProposer | uint256 | new limit |
-### _tokenTransferToUser
+### SetSecurityCouncil
```solidity
-function _tokenTransferToUser(address token, address to, uint256 amount, uint256 tokenDecimals) internal
+event SetSecurityCouncil(uint256 version, address[] members)
```
-_do safeTransfer on a given token
-and converts `amount` from base18
-to amount with a correct precision. Sends tokens
-from `contract` to `user`_
-
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| token | address | address of token |
-| to | address | address of user |
-| amount | uint256 | amount of `token` to transfer from `user` (decimals 18) |
-| tokenDecimals | uint256 | token decimals |
+| version | uint256 | new security council version |
+| members | address[] | council member addresses |
-### _tokenDecimals
+### ScheduleTimelockOperation
```solidity
-function _tokenDecimals(address token) internal view returns (uint8)
+event ScheduleTimelockOperation(address caller, bytes32 operationId)
```
-_retreives decimals of a given `token`_
-
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| token | address | address of token |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint8 | decimals decinmals value of a given `token` |
+| caller | address | operation proposer |
+| operationId | bytes32 | scheduled operation id |
-### _requireTokenExists
+### PauseTimelockOperation
```solidity
-function _requireTokenExists(address token) internal view virtual
+event PauseTimelockOperation(address caller, bytes32 operationId, uint8 pauseReasonCode, uint256 councilVersion)
```
-_checks that `token` is presented in `_paymentTokens`_
-
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| token | address | address of token |
+| caller | address | pauser address |
+| operationId | bytes32 | paused operation id |
+| pauseReasonCode | uint8 | pause reason code |
+| councilVersion | uint256 | security council version at pause |
-### _requireAndUpdateLimit
+### ExecuteTimelockOperation
```solidity
-function _requireAndUpdateLimit(uint256 amount) internal
+event ExecuteTimelockOperation(address caller, bytes32 operationId, bool success)
```
-_check if operation exceed daily limit and update limit data_
-
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| amount | uint256 | operation amount (decimals 18) |
+| caller | address | executor address |
+| operationId | bytes32 | executed operation id |
+| success | bool | true if operation executed successfully, false otherwise |
-### _requireAndUpdateAllowance
+### PausedProposalVoteCast
```solidity
-function _requireAndUpdateAllowance(address token, uint256 amount) internal
+event PausedProposalVoteCast(address caller, bytes32 operationId, bool votedForExecution)
```
-_check if operation exceed token allowance and update allowance_
-
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| token | address | address of token |
-| amount | uint256 | operation amount (decimals 18) |
+| caller | address | council member address |
+| operationId | bytes32 | operation id |
+| votedForExecution | bool | true for execution vote, false for veto vote |
-### _getFeeAmount
+### AbortTimelockOperation
```solidity
-function _getFeeAmount(address sender, address token, uint256 amount, bool isInstant, uint256 additionalFee) internal view returns (uint256)
+event AbortTimelockOperation(address caller, bytes32 operationId, enum TimelockOperationStatus status)
```
-_returns calculated fee amount depends on parameters
-if additionalFee not zero, token fee replaced with additionalFee_
-
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| sender | address | sender address |
-| token | address | token address |
-| amount | uint256 | amount of token (decimals 18) |
-| isInstant | bool | is instant operation |
-| additionalFee | uint256 | fee for fiat operations |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint256 | fee amount of input token |
+| caller | address | address that aborted the operation |
+| operationId | bytes32 | aborted operation id |
+| status | enum TimelockOperationStatus | status before abort |
-### _requireVariationTolerance
+### RolePreflightSucceeded
```solidity
-function _requireVariationTolerance(uint256 prevPrice, uint256 newPrice) internal view
+error RolePreflightSucceeded(bytes32 role, uint32 overrideDelay, bool roleIsFunctionOperator, bool validateFunctionRole)
```
-_check if prev and new prices diviation fit variationTolerance_
+Preflight call succeeded with role info
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| prevPrice | uint256 | previous rate |
-| newPrice | uint256 | new rate |
+| role | bytes32 | role used for the call |
+| overrideDelay | uint32 | override delay for the invocation |
+| roleIsFunctionOperator | bool | true if role is function operator |
+| validateFunctionRole | bool | true if function role should be validated |
-### _validateUserAccess
+### TimelockAlreadySet
```solidity
-function _validateUserAccess(address user) internal view
+error TimelockAlreadySet()
```
-### _truncate
+Timelock address is already set
+
+### UnexpectedOperationStatus
```solidity
-function _truncate(uint256 value, uint256 decimals) internal pure returns (uint256)
+error UnexpectedOperationStatus(enum TimelockOperationStatus actualStatus)
```
-_convert value to inputted decimals precision_
+Operation status is not valid for this action
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| value | uint256 | value for format |
-| decimals | uint256 | decimals |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint256 | converted amount |
+| actualStatus | enum TimelockOperationStatus | current operation status |
-### _validateFee
+### OperationNotPending
```solidity
-function _validateFee(uint256 fee, bool checkMin) internal pure
+error OperationNotPending()
```
-_check if fee <= 100% and check > 0 if needs_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| fee | uint256 | fee value |
-| checkMin | bool | if need to check minimum |
+Operation is not in the pending set
-### _validateAddress
+### OperationAlreadyPending
```solidity
-function _validateAddress(address addr, bool selfCheck) internal view
+error OperationAlreadyPending()
```
-_check if address not zero and not address(this)_
+Operation is already pending
-#### Parameters
+### TimelockOperationNotReady
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| addr | address | address to check |
-| selfCheck | bool | check if address not address(this) |
+```solidity
+error TimelockOperationNotReady()
+```
-### _getTokenRate
+Timelock delay has not passed yet
+
+### NotInSecurityCouncil
```solidity
-function _getTokenRate(address dataFeed, bool stable) internal view virtual returns (uint256)
+error NotInSecurityCouncil()
```
-_get token rate depends on data feed and stablecoin flag_
-
-#### Parameters
+Caller is not a security council member for this operation
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| dataFeed | address | address of dataFeed from token config |
-| stable | bool | is stablecoin |
+### AlreadyVoted
-## MidasInitializable
+```solidity
+error AlreadyVoted()
+```
-Base Initializable contract that implements constructor
-that calls _disableInitializers() to prevent
-initialization of implementation contract
+Council member already voted
-### constructor
+### NoTimelockDelayForRole
```solidity
-constructor() internal
+error NoTimelockDelayForRole()
```
-## WithSanctionsList
-
-Base contract that uses sanctions oracle from
-Chainalysis to check that user is not sanctioned
+Role has no timelock delay configured
-### sanctionsList
+### TooManyPendingOperations
```solidity
-address sanctionsList
+error TooManyPendingOperations()
```
-address of Chainalysis sanctions oracle
+Proposer has too many pending operations
-### SetSanctionsList
+### PendingSetCouncilOperationExists
```solidity
-event SetSanctionsList(address caller, address newSanctionsList)
+error PendingSetCouncilOperationExists()
```
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| caller | address | function caller (msg.sender) |
-| newSanctionsList | address | new address of `sanctionsList` |
+Pending set-council operation already exists
-### onlyNotSanctioned
+### InvalidSecurityCouncilMembersLength
```solidity
-modifier onlyNotSanctioned(address user)
+error InvalidSecurityCouncilMembersLength()
```
-_checks that a given `user` is not sanctioned_
+Security council size is out of allowed range
-### __WithSanctionsList_init
+### InvalidMaxPendingOperationsPerProposer
```solidity
-function __WithSanctionsList_init(address _accesControl, address _sanctionsList) internal
+error InvalidMaxPendingOperationsPerProposer()
```
-_upgradeable pattern contract`s initializer_
+Max pending operations value is invalid
-### __WithSanctionsList_init_unchained
+### PreflightCallUnexpectedSuccess
```solidity
-function __WithSanctionsList_init_unchained(address _sanctionsList) internal
+error PreflightCallUnexpectedSuccess()
```
-_upgradeable pattern contract`s initializer unchained_
+Target call should have reverted on preflight
-### setSanctionsList
+### InvalidPreflightError
```solidity
-function setSanctionsList(address newSanctionsList) external
+error InvalidPreflightError(bytes err)
```
-updates `sanctionsList` address.
-can be called only from permissioned actor that have
-`sanctionsListAdminRole()` role
+Preflight revert data is invalid
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| newSanctionsList | address | new sanctions list address |
+| err | bytes | revert bytes |
-### sanctionsListAdminRole
+### setMaxPendingOperationsPerProposer
```solidity
-function sanctionsListAdminRole() public view virtual returns (bytes32)
+function setMaxPendingOperationsPerProposer(uint256 _maxPendingOperationsPerProposer) external
```
-AC role of sanctions list admin
-
-_address that have this role can use `setSanctionsList`_
+Sets max pending operations per proposer
-#### Return Values
+#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | bytes32 | role bytes32 role |
-
-## Blacklistable
-
-Base contract that implements basic functions and modifiers
-to work with blacklistable
+| _maxPendingOperationsPerProposer | uint256 | new limit |
-### onlyNotBlacklisted
+### setSecurityCouncil
```solidity
-modifier onlyNotBlacklisted(address account)
+function setSecurityCouncil(address[] members) external
```
-_checks that a given `account` doesnt
-have BLACKLISTED_ROLE_
+Sets a new security council version
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| members | address[] | council member addresses |
-### __Blacklistable_init
+### bulkScheduleTimelockOperation
```solidity
-function __Blacklistable_init(address _accessControl) internal
+function bulkScheduleTimelockOperation(struct IMidasTimelockManager.ScheduleTimelockOperationParams[] params) external
```
-_upgradeable pattern contract`s initializer_
+Schedules multiple timelock operations
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| _accessControl | address | MidasAccessControl contract address |
+| params | struct IMidasTimelockManager.ScheduleTimelockOperationParams[] | array of schedule timelock operation parameters |
-### __Blacklistable_init_unchained
+### scheduleTimelockOperation
```solidity
-function __Blacklistable_init_unchained() internal
+function scheduleTimelockOperation(struct IMidasTimelockManager.ScheduleTimelockOperationParams params) external
```
-_upgradeable pattern contract`s initializer unchained_
+Schedules one timelock operation
-### _onlyNotBlacklisted
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| params | struct IMidasTimelockManager.ScheduleTimelockOperationParams | schedule timelock operation parameters |
+
+### executeTimelockOperation
```solidity
-function _onlyNotBlacklisted(address account) internal view
+function executeTimelockOperation(address target, bytes data, bool revertOnFailure) external
```
-_checks that a given `account` doesnt
-have BLACKLISTED_ROLE_
+Executes a scheduled timelock operation
-## Greenlistable
+#### Parameters
-Base contract that implements basic functions and modifiers
-to work with greenlistable
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| target | address | target contract |
+| data | bytes | operation data |
+| revertOnFailure | bool | true if execution should revert on failure |
-### GREENLIST_TOGGLER_ROLE
+### pauseOperation
```solidity
-bytes32 GREENLIST_TOGGLER_ROLE
+function pauseOperation(bytes32 operationId, uint8 pauseReasonCode) external
```
-actor that can change green list enable
+Pauses a pending operation
-### greenlistEnabled
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| operationId | bytes32 | operation id |
+| pauseReasonCode | uint8 | reason code set by pauser |
+
+### voteForVeto
```solidity
-bool greenlistEnabled
+function voteForVeto(bytes32 operationId) external
```
-is greenlist enabled
+Security council votes to abort the operation
-### SetGreenlistEnable
+_can vote even if member is already voted for execution_
-```solidity
-event SetGreenlistEnable(address sender, bool enable)
-```
+#### Parameters
-### onlyGreenlisted
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| operationId | bytes32 | operation id |
+
+### voteForExecution
```solidity
-modifier onlyGreenlisted(address account)
+function voteForExecution(bytes32 operationId) external
```
-_checks that a given `account`
-have `greenlistedRole()`_
+Security council votes to allow execution
-### onlyAlwaysGreenlisted
+_cannot vote if member is already voted for veto_
-```solidity
-modifier onlyAlwaysGreenlisted(address account)
-```
+#### Parameters
-_checks that a given `account`
-have `greenlistedRole()`
-do the check even if greenlist check is off_
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| operationId | bytes32 | operation id |
-### __Greenlistable_init
+### abortOperation
```solidity
-function __Greenlistable_init(address _accessControl) internal
+function abortOperation(bytes32 operationId) external
```
-_upgradeable pattern contract`s initializer_
+Aborts operation after veto quorum or expiry
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| _accessControl | address | MidasAccessControl contract address |
+| operationId | bytes32 | operation id |
-### __Greenlistable_init_unchained
+### getOriginalProposer
```solidity
-function __Greenlistable_init_unchained() internal
+function getOriginalProposer(address target, bytes data) external view returns (address)
```
-_upgradeable pattern contract`s initializer unchained_
-
-### setGreenlistEnable
+Returns original proposer for a pending operation
-```solidity
-function setGreenlistEnable(bool enable) external
-```
+#### Parameters
-enable or disable greenlist.
-can be called only from permissioned actor.
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| target | address | target contract |
+| data | bytes | operation data |
-#### Parameters
+#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| enable | bool | enable |
+| [0] | address | proposer address |
-### greenlistedRole
+### councilQuorum
```solidity
-function greenlistedRole() public view virtual returns (bytes32)
+function councilQuorum(uint256 version) external view returns (uint8 quorum)
```
-AC role of a greenlist
+Votes needed for council quorum at a version
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| version | uint256 | security council version |
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | bytes32 | role bytes32 role |
+| quorum | uint8 | required votes |
-### greenlistTogglerRole
+### getCouncilMemberVoteStatus
```solidity
-function greenlistTogglerRole() public view virtual returns (bytes32)
+function getCouncilMemberVoteStatus(bytes32 operationId, address councilMember) external view returns (bool votedForExecution, bool votedForVeto)
```
-AC role of a greenlist
+Whether a council member voted on an operation
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| operationId | bytes32 | operation id |
+| councilMember | address | member address |
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | bytes32 | role bytes32 role |
+| votedForExecution | bool | true if voted for execution |
+| votedForVeto | bool | true if voted for veto |
-### _onlyGreenlistToggler
+### getPendingOperations
```solidity
-function _onlyGreenlistToggler(address account) internal view
+function getPendingOperations() external view returns (bytes32[] operationIds)
```
-_checks that a given `account`
-have a `greenlistTogglerRole()`_
+Returns all pending operation ids
-## MidasAccessControl
+#### Return Values
-Smart contract that stores all roles for Midas project
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| operationIds | bytes32[] | pending operation ids |
-### initialize
+### getOperationDetails
```solidity
-function initialize() external
+function getOperationDetails(bytes32 operationId) external view returns (struct GetOperationStatusResult result)
```
-upgradeable pattern contract`s initializer
+Returns full operation details
-### grantRoleMult
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| operationId | bytes32 | operation id |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| result | struct GetOperationStatusResult | operation details |
+
+### getOperationStatus
```solidity
-function grantRoleMult(bytes32[] roles, address[] addresses) external
+function getOperationStatus(bytes32 operationId) external view returns (enum TimelockOperationStatus status)
```
-grant multiple roles to multiple users
-in one transaction
-
-_length`s of 2 arays should match_
+Returns operation status (with expiry/dispute rules applied)
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| roles | bytes32[] | array of bytes32 roles |
-| addresses | address[] | array of user addresses |
+| operationId | bytes32 | operation id |
-### revokeRoleMult
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| status | enum TimelockOperationStatus | current status |
+
+### getOperationStatusRaw
```solidity
-function revokeRoleMult(bytes32[] roles, address[] addresses) external
+function getOperationStatusRaw(bytes32 operationId) external view returns (enum TimelockOperationStatus status)
```
-revoke multiple roles from multiple users
-in one transaction
-
-_length`s of 2 arays should match_
+Returns stored operation status without adjustments
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| roles | bytes32[] | array of bytes32 roles |
-| addresses | address[] | array of user addresses |
+| operationId | bytes32 | operation id |
-### renounceRole
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| status | enum TimelockOperationStatus | stored status |
+
+### getSecurityCouncilMembers
```solidity
-function renounceRole(bytes32, address) public pure
+function getSecurityCouncilMembers(uint256 version) external view returns (address[] members)
```
-## MidasAccessControlRoles
+Returns security council members for a version
-Base contract that stores all roles descriptors
+#### Parameters
-### GREENLIST_OPERATOR_ROLE
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| version | uint256 | security council version |
-```solidity
-bytes32 GREENLIST_OPERATOR_ROLE
-```
+#### Return Values
-actor that can change green list statuses of addresses
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| members | address[] | member addresses |
-### BLACKLIST_OPERATOR_ROLE
+### getOperationId
```solidity
-bytes32 BLACKLIST_OPERATOR_ROLE
+function getOperationId(address target, bytes data) external view returns (bytes32 operationId)
```
-actor that can change black list statuses of addresses
+Returns operation id for target and data
+
+#### Parameters
-### M_TBILL_MINT_OPERATOR_ROLE
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| target | address | target contract |
+| data | bytes | operation data |
-```solidity
-bytes32 M_TBILL_MINT_OPERATOR_ROLE
-```
+#### Return Values
-actor that can mint mTBILL
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| operationId | bytes32 | operation id |
-### M_TBILL_BURN_OPERATOR_ROLE
+### getTargetRole
```solidity
-bytes32 M_TBILL_BURN_OPERATOR_ROLE
+function getTargetRole(address target, bytes data, address proposer) external view returns (bytes32 role, uint32 overrideDelay)
```
-actor that can burn mTBILL
+_gets the target role for a given operation_
-### M_TBILL_PAUSE_OPERATOR_ROLE
+#### Parameters
-```solidity
-bytes32 M_TBILL_PAUSE_OPERATOR_ROLE
-```
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| target | address | target contract |
+| data | bytes | operation data |
+| proposer | address | operation proposer address |
+
+#### Return Values
-actor that can pause mTBILL
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| role | bytes32 | target role |
+| overrideDelay | uint32 | override delay for the invocation |
-### DEPOSIT_VAULT_ADMIN_ROLE
+### isInSecurityCouncil
```solidity
-bytes32 DEPOSIT_VAULT_ADMIN_ROLE
+function isInSecurityCouncil(uint256 version, address account) external view returns (bool)
```
-actor that have admin rights in deposit vault
+Checks if an account is in the security council for a given version
-### REDEMPTION_VAULT_ADMIN_ROLE
+#### Parameters
-```solidity
-bytes32 REDEMPTION_VAULT_ADMIN_ROLE
-```
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| version | uint256 | security council version |
+| account | address | account to check |
-actor that have admin rights in redemption vault
+#### Return Values
-### GREENLISTED_ROLE
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bool | true if the account is in the security council |
+
+### timelock
```solidity
-bytes32 GREENLISTED_ROLE
+function timelock() external view returns (address timelockAddress)
```
-actor that is greenlisted
+Timelock controller address
-### BLACKLISTED_ROLE
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| timelockAddress | address | timelock controller |
+
+### maxPendingOperationsPerProposer
```solidity
-bytes32 BLACKLISTED_ROLE
+function maxPendingOperationsPerProposer() external view returns (uint256)
```
-actor that is blacklisted
+Max pending operations per proposer
-## Pausable
+#### Return Values
-Base contract that implements basic functions and modifiers
-with pause functionality
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | value current limit |
-### fnPaused
+### securityCouncilVersion
```solidity
-mapping(bytes4 => bool) fnPaused
+function securityCouncilVersion() external view returns (uint256)
```
-### PauseFn
+Current security council version
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | version council version |
+
+### dataHashIndexes
```solidity
-event PauseFn(address caller, bytes4 fn)
+function dataHashIndexes(bytes32 dataHash) external view returns (uint256)
```
+Data hash index used for operation id salt
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| caller | address | caller address (msg.sender) |
-| fn | bytes4 | function id |
+| dataHash | bytes32 | operation data hash |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | index current index for this data hash |
-### UnpauseFn
+### proposerPendingOperationsCount
```solidity
-event UnpauseFn(address caller, bytes4 fn)
+function proposerPendingOperationsCount(address proposer) external view returns (uint256)
```
+Pending operations count for a proposer
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| caller | address | caller address (msg.sender) |
-| fn | bytes4 | function id |
+| proposer | address | proposer address |
-### whenFnNotPaused
+#### Return Values
-```solidity
-modifier whenFnNotPaused(bytes4 fn)
-```
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | count pending count |
-### onlyPauseAdmin
+### pendingSetCouncilOperationId
```solidity
-modifier onlyPauseAdmin()
+function pendingSetCouncilOperationId() external view returns (bytes32)
```
-_checks that a given `account`
-has a determinedPauseAdminRole_
+Pending set-security-council operation id, if any
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bytes32 | operationId operation id or zero |
+
+## DecimalsCorrectionLibrary
-### __Pausable_init
+### convert
```solidity
-function __Pausable_init(address _accessControl) internal
+function convert(uint256 originalAmount, uint256 originalDecimals, uint256 decidedDecimals) internal pure returns (uint256)
```
-_upgradeable pattern contract`s initializer_
+_converts `originalAmount` with `originalDecimals` into
+amount with `decidedDecimals`_
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| _accessControl | address | MidasAccessControl contract address |
+| originalAmount | uint256 | amount to convert |
+| originalDecimals | uint256 | decimals of the original amount |
+| decidedDecimals | uint256 | decimals for the output amount |
-### pause
+#### Return Values
-```solidity
-function pause() external
-```
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | amount converted amount with `decidedDecimals` |
-### unpause
+### convertFromBase18
```solidity
-function unpause() external
+function convertFromBase18(uint256 originalAmount, uint256 decidedDecimals) internal pure returns (uint256)
```
-### pauseFn
+_converts `originalAmount` with decimals 18 into
+amount with `decidedDecimals`_
-```solidity
-function pauseFn(bytes4 fn) external
-```
+#### Parameters
-_pause specific function_
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| originalAmount | uint256 | amount to convert |
+| decidedDecimals | uint256 | decimals for the output amount |
-#### Parameters
+#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| fn | bytes4 | function id |
+| [0] | uint256 | amount converted amount with `decidedDecimals` |
-### unpauseFn
+### convertToBase18
```solidity
-function unpauseFn(bytes4 fn) external
+function convertToBase18(uint256 originalAmount, uint256 originalDecimals) internal pure returns (uint256)
```
-_unpause specific function_
+_converts `originalAmount` with `originalDecimals` into
+amount with decimals 18_
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| fn | bytes4 | function id |
+| originalAmount | uint256 | amount to convert |
+| originalDecimals | uint256 | decimals of the original amount |
-### pauseAdminRole
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | amount converted amount with 18 decimals |
+
+## MidasAuthLibrary
+
+### NoFunctionPermission
```solidity
-function pauseAdminRole() public view virtual returns (bytes32)
+error NoFunctionPermission(bytes32 roleUsed, bytes4 functionSelector, address account)
```
-_virtual function to determine pauseAdmin role_
+error when the function permission is not found
-## WithMidasAccessControl
+#### Parameters
-Base contract that consumes MidasAccessControl
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| roleUsed | bytes32 | role used |
+| functionSelector | bytes4 | function selector |
+| account | address | account |
-### DEFAULT_ADMIN_ROLE
+### NotGreenlisted
```solidity
-bytes32 DEFAULT_ADMIN_ROLE
+error NotGreenlisted(address account, bytes32 greenlistedRole)
```
-admin role
-
-### accessControl
+error when the account is not greenlisted
-```solidity
-contract MidasAccessControl accessControl
-```
+#### Parameters
-MidasAccessControl contract address
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| account | address | account |
+| greenlistedRole | bytes32 | greenlisted role |
-### onlyRole
+### Blacklisted
```solidity
-modifier onlyRole(bytes32 role, address account)
+error Blacklisted(bytes32 blacklistedRole, address account)
```
-_checks that given `address` have `role`_
-
-### onlyNotRole
+error when the account is blacklisted
-```solidity
-modifier onlyNotRole(bytes32 role, address account)
-```
+#### Parameters
-_checks that given `address` do not have `role`_
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| blacklistedRole | bytes32 | blacklisted role |
+| account | address | account |
-### __WithMidasAccessControl_init
+### SenderIsNotTimelock
```solidity
-function __WithMidasAccessControl_init(address _accessControl) internal
+error SenderIsNotTimelock(bytes32 roleUsed, bytes4 functionSelector, address sender)
```
-_upgradeable pattern contract`s initializer_
-
-### _onlyRole
+error when the sender is not the timelock
-```solidity
-function _onlyRole(bytes32 role, address account) internal view
-```
+#### Parameters
-_checks that given `address` have `role`_
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| roleUsed | bytes32 | role used |
+| functionSelector | bytes4 | function selector |
+| sender | address | sender |
-### _onlyNotRole
+### UserFacingRoleNotAllowed
```solidity
-function _onlyNotRole(bytes32 role, address account) internal view
+error UserFacingRoleNotAllowed(bytes32 role)
```
-_checks that given `address` do not have `role`_
+error when the user facing role is not allowed
-## EUsdMidasAccessControlRoles
+#### Parameters
-Base contract that stores all roles descriptors for eUSD contracts
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| role | bytes32 | role |
-### E_USD_VAULT_ROLES_OPERATOR
+### InvalidDelay
```solidity
-bytes32 E_USD_VAULT_ROLES_OPERATOR
+error InvalidDelay()
```
-actor that can manage vault admin roles
+error when the delay is invalid
-### E_USD_DEPOSIT_VAULT_ADMIN_ROLE
+### DEFAULT_GREENLISTED_ROLE
```solidity
-bytes32 E_USD_DEPOSIT_VAULT_ADMIN_ROLE
+bytes32 DEFAULT_GREENLISTED_ROLE
```
-actor that can manage EUsdDepositVault
+default role for greenlisted actor
-### E_USD_REDEMPTION_VAULT_ADMIN_ROLE
+### DEFAULT_BLACKLISTED_ROLE
```solidity
-bytes32 E_USD_REDEMPTION_VAULT_ADMIN_ROLE
+bytes32 DEFAULT_BLACKLISTED_ROLE
```
-actor that can manage EUsdRedemptionVault
+default role for blacklisted actor
-### E_USD_GREENLIST_OPERATOR_ROLE
+### NO_DELAY
```solidity
-bytes32 E_USD_GREENLIST_OPERATOR_ROLE
+uint32 NO_DELAY
```
-actor that can change eUSD green list statuses of addresses
+timelock value that represents no delay
-### E_USD_GREENLISTED_ROLE
+### NULL_DELAY
```solidity
-bytes32 E_USD_GREENLISTED_ROLE
+uint32 NULL_DELAY
```
-actor that is greenlisted in eUSD
-
-## EUsdRedemptionVault
+timelock value that represents non-set delay
-Smart contract that handles eUSD redeeming
-
-### vaultRole
+### MAX_DELAY
```solidity
-function vaultRole() public pure returns (bytes32)
+uint32 MAX_DELAY
```
-### greenlistedRole
+maximum delay for a role
+
+### validateFunctionAccessWithTimelock
```solidity
-function greenlistedRole() public pure returns (bytes32)
+function validateFunctionAccessWithTimelock(contract IMidasAccessControl accessControl, bytes32 contractAdminRole, uint32 overrideDelay, bool roleIsFunctionOperatorRole, address accountToCheck, bool validateFunctionRole) internal view returns (address)
```
-AC role of a greenlist
+_validates that the function access is valid with timelock_
-#### Return Values
+#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | bytes32 | role bytes32 role |
+| accessControl | contract IMidasAccessControl | access control contract |
+| contractAdminRole | bytes32 | contract admin role |
+| overrideDelay | uint32 | |
+| roleIsFunctionOperatorRole | bool | whether the role is a function operator |
+| accountToCheck | address | account to check |
+| validateFunctionRole | bool | whether to validate the function role |
-## EUsdRedemptionVaultWithBUIDL
+#### Return Values
-Smart contract that handles eUSD redeeming
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | address | actualAccount actual account that has access to the function |
-### vaultRole
+### validateFunctionAccess
```solidity
-function vaultRole() public pure returns (bytes32)
+function validateFunctionAccess(contract IMidasAccessControl accessControl, address targetContract, bytes32 role, uint32 overrideDelay, bool roleIsFunctionOperatorRole, address account, bytes4 functionSelector, bool validateFunctionRole) internal view returns (bytes32)
```
-### greenlistedRole
+_validates that the function access is valid_
-```solidity
-function greenlistedRole() public pure returns (bytes32)
-```
+#### Parameters
-AC role of a greenlist
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| accessControl | contract IMidasAccessControl | access control contract |
+| targetContract | address | |
+| role | bytes32 | admin role |
+| overrideDelay | uint32 | override delay for the invocation |
+| roleIsFunctionOperatorRole | bool | whether the role is a function operator role |
+| account | address | account to check |
+| functionSelector | bytes4 | function selector |
+| validateFunctionRole | bool | whether to validate the function role |
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | bytes32 | role bytes32 role |
+| [0] | bytes32 | roleUsed role used to validate the function access |
-## HBUsdtMidasAccessControlRoles
-
-Base contract that stores all roles descriptors for hbUSDT contracts
-
-### HB_USDT_DEPOSIT_VAULT_ADMIN_ROLE
+### requireNotUserFacingRole
```solidity
-bytes32 HB_USDT_DEPOSIT_VAULT_ADMIN_ROLE
+function requireNotUserFacingRole(contract IMidasAccessControl accessControl, bytes32 role) internal view
```
-actor that can manage HBUsdtDepositVault
+_validates that the role is not a user facing role_
-### HB_USDT_REDEMPTION_VAULT_ADMIN_ROLE
-
-```solidity
-bytes32 HB_USDT_REDEMPTION_VAULT_ADMIN_ROLE
-```
+#### Parameters
-actor that can manage HBUsdtRedemptionVault
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| accessControl | contract IMidasAccessControl | access control contract |
+| role | bytes32 | role |
-### HB_USDT_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+### requireGreenlisted
```solidity
-bytes32 HB_USDT_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+function requireGreenlisted(contract IMidasAccessControl accessControl, address account, bytes32 greenlistedRole) internal view
```
-actor that can manage HBUsdtCustomAggregatorFeed and HBUsdtDataFeed
+_checks that a given `account` has `greenlistedRole`_
-## HBUsdtRedemptionVaultWithSwapper
+#### Parameters
-Smart contract that handles hbUSDT redemptions
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| accessControl | contract IMidasAccessControl | access control contract |
+| account | address | account |
+| greenlistedRole | bytes32 | greenlisted role |
-### vaultRole
+### requireNotBlacklisted
```solidity
-function vaultRole() public pure returns (bytes32)
+function requireNotBlacklisted(contract IMidasAccessControl accessControl, address account, bytes32 blacklistedRole) internal view
```
-## HBXautMidasAccessControlRoles
+_checks that a given `account` doesnt have `blacklistedRole`_
+
+#### Parameters
-Base contract that stores all roles descriptors for hbXAUt contracts
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| accessControl | contract IMidasAccessControl | access control contract |
+| account | address | account |
+| blacklistedRole | bytes32 | blacklisted role |
-### HB_XAUT_DEPOSIT_VAULT_ADMIN_ROLE
+### resolveAccessRole
```solidity
-bytes32 HB_XAUT_DEPOSIT_VAULT_ADMIN_ROLE
+function resolveAccessRole(contract IMidasAccessControl accessControl, bytes32 rootRole, bytes32 functionRoleKey, uint32 overrideDelay) internal view returns (bytes32 roleUsed)
```
-actor that can manage HBXautDepositVault
+_resolves the access role based on the shortest delay_
-### HB_XAUT_REDEMPTION_VAULT_ADMIN_ROLE
+#### Parameters
-```solidity
-bytes32 HB_XAUT_REDEMPTION_VAULT_ADMIN_ROLE
-```
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| accessControl | contract IMidasAccessControl | access control contract |
+| rootRole | bytes32 | root role |
+| functionRoleKey | bytes32 | function key |
+| overrideDelay | uint32 | override delay |
-actor that can manage HBXautRedemptionVault
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| roleUsed | bytes32 | role used to validate the function access |
-### HB_XAUT_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+### validateTimelockDelay
```solidity
-bytes32 HB_XAUT_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+function validateTimelockDelay(uint32 delay) internal view
```
-actor that can manage HBXautCustomAggregatorFeed and HBXautDataFeed
+validates that the delay is within the maximum delay
-## HBXautRedemptionVaultWithSwapper
+#### Parameters
-Smart contract that handles hbXAUt redemptions
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| delay | uint32 | delay to validate |
-### vaultRole
+### appendProposer
```solidity
-function vaultRole() public pure returns (bytes32)
+function appendProposer(bytes data, address proposer) internal pure returns (bytes)
```
-## HypeBtcMidasAccessControlRoles
+_appends the proposer to the data_
-Base contract that stores all roles descriptors for hypeBTC contracts
+#### Parameters
-### HYPE_BTC_DEPOSIT_VAULT_ADMIN_ROLE
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| data | bytes | operation data |
+| proposer | address | proposer address |
-```solidity
-bytes32 HYPE_BTC_DEPOSIT_VAULT_ADMIN_ROLE
-```
+#### Return Values
-actor that can manage HypeBtcDepositVault
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bytes | appended data |
-### HYPE_BTC_REDEMPTION_VAULT_ADMIN_ROLE
+### resolveProposer
```solidity
-bytes32 HYPE_BTC_REDEMPTION_VAULT_ADMIN_ROLE
+function resolveProposer(bytes data) internal pure returns (address proposer)
```
-actor that can manage HypeBtcRedemptionVault
+_resolves the proposer from the data_
+
+#### Parameters
-### HYPE_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| data | bytes | data |
-```solidity
-bytes32 HYPE_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
-```
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| proposer | address | proposer address |
-actor that can manage HypeBtcCustomAggregatorFeed and HypeBtcDataFeed
+## CompositeDataFeedToBandStdAdapter
-## HypeBtcRedemptionVaultWithSwapper
+Converts CompositeDataFeed to Band Protocol's IStdReference interface
-Smart contract that handles hypeBTC redemptions
+_Adapter that wraps CompositeDataFeed to provide Band Protocol standard reference data_
-### vaultRole
+### constructor
```solidity
-function vaultRole() public pure returns (bytes32)
+constructor(address _compositeDataFeed, string _baseSymbol, string _quoteSymbol) public
```
-## HypeEthMidasAccessControlRoles
+Constructor initializes the adapter with a CompositeDataFeed contract
+
+#### Parameters
-Base contract that stores all roles descriptors for hypeETH contracts
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _compositeDataFeed | address | Address of the CompositeDataFeed contract providing composite price data |
+| _baseSymbol | string | Symbol of the base token |
+| _quoteSymbol | string | Symbol of the quote currency |
-### HYPE_ETH_DEPOSIT_VAULT_ADMIN_ROLE
+### _getTimestamp
```solidity
-bytes32 HYPE_ETH_DEPOSIT_VAULT_ADMIN_ROLE
+function _getTimestamp() internal view returns (uint256 timestamp)
```
-actor that can manage HypeEthDepositVault
+Gets the timestamp for the price data
-### HYPE_ETH_REDEMPTION_VAULT_ADMIN_ROLE
+_Overrides base to handle composite feeds by taking min timestamp from numerator/denominator_
-```solidity
-bytes32 HYPE_ETH_REDEMPTION_VAULT_ADMIN_ROLE
-```
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| timestamp | uint256 | The timestamp of the last price update |
-actor that can manage HypeEthRedemptionVault
+## IStdReference
+
+### ReferenceData
-### HYPE_ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+A structure returned whenever someone requests for standard reference data.
```solidity
-bytes32 HYPE_ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+struct ReferenceData {
+ uint256 rate;
+ uint256 lastUpdatedBase;
+ uint256 lastUpdatedQuote;
+}
```
-actor that can manage HypeEthCustomAggregatorFeed and HypeEthDataFeed
+### getReferenceData
-## HypeEthRedemptionVaultWithSwapper
+```solidity
+function getReferenceData(string _base, string _quote) external view returns (struct IStdReference.ReferenceData)
+```
-Smart contract that handles hypeETH redemptions
+Returns the price data for the given base/quote pair. Revert if not available.
-### vaultRole
+### getReferenceDataBulk
```solidity
-function vaultRole() public pure returns (bytes32)
+function getReferenceDataBulk(string[] _bases, string[] _quotes) external view returns (struct IStdReference.ReferenceData[])
```
-## HypeUsdMidasAccessControlRoles
-
-Base contract that stores all roles descriptors for hypeUSD contracts
+Similar to getReferenceData, but with multiple base/quote pairs at once.
-### HYPE_USD_DEPOSIT_VAULT_ADMIN_ROLE
+## DataFeedToBandStdAdapter
-```solidity
-bytes32 HYPE_USD_DEPOSIT_VAULT_ADMIN_ROLE
-```
+Converts DataFeed to Band Protocol's IStdReference interface
-actor that can manage HypeUsdDepositVault
+_Base adapter that wraps a DataFeed to provide Band Protocol standard reference data_
-### HYPE_USD_REDEMPTION_VAULT_ADMIN_ROLE
+### dataFeed
```solidity
-bytes32 HYPE_USD_REDEMPTION_VAULT_ADMIN_ROLE
+contract IDataFeed dataFeed
```
-actor that can manage HypeUsdRedemptionVault
+DataFeed contract providing validated price data
-### HYPE_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+### baseSymbol
```solidity
-bytes32 HYPE_USD_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+string baseSymbol
```
-actor that can manage HypeUsdCustomAggregatorFeed and HypeUsdDataFeed
-
-## HypeUsdRedemptionVaultWithSwapper
+Base token symbol
-Smart contract that handles hypeUSD redemptions
-
-### vaultRole
+### quoteSymbol
```solidity
-function vaultRole() public pure returns (bytes32)
+string quoteSymbol
```
-## IDataFeed
+Quote currency symbol
-### initialize
+### constructor
```solidity
-function initialize(address _ac, address _aggregator, uint256 _healthyDiff, int256 _minExpectedAnswer, int256 _maxExpectedAnswer) external
+constructor(address _dataFeed, string _baseSymbol, string _quoteSymbol) public
```
-upgradeable pattern contract`s initializer
+Constructor initializes the adapter with a DataFeed contract
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| _ac | address | MidasAccessControl contract address |
-| _aggregator | address | AggregatorV3Interface contract address |
-| _healthyDiff | uint256 | max. staleness time for data feed answers |
-| _minExpectedAnswer | int256 | min.expected answer value from data feed |
-| _maxExpectedAnswer | int256 | max.expected answer value from data feed |
+| _dataFeed | address | Address of the DataFeed contract providing price data |
+| _baseSymbol | string | Symbol of the base token |
+| _quoteSymbol | string | Symbol of the quote currency |
-### changeAggregator
+### getReferenceData
```solidity
-function changeAggregator(address _aggregator) external
+function getReferenceData(string _base, string _quote) external view returns (struct IStdReference.ReferenceData)
```
-updates `aggregator` address
+Returns the price data for the given base/quote pair
+
+_Only supports the configured baseSymbol/quoteSymbol pair_
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| _aggregator | address | new AggregatorV3Interface contract address |
+| _base | string | The base token symbol |
+| _quote | string | The quote currency symbol |
-### getDataInBase18
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | struct IStdReference.ReferenceData | ReferenceData containing rate and update timestamps |
+
+### getReferenceDataBulk
```solidity
-function getDataInBase18() external view returns (uint256 answer)
+function getReferenceDataBulk(string[] _bases, string[] _quotes) external view returns (struct IStdReference.ReferenceData[])
```
-fetches answer from aggregator
-and converts it to the base18 precision
+Returns price data for multiple base/quote pairs
+
+_Only supports single pair queries (array length must be 1)_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _bases | string[] | Array of base token symbols (must have length 1) |
+| _quotes | string[] | Array of quote currency symbols (must have length 1) |
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| answer | uint256 | fetched aggregator answer |
+| [0] | struct IStdReference.ReferenceData[] | Array containing single ReferenceData element |
-### feedAdminRole
+### _getTimestamp
```solidity
-function feedAdminRole() external view returns (bytes32)
+function _getTimestamp() internal view virtual returns (uint256 timestamp)
```
-_describes a role, owner of which can manage this feed_
+Gets the timestamp for the price data
+
+_Virtual function that can be overridden by child contracts_
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
-
-## IMTbill
+| timestamp | uint256 | The timestamp of the last price update |
-### mint
+### _getAggregatorTimestamp
```solidity
-function mint(address to, uint256 amount) external
+function _getAggregatorTimestamp(contract IDataFeed feed) internal view returns (uint256)
```
-mints mTBILL token `amount` to a given `to` address.
-should be called only from permissioned actor
+Gets timestamp from a DataFeed via its aggregator
+
+_Assumes the feed is a DataFeed. Reverts if not._
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| to | address | addres to mint tokens to |
-| amount | uint256 | amount to mint |
+| feed | contract IDataFeed | The data feed to get timestamp from |
-### burn
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | timestamp The timestamp from the aggregator |
+
+## CompositeDataFeedTest
+
+### constructor
```solidity
-function burn(address from, uint256 amount) external
+constructor() public
```
-burns mTBILL token `amount` to a given `to` address.
-should be called only from permissioned actor
+### _disableInitializers
-#### Parameters
+```solidity
+function _disableInitializers() internal
+```
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| from | address | addres to burn tokens from |
-| amount | uint256 | amount to burn |
+_Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
+Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
+to any version. It is recommended to use this to lock implementation contracts that are designed to be called
+through proxies.
-### setMetadata
+Emits an {Initialized} event the first time it is successfully executed._
+
+### _onlyProxyAdmin
```solidity
-function setMetadata(bytes32 key, bytes data) external
+function _onlyProxyAdmin() internal view
```
-updates contract`s metadata.
-should be called only from permissioned actor
+function to check if the sender is the proxy admin
-#### Parameters
+## DataFeedTest
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| key | bytes32 | metadata map. key |
-| data | bytes | metadata map. value |
+### constructor
-### pause
+```solidity
+constructor() public
+```
+
+### _disableInitializers
```solidity
-function pause() external
+function _disableInitializers() internal
```
-puts mTBILL token on pause.
-should be called only from permissioned actor
+_Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
+Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
+to any version. It is recommended to use this to lock implementation contracts that are designed to be called
+through proxies.
+
+Emits an {Initialized} event the first time it is successfully executed._
-### unpause
+### _onlyProxyAdmin
```solidity
-function unpause() external
+function _onlyProxyAdmin() internal view
```
-puts mTBILL token on pause.
-should be called only from permissioned actor
+function to check if the sender is the proxy admin
-## TokenConfig
+## CustomAggregatorV3CompatibleFeedGrowth
+
+AggregatorV3 compatible feed, where price is submitted manually by feed admins
+and growth apr % is applied to the answer.
+
+### RoundDataWithGrowth
```solidity
-struct TokenConfig {
- address dataFeed;
- uint256 fee;
- uint256 allowance;
- bool stable;
+struct RoundDataWithGrowth {
+ uint80 roundId;
+ uint80 answeredInRound;
+ int80 growthApr;
+ int256 answer;
+ uint256 startedAt;
+ uint256 updatedAt;
}
```
-## RequestStatus
+### description
```solidity
-enum RequestStatus {
- Pending,
- Processed,
- Canceled
-}
+string description
```
-## MTokenInitParams
+feed description
+
+### maxAnswerDeviation
```solidity
-struct MTokenInitParams {
- address mToken;
- address mTokenDataFeed;
-}
+uint256 maxAnswerDeviation
```
-## ReceiversInitParams
+max deviation from latest price in %
+
+_10 ** decimals() is a percentage precision_
+
+### minAnswer
```solidity
-struct ReceiversInitParams {
- address tokensReceiver;
- address feeReceiver;
-}
+int192 minAnswer
```
-## InstantInitParams
+minimal possible answer that feed can return
+
+### maxAnswer
```solidity
-struct InstantInitParams {
- uint256 instantFee;
- uint256 instantDailyLimit;
-}
+int192 maxAnswer
```
-## IManageableVault
+maximal possible answer that feed can return
-### WithdrawToken
+### minGrowthApr
```solidity
-event WithdrawToken(address caller, address token, address withdrawTo, uint256 amount)
+int80 minGrowthApr
```
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| caller | address | function caller (msg.sender) |
-| token | address | token that was withdrawn |
-| withdrawTo | address | address to which tokens were withdrawn |
-| amount | uint256 | `token` transfer amount |
+minimal possible growth apr value that can be set
-### AddPaymentToken
+### maxGrowthApr
```solidity
-event AddPaymentToken(address caller, address token, address dataFeed, uint256 fee, bool stable)
+int80 maxGrowthApr
```
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| caller | address | function caller (msg.sender) |
-| token | address | address of token that |
-| dataFeed | address | token dataFeed address |
-| fee | uint256 | fee 1% = 100 |
-| stable | bool | stablecoin flag |
+maximal possible growth apr value that can be set
-### ChangeTokenAllowance
+### latestRound
```solidity
-event ChangeTokenAllowance(address token, address caller, uint256 allowance)
+uint80 latestRound
```
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| token | address | address of token that |
-| caller | address | function caller (msg.sender) |
-| allowance | uint256 | new allowance |
+last round id
-### ChangeTokenFee
+### onlyUp
```solidity
-event ChangeTokenFee(address token, address caller, uint256 fee)
+bool onlyUp
```
-#### Parameters
+if true, the price can only increase
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| token | address | address of token that |
-| caller | address | function caller (msg.sender) |
-| fee | uint256 | new fee |
+_applicable only for setRoundDataSafe_
-### RemovePaymentToken
+### constructor
```solidity
-event RemovePaymentToken(address token, address caller)
+constructor(bytes32 _contractAdminRole) public
```
+constructor
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| token | address | address of token that |
-| caller | address | function caller (msg.sender) |
+| _contractAdminRole | bytes32 | contract admin role |
-### AddWaivedFeeAccount
+### initialize
```solidity
-event AddWaivedFeeAccount(address account, address caller)
+function initialize(address _accessControl, int192 _minAnswer, int192 _maxAnswer, uint256 _maxAnswerDeviation, int80 _minGrowthApr, int80 _maxGrowthApr, bool _onlyUp, string _description) external
```
+upgradeable pattern contract`s initializer
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| account | address | address of account |
-| caller | address | function caller (msg.sender) |
+| _accessControl | address | address of MidasAccessControll contract |
+| _minAnswer | int192 | init value for `minAnswer`. Should be < `_maxAnswer` |
+| _maxAnswer | int192 | init value for `maxAnswer`. Should be > `_minAnswer` |
+| _maxAnswerDeviation | uint256 | init value for `maxAnswerDeviation` |
+| _minGrowthApr | int80 | init value for `minGrowthApr` |
+| _maxGrowthApr | int80 | init value for `maxGrowthApr` |
+| _onlyUp | bool | init value for `onlyUp` |
+| _description | string | init value for `description` |
-### RemoveWaivedFeeAccount
+### setOnlyUp
```solidity
-event RemoveWaivedFeeAccount(address account, address caller)
+function setOnlyUp(bool _onlyUp) external
```
+updates onlyUp flag
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| account | address | address of account |
-| caller | address | function caller (msg.sender) |
+| _onlyUp | bool | new onlyUp flag |
-### SetInstantFee
+### setMinMaxAnswer
```solidity
-event SetInstantFee(address caller, uint256 newFee)
+function setMinMaxAnswer(int192 _minAnswer, int192 _maxAnswer) external
```
+sets the min and max answer
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| caller | address | function caller (msg.sender) |
-| newFee | uint256 | new operation fee value |
+| _minAnswer | int192 | the new min answer |
+| _maxAnswer | int192 | the new max answer |
-### SetMinAmount
+### setMaxGrowthApr
```solidity
-event SetMinAmount(address caller, uint256 newAmount)
+function setMaxGrowthApr(int80 _maxGrowthApr) external
```
+updates max growth apr
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| caller | address | function caller (msg.sender) |
-| newAmount | uint256 | new min amount for operation |
+| _maxGrowthApr | int80 | new max growth apr |
-### SetInstantDailyLimit
+### setMinGrowthApr
```solidity
-event SetInstantDailyLimit(address caller, uint256 newLimit)
+function setMinGrowthApr(int80 _minGrowthApr) external
```
+updates min growth apr
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| caller | address | function caller (msg.sender) |
-| newLimit | uint256 | new operation daily limit |
+| _minGrowthApr | int80 | new min growth apr |
-### SetVariationTolerance
+### setMaxAnswerDeviation
```solidity
-event SetVariationTolerance(address caller, uint256 newTolerance)
+function setMaxAnswerDeviation(uint256 _maxAnswerDeviation) external
```
+sets the max answer deviation
+
+_the max answer deviation is the maximum allowed deviation from the latest price_
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| caller | address | function caller (msg.sender) |
-| newTolerance | uint256 | percent of price diviation 1% = 100 |
+| _maxAnswerDeviation | uint256 | the new max answer deviation in % |
-### SetFeeReceiver
+### setRoundDataSafe
```solidity
-event SetFeeReceiver(address caller, address reciever)
+function setRoundDataSafe(int256 _data, uint256 _dataTimestamp, int80 _growthApr) external
```
+works as `setRoundData()`, but also checks the
+deviation with the lattest submitted data
+
+_deviation with previous data needs to be <= `maxAnswerDeviation`_
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| caller | address | function caller (msg.sender) |
-| reciever | address | new reciever address |
+| _data | int256 | data value |
+| _dataTimestamp | uint256 | timestamp of the data in the past |
+| _growthApr | int80 | growth apr value |
-### SetTokensReceiver
+### setRoundData
```solidity
-event SetTokensReceiver(address caller, address reciever)
+function setRoundData(int256 _data, uint256 _dataTimestamp, int80 _growthApr) public
```
-#### Parameters
+sets the data for `latestRound` + 1 round id
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| caller | address | function caller (msg.sender) |
-| reciever | address | new reciever address |
-
-### FreeFromMinAmount
-
-```solidity
-event FreeFromMinAmount(address user, bool enable)
-```
+_`_data` should be >= `minAnswer` and <= `maxAnswer`.
+Function should be called only from permissioned actor_
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| user | address | user address |
-| enable | bool | is enabled |
+| _data | int256 | data value |
+| _dataTimestamp | uint256 | timestamp of the data in the past |
+| _growthApr | int80 | growth apr value |
-### mTokenDataFeed
+### latestRoundData
```solidity
-function mTokenDataFeed() external view returns (contract IDataFeed)
+function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
```
-The mTokenDataFeed contract address.
+returns data for latest round with growth applied
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | contract IDataFeed | The address of the mTokenDataFeed contract. |
+| roundId | uint80 | roundId |
+| answer | int256 | answer with growth applied |
+| startedAt | uint256 | timestamp passed to setRoundData |
+| updatedAt | uint256 | timestamp of the last price submission |
+| answeredInRound | uint80 | answeredInRound |
-### mToken
+### latestRoundDataRaw
```solidity
-function mToken() external view returns (contract IMTbill)
+function latestRoundDataRaw() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound, int80 growthApr)
```
-The mToken contract address.
+returns `latestRoundData` without growth applied
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | contract IMTbill | The address of the mToken contract. |
+| roundId | uint80 | roundId |
+| answer | int256 | answer with growth applied |
+| startedAt | uint256 | startedAt |
+| updatedAt | uint256 | updatedAt |
+| answeredInRound | uint80 | answeredInRound |
+| growthApr | int80 | growthApr |
-### withdrawToken
+### version
```solidity
-function withdrawToken(address token, uint256 amount, address withdrawTo) external
+function version() external pure returns (uint256)
```
-withdraws `amount` of a given `token` from the contract.
-can be called only from permissioned actor.
+### lastAnswer
-#### Parameters
+```solidity
+function lastAnswer() public view returns (int256)
+```
+
+#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| token | address | token address |
-| amount | uint256 | token amount |
-| withdrawTo | address | withdraw destination address |
+| [0] | int256 | answer of latest price submission |
-### addPaymentToken
+### lastGrowthApr
```solidity
-function addPaymentToken(address token, address dataFeed, uint256 fee, bool stable) external
+function lastGrowthApr() public view returns (int80)
```
-adds a token to the stablecoins list.
-can be called only from permissioned actor.
+returns the growth apr of the latest round
-#### Parameters
+#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| token | address | token address |
-| dataFeed | address | dataFeed address |
-| fee | uint256 | 1% = 100 |
-| stable | bool | is stablecoin flag |
+| [0] | int80 | growthApr latest growthApr value |
-### removePaymentToken
+### lastTimestamp
```solidity
-function removePaymentToken(address token) external
+function lastTimestamp() public view returns (uint256)
```
-removes a token from stablecoins list.
-can be called only from permissioned actor.
-
-#### Parameters
+#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| token | address | token address |
+| [0] | uint256 | `updatedAt` timestamp of latest price submission |
-### changeTokenAllowance
+### lastStartedAt
```solidity
-function changeTokenAllowance(address token, uint256 allowance) external
+function lastStartedAt() public view returns (uint256)
```
-set new token allowance.
-if MAX_UINT = infinite allowance
-prev allowance rewrites by new
-can be called only from permissioned actor.
-
-#### Parameters
+#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| token | address | token address |
-| allowance | uint256 | new allowance (decimals 18) |
+| [0] | uint256 | `startedAt` timestamp of latest price submission |
-### changeTokenFee
+### getRoundData
```solidity
-function changeTokenFee(address token, uint256 fee) external
+function getRoundData(uint80 _roundId) public view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
```
-set new token fee.
-can be called only from permissioned actor.
+returns data for a specific round with growth applied
+
+_growth to answer is only applied between [roundStartedAt,nextRoundUpdatedAt]
+or if roundId is latestRound, block.timestamp will be used as nextRoundUpdatedAt_
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| token | address | token address |
-| fee | uint256 | new fee percent 1% = 100 |
-
-### setVariationTolerance
-
-```solidity
-function setVariationTolerance(uint256 tolerance) external
-```
-
-set new prices diviation percent.
-can be called only from permissioned actor.
+| _roundId | uint80 | roundId |
-#### Parameters
+#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| tolerance | uint256 | new prices diviation percent 1% = 100 |
+| roundId | uint80 | roundId |
+| answer | int256 | answer with growth applied |
+| startedAt | uint256 | timestamp passed to setRoundData |
+| updatedAt | uint256 | timestamp of the last price submission |
+| answeredInRound | uint80 | answeredInRound |
-### setMinAmount
+### getRoundDataRaw
```solidity
-function setMinAmount(uint256 newAmount) external
+function getRoundDataRaw(uint80 _roundId) public view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound, int80 growthApr)
```
-set new min amount.
-can be called only from permissioned actor.
+returns data for a specific round without growth applied
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| newAmount | uint256 | min amount for operations in mToken |
+| _roundId | uint80 | roundId |
-### addWaivedFeeAccount
-
-```solidity
-function addWaivedFeeAccount(address account) external
-```
-
-adds a account to waived fee restriction.
-can be called only from permissioned actor.
-
-#### Parameters
+#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| account | address | user address |
+| roundId | uint80 | roundId |
+| answer | int256 | answer with growth applied |
+| startedAt | uint256 | startedAt |
+| updatedAt | uint256 | updatedAt |
+| answeredInRound | uint80 | answeredInRound |
+| growthApr | int80 | growthApr value |
-### removeWaivedFeeAccount
+### contractAdminRole
```solidity
-function removeWaivedFeeAccount(address account) external
+function contractAdminRole() public view returns (bytes32)
```
-removes a account from waived fee restriction.
-can be called only from permissioned actor.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| account | address | user address |
+_main admin role for the contract_
-### setFeeReceiver
+### applyGrowth
```solidity
-function setFeeReceiver(address reciever) external
+function applyGrowth(int256 _answer, int80 _growthApr, uint256 _timestampFrom) public view returns (int256)
```
-set new reciever for fees.
-can be called only from permissioned actor.
+applies growth to the answer until current timestamp
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| reciever | address | new fee reciever address |
-
-### setTokensReceiver
-
-```solidity
-function setTokensReceiver(address reciever) external
-```
-
-set new reciever for tokens.
-can be called only from permissioned actor.
+| _answer | int256 | answer |
+| _growthApr | int80 | growth apr |
+| _timestampFrom | uint256 | timestamp from |
-#### Parameters
+#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| reciever | address | new token reciever address |
+| [0] | int256 | answer with growth applied |
-### setInstantFee
+### applyGrowth
```solidity
-function setInstantFee(uint256 newInstantFee) external
+function applyGrowth(int256 _answer, int80 _growthApr, uint256 _timestampFrom, uint256 _timestampTo) public pure returns (int256)
```
-set operation fee percent.
-can be called only from permissioned actor.
+applies growth to the answer between two timestamps
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| newInstantFee | uint256 | new instant operations fee percent 1& = 100 |
+| _answer | int256 | answer |
+| _growthApr | int80 | growth apr |
+| _timestampFrom | uint256 | timestamp from |
+| _timestampTo | uint256 | timestamp to |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | int256 | answer with growth applied |
-### setInstantDailyLimit
+### decimals
```solidity
-function setInstantDailyLimit(uint256 newInstantDailyLimit) external
+function decimals() public pure returns (uint8)
```
-set operation daily limit.
-can be called only from permissioned actor.
+### _getDeviation
+
+```solidity
+function _getDeviation(int256 _lastPrice, int256 _newPrice, bool _validateOnlyUp) internal pure returns (uint256)
+```
+
+_calculates a deviation in % between `_lastPrice` and `_newPrice`_
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| newInstantDailyLimit | uint256 | new operation daily limit (decimals 18) |
+| _lastPrice | int256 | last price |
+| _newPrice | int256 | new price |
+| _validateOnlyUp | bool | if true, will validate that deviation is positive |
-### freeFromMinAmount
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | deviation in `decimals()` precision |
+
+## IAggregatorV3CompatibleFeedGrowth
+
+### AnswerUpdated
```solidity
-function freeFromMinAmount(address user, bool enable) external
+event AnswerUpdated(int256 data, uint256 roundId, uint256 timestamp, int80 growthApr)
```
-frees given `user` from the minimal deposit
-amount validation in `initiateDepositRequest`
+emitted when answer is updated
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| user | address | address of user |
-| enable | bool | |
+| data | int256 | data value without growth applied |
+| roundId | uint256 | roundId |
+| timestamp | uint256 | timestamp of the data in the past |
+| growthApr | int80 | growthApr value |
-## Request
+### MaxAnswerDeviationUpdated
```solidity
-struct Request {
- address sender;
- address tokenOut;
- enum RequestStatus status;
- uint256 amountMToken;
- uint256 mTokenRate;
- uint256 tokenOutRate;
-}
+event MaxAnswerDeviationUpdated(uint256 maxAnswerDeviation)
```
-## FiatRedeptionInitParams
-
-```solidity
-struct FiatRedeptionInitParams {
- uint256 fiatAdditionalFee;
- uint256 fiatFlatFee;
- uint256 minFiatRedeemAmount;
-}
-```
+#### Parameters
-## IRedemptionVault
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| maxAnswerDeviation | uint256 | the new max answer deviation |
-### RedeemInstant
+### MaxGrowthAprUpdated
```solidity
-event RedeemInstant(address user, address tokenOut, uint256 amount, uint256 feeAmount, uint256 amountTokenOut)
+event MaxGrowthAprUpdated(int80 newMaxGrowthApr)
```
+emitted when max growth apr is updated
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| user | address | function caller (msg.sender) |
-| tokenOut | address | address of tokenOut |
-| amount | uint256 | amount of mToken |
-| feeAmount | uint256 | fee amount in mToken |
-| amountTokenOut | uint256 | amount of tokenOut |
+| newMaxGrowthApr | int80 | new max growth apr |
-### RedeemInstantWithCustomRecipient
+### MinGrowthAprUpdated
```solidity
-event RedeemInstantWithCustomRecipient(address user, address tokenOut, address recipient, uint256 amount, uint256 feeAmount, uint256 amountTokenOut)
+event MinGrowthAprUpdated(int80 newMinGrowthApr)
```
+emitted when min growth apr is updated
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| user | address | function caller (msg.sender) |
-| tokenOut | address | address of tokenOut |
-| recipient | address | address that receives tokens |
-| amount | uint256 | amount of mToken |
-| feeAmount | uint256 | fee amount in mToken |
-| amountTokenOut | uint256 | amount of tokenOut |
+| newMinGrowthApr | int80 | new min growth apr |
-### RedeemRequest
+### OnlyUpUpdated
```solidity
-event RedeemRequest(uint256 requestId, address user, address tokenOut, uint256 amountMTokenIn, uint256 feeAmount)
+event OnlyUpUpdated(bool newOnlyUp)
```
+emitted when onlyUp flag is updated
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| requestId | uint256 | request id |
-| user | address | function caller (msg.sender) |
-| tokenOut | address | address of tokenOut |
-| amountMTokenIn | uint256 | amount of mToken |
-| feeAmount | uint256 | fee amount in mToken |
+| newOnlyUp | bool | new onlyUp flag |
-### RedeemRequestWithCustomRecipient
+### SetMinMaxAnswer
```solidity
-event RedeemRequestWithCustomRecipient(uint256 requestId, address user, address tokenOut, address recipient, uint256 amountMTokenIn, uint256 feeAmount)
+event SetMinMaxAnswer(int192 minAnswer, int192 maxAnswer)
```
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| requestId | uint256 | request id |
-| user | address | function caller (msg.sender) |
-| tokenOut | address | address of tokenOut |
-| recipient | address | address that receives tokens |
-| amountMTokenIn | uint256 | amount of mToken |
-| feeAmount | uint256 | fee amount in mToken |
+| minAnswer | int192 | the new min answer |
+| maxAnswer | int192 | the new max answer |
-### ApproveRequest
+### setOnlyUp
```solidity
-event ApproveRequest(uint256 requestId, uint256 newMTokenRate)
+function setOnlyUp(bool _onlyUp) external
```
+updates onlyUp flag
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| requestId | uint256 | mint request id |
-| newMTokenRate | uint256 | net mToken rate |
+| _onlyUp | bool | new onlyUp flag |
-### SafeApproveRequest
+### setMaxGrowthApr
```solidity
-event SafeApproveRequest(uint256 requestId, uint256 newMTokenRate)
+function setMaxGrowthApr(int80 _maxGrowthApr) external
```
+updates max growth apr
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| requestId | uint256 | mint request id |
-| newMTokenRate | uint256 | net mToken rate |
+| _maxGrowthApr | int80 | new max growth apr |
-### RejectRequest
+### setMinGrowthApr
```solidity
-event RejectRequest(uint256 requestId, address user)
+function setMinGrowthApr(int80 _minGrowthApr) external
```
+updates min growth apr
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| requestId | uint256 | mint request id |
-| user | address | address of user |
+| _minGrowthApr | int80 | new min growth apr |
-### SetMinFiatRedeemAmount
+### setMaxAnswerDeviation
```solidity
-event SetMinFiatRedeemAmount(address caller, uint256 newMinAmount)
+function setMaxAnswerDeviation(uint256 _maxAnswerDeviation) external
```
+sets the max answer deviation
+
+_the max answer deviation is the maximum allowed deviation from the latest price_
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| caller | address | function caller (msg.sender) |
-| newMinAmount | uint256 | new min amount for fiat requests |
+| _maxAnswerDeviation | uint256 | the new max answer deviation in % |
-### SetFiatFlatFee
+### setMinMaxAnswer
```solidity
-event SetFiatFlatFee(address caller, uint256 feeInMToken)
+function setMinMaxAnswer(int192 _minAnswer, int192 _maxAnswer) external
```
+sets the min and max answer
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| caller | address | function caller (msg.sender) |
-| feeInMToken | uint256 | fee amount in mToken |
+| _minAnswer | int192 | the new min answer |
+| _maxAnswer | int192 | the new max answer |
-### SetFiatAdditionalFee
+### setRoundDataSafe
```solidity
-event SetFiatAdditionalFee(address caller, uint256 newfee)
+function setRoundDataSafe(int256 _data, uint256 _dataTimestamp, int80 _growthApr) external
```
+works as `setRoundData()`, but also checks the
+deviation with the lattest submitted data
+
+_deviation with previous data needs to be <= `maxAnswerDeviation`_
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| caller | address | function caller (msg.sender) |
-| newfee | uint256 | new fiat fee percent 1% = 100 |
+| _data | int256 | data value |
+| _dataTimestamp | uint256 | timestamp of the data in the past |
+| _growthApr | int80 | growth apr value |
-### SetRequestRedeemer
+### setRoundData
```solidity
-event SetRequestRedeemer(address caller, address redeemer)
+function setRoundData(int256 _data, uint256 _dataTimestamp, int80 _growthApr) external
```
+sets the data for `latestRound` + 1 round id
+
+_`_data` should be >= `minAnswer` and <= `maxAnswer`.
+Function should be called only from permissioned actor_
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| caller | address | function caller (msg.sender) |
-| redeemer | address | new address of request redeemer |
+| _data | int256 | data value |
+| _dataTimestamp | uint256 | timestamp of the data in the past |
+| _growthApr | int80 | growth apr value |
-### redeemInstant
+### latestRoundDataRaw
```solidity
-function redeemInstant(address tokenOut, uint256 amountMTokenIn, uint256 minReceiveAmount) external
+function latestRoundDataRaw() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound, int80 growthApr)
```
-redeem mToken to tokenOut if daily limit and allowance not exceeded
-Burns mTBILL from the user.
-Transfers fee in mToken to feeReceiver
-Transfers tokenOut to user.
+returns `latestRoundData` without growth applied
-#### Parameters
+#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| tokenOut | address | stable coin token address to redeem to |
-| amountMTokenIn | uint256 | amount of mTBILL to redeem (decimals 18) |
-| minReceiveAmount | uint256 | minimum expected amount of tokenOut to receive (decimals 18) |
+| roundId | uint80 | roundId |
+| answer | int256 | answer with growth applied |
+| startedAt | uint256 | startedAt |
+| updatedAt | uint256 | updatedAt |
+| answeredInRound | uint80 | answeredInRound |
+| growthApr | int80 | growthApr |
-### redeemInstant
+### lastGrowthApr
```solidity
-function redeemInstant(address tokenOut, uint256 amountMTokenIn, uint256 minReceiveAmount, address recipient) external
+function lastGrowthApr() external view returns (int80)
```
-Does the same as `redeemInstant` but allows specifying a custom tokensReceiver address.
+returns the growth apr of the latest round
-#### Parameters
+#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| tokenOut | address | stable coin token address to redeem to |
-| amountMTokenIn | uint256 | amount of mTBILL to redeem (decimals 18) |
-| minReceiveAmount | uint256 | minimum expected amount of tokenOut to receive (decimals 18) |
-| recipient | address | address that receives tokens |
+| [0] | int80 | growthApr latest growthApr value |
-### redeemRequest
+### getRoundDataRaw
```solidity
-function redeemRequest(address tokenOut, uint256 amountMTokenIn) external returns (uint256)
+function getRoundDataRaw(uint80 _roundId) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound, int80 growthApr)
```
-creating redeem request if tokenOut not fiat
-Transfers amount in mToken to contract
-Transfers fee in mToken to feeReceiver
+returns data for a specific round without growth applied
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| tokenOut | address | stable coin token address to redeem to |
-| amountMTokenIn | uint256 | amount of mToken to redeem (decimals 18) |
+| _roundId | uint80 | roundId |
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | uint256 | request id |
+| roundId | uint80 | roundId |
+| answer | int256 | answer with growth applied |
+| startedAt | uint256 | startedAt |
+| updatedAt | uint256 | updatedAt |
+| answeredInRound | uint80 | answeredInRound |
+| growthApr | int80 | growthApr value |
-### redeemRequest
+### applyGrowth
```solidity
-function redeemRequest(address tokenOut, uint256 amountMTokenIn, address recipient) external returns (uint256)
+function applyGrowth(int256 _answer, int80 _growthApr, uint256 _timestampFrom) external view returns (int256)
```
-Does the same as `redeemRequest` but allows specifying a custom tokensReceiver address.
+applies growth to the answer until current timestamp
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| tokenOut | address | stable coin token address to redeem to |
-| amountMTokenIn | uint256 | amount of mToken to redeem (decimals 18) |
-| recipient | address | address that receives tokens |
+| _answer | int256 | answer |
+| _growthApr | int80 | growth apr |
+| _timestampFrom | uint256 | timestamp from |
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | uint256 | request id |
+| [0] | int256 | answer with growth applied |
-### redeemFiatRequest
+### applyGrowth
```solidity
-function redeemFiatRequest(uint256 amountMTokenIn) external returns (uint256)
+function applyGrowth(int256 _answer, int80 _growthApr, uint256 _timestampFrom, uint256 _timestampTo) external pure returns (int256)
```
-creating redeem request if tokenOut is fiat
-Transfers amount in mToken to contract
-Transfers fee in mToken to feeReceiver
+applies growth to the answer between two timestamps
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| amountMTokenIn | uint256 | amount of mToken to redeem (decimals 18) |
+| _answer | int256 | answer |
+| _growthApr | int80 | growth apr |
+| _timestampFrom | uint256 | timestamp from |
+| _timestampTo | uint256 | timestamp to |
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | uint256 | request id |
+| [0] | int256 | answer with growth applied |
-### approveRequest
+## CustomAggregatorV3CompatibleFeedGrowthTester
+
+### constructor
```solidity
-function approveRequest(uint256 requestId, uint256 newMTokenRate) external
+constructor() public
```
-approving redeem request if not exceed tokenOut allowance
-Burns amount mToken from contract
-Transfers tokenOut to user
-Sets flag Processed
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| requestId | uint256 | request id |
-| newMTokenRate | uint256 | new mToken rate inputted by vault admin |
-
-### safeApproveRequest
+### _disableInitializers
```solidity
-function safeApproveRequest(uint256 requestId, uint256 newMTokenRate) external
+function _disableInitializers() internal
```
-approving request if inputted token rate fit price diviation percent
-Burns amount mToken from contract
-Transfers tokenOut to user
-Sets flag Processed
-
-#### Parameters
+_Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
+Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
+to any version. It is recommended to use this to lock implementation contracts that are designed to be called
+through proxies.
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| requestId | uint256 | request id |
-| newMTokenRate | uint256 | new mToken rate inputted by vault admin |
+Emits an {Initialized} event the first time it is successfully executed._
-### rejectRequest
+### _onlyProxyAdmin
```solidity
-function rejectRequest(uint256 requestId) external
+function _onlyProxyAdmin() internal view
```
-rejecting request
-Sets request flag to Canceled.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| requestId | uint256 | request id |
+function to check if the sender is the proxy admin
-### setMinFiatRedeemAmount
+### getDeviation
```solidity
-function setMinFiatRedeemAmount(uint256 newValue) external
+function getDeviation(int256 _lastPrice, int256 _newPrice, bool _validateOnlyUp) public pure returns (uint256)
```
-set new min amount for fiat requests
+## DepositVault
-#### Parameters
+Smart contract that handles mToken minting
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| newValue | uint256 | new min amount |
+### CalcAndValidateDepositResult
-### setFiatFlatFee
+return data of _calcAndValidateDeposit
+packed into a struct to avoid stack too deep errors
```solidity
-function setFiatFlatFee(uint256 feeInMToken) external
+struct CalcAndValidateDepositResult {
+ uint256 tokenAmountInUsd;
+ uint256 feeTokenAmount;
+ uint256 amountTokenWithoutFee;
+ uint256 mintAmount;
+ uint256 tokenInRate;
+ uint256 tokenOutRate;
+ uint256 tokenDecimals;
+}
```
-set fee amount in mToken for fiat requests
+### mintRequests
-#### Parameters
+```solidity
+mapping(uint256 => struct Request) mintRequests
+```
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| feeInMToken | uint256 | fee amount in mToken |
+request data storage
-### setFiatAdditionalFee
+### totalMinted
```solidity
-function setFiatAdditionalFee(uint256 newFee) external
+mapping(address => uint256) totalMinted
```
-set new fee percent for fiat requests
+_how much mTokens were minted by the depositor
+depositor address => amount minted_
-#### Parameters
+### minMTokenAmountForFirstDeposit
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| newFee | uint256 | new fee percent 1% = 100 |
+```solidity
+uint256 minMTokenAmountForFirstDeposit
+```
-### setRequestRedeemer
+minimal USD amount for first user`s deposit
+
+### maxSupplyCap
```solidity
-function setRequestRedeemer(address redeemer) external
+uint256 maxSupplyCap
```
-set address which is designated for standard redemptions, allowing tokens to be pulled from this address
+max supply cap value in mToken
-#### Parameters
+_if after the deposit, mToken.totalSupply() > maxSupplyCap,
+the tx will be reverted_
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| redeemer | address | new address of request redeemer |
+### maxAmountPerRequest
-## IRedemptionVaultWithSwapper
+```solidity
+uint256 maxAmountPerRequest
+```
-### SetLiquidityProvider
+max amount per request in mToken
+
+### upcomingSupply
```solidity
-event SetLiquidityProvider(address caller, address provider)
+uint256 upcomingSupply
```
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| caller | address | caller address (msg.sender) |
-| provider | address | new LP address |
+pending supply in mToken that will be released
+after the deposit request is processed
-### SetSwapperVault
+### constructor
```solidity
-event SetSwapperVault(address caller, address vault)
+constructor(bytes32 _contractAdminRole, bytes32 _greenlistedRole) public
```
+Passes role identifiers to the base ManageableVault constructor
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| caller | address | caller address (msg.sender) |
-| vault | address | new underlying vault for swapper |
+| _contractAdminRole | bytes32 | contract admin role identifier |
+| _greenlistedRole | bytes32 | greenlisted role identifier |
-### setLiquidityProvider
+### initialize
```solidity
-function setLiquidityProvider(address provider) external
+function initialize(struct CommonVaultInitParams _commonVaultInitParams, struct DepositVaultInitParams _depositVaultInitParams) public
```
-sets new liquidity provider address
+upgradeable pattern contract`s initializer
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| provider | address | new liquidity provider address |
+| _commonVaultInitParams | struct CommonVaultInitParams | init params for common vault |
+| _depositVaultInitParams | struct DepositVaultInitParams | init params for deposit vault |
-### setSwapperVault
+### depositInstant
```solidity
-function setSwapperVault(address vault) external
+function depositInstant(address tokenIn, uint256 amountToken, uint256 minReceiveAmount, bytes32 referrerId) external returns (uint256)
```
-sets new underlying vault for swapper
+depositing proccess with auto mint if
+account fit daily limit and token allowance.
+Transfers token from the user.
+Transfers fee in tokenIn to tokensReceiver.
+Mints mToken to user.
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| vault | address | new underlying vault for swapper |
+| tokenIn | address | address of tokenIn |
+| amountToken | uint256 | amount of `tokenIn` that will be taken from user (decimals 18) |
+| minReceiveAmount | uint256 | minimum expected amount of mToken to receive (decimals 18) |
+| referrerId | bytes32 | referrer id |
-## ISanctionsList
+#### Return Values
-### isSanctioned
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | mintAmount amount of mToken that was minted |
+
+### depositInstant
```solidity
-function isSanctioned(address addr) external view returns (bool)
+function depositInstant(address tokenIn, uint256 amountToken, uint256 minReceiveAmount, bytes32 referrerId, address recipient) external returns (uint256)
```
-## IRedemption
+Does the same as original `depositInstant` but allows specifying a custom tokensReceiver address.
-### asset
-
-```solidity
-function asset() external view returns (address)
-```
+#### Parameters
-The asset being redeemed.
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| tokenIn | address | address of tokenIn |
+| amountToken | uint256 | amount of `tokenIn` that will be taken from user (decimals 18) |
+| minReceiveAmount | uint256 | minimum expected amount of mToken to receive (decimals 18) |
+| referrerId | bytes32 | referrer id |
+| recipient | address | |
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | address | The address of the asset token. |
+| [0] | uint256 | mintAmount amount of mToken that was minted |
-### liquidity
+### depositRequest
```solidity
-function liquidity() external view returns (address)
+function depositRequest(address tokenIn, uint256 amountToken, bytes32 referrerId) external returns (uint256 requestId)
```
-The liquidity token that the asset is being redeemed for.
+depositing proccess with mint request creating if
+account fit token allowance.
+Transfers token from the user.
+Transfers fee in tokenIn to tokensReceiver.
+Creates mint request.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| tokenIn | address | address of tokenIn |
+| amountToken | uint256 | amount of `tokenIn` that will be taken from user (decimals 18) |
+| referrerId | bytes32 | referrer id |
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | address | The address of the liquidity token. |
+| requestId | uint256 | request id |
-### settlement
+### depositRequest
```solidity
-function settlement() external view returns (address)
+function depositRequest(address tokenIn, uint256 amountToken, bytes32 referrerId, address recipientRequest, uint256 instantShare, uint256 minReceiveAmountInstantShare, address recipientInstant) external returns (uint256, uint256)
```
-The settlement contract address.
+Instantly deposits `instantShare` amount of `amountMTokenIn` and creates a request for the remaining amount.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| tokenIn | address | address of tokenIn |
+| amountToken | uint256 | amount of `tokenIn` that will be taken from user (decimals 18) |
+| referrerId | bytes32 | referrer id |
+| recipientRequest | address | address that receives the mTokens for the request part |
+| instantShare | uint256 | % amount of `amountToken` that will be deposited instantly |
+| minReceiveAmountInstantShare | uint256 | min receive amount for the instant share |
+| recipientInstant | address | address that receives the mTokens for the instant part |
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | address | The address of the settlement contract. |
+| [0] | uint256 | request id |
+| [1] | uint256 | instantMintAmount amount of mToken that was minted instantly |
-### redeem
+### safeBulkApproveRequestAtSavedRate
```solidity
-function redeem(uint256 amount) external
+function safeBulkApproveRequestAtSavedRate(uint256[] requestIds) external
```
-Redeems an amount of asset for liquidity
+approving requests from the `requestIds` array
+with the mToken rate from the request.
+Validates that new mToken rate does not exceed variation tolerance
+Mints mToken to request users.
+Sets request flags to Processed.
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| amount | uint256 | The amount of the asset token to redeem |
-
-## IUSTBRedemption
+| requestIds | uint256[] | request ids array |
-### SUPERSTATE_TOKEN
+### safeBulkApproveRequest
```solidity
-function SUPERSTATE_TOKEN() external view returns (address)
+function safeBulkApproveRequest(uint256[] requestIds) external
```
-### USDC
-
-```solidity
-function USDC() external view returns (address)
-```
+approving requests from the `requestIds` array
+with the current mToken rate.
+Validates that new mToken rate does not exceed variation tolerance
+Mints mToken to request users.
+Sets request flags to Processed.
-### owner
+#### Parameters
-```solidity
-function owner() external view returns (address)
-```
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestIds | uint256[] | request ids array |
-### redeem
+### safeBulkApproveRequestAvgRate
```solidity
-function redeem(uint256 superstateTokenInAmount) external
+function safeBulkApproveRequestAvgRate(uint256[] requestIds) external
```
-### setRedemptionFee
+approving requests from the `requestIds` array
+with the current mToken rate.
+Validates that new mToken rate does not exceed variation tolerance
+Mints mToken to request users.
+Sets request flags to Processed.
-```solidity
-function setRedemptionFee(uint256 _newFee) external
-```
+#### Parameters
-### calculateFee
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestIds | uint256[] | request ids array |
+
+### safeBulkApproveRequest
```solidity
-function calculateFee(uint256 amount) external view returns (uint256)
+function safeBulkApproveRequest(uint256[] requestIds, uint256 newOutRate) external
```
-### calculateUstbIn
+approving requests from the `requestIds` array using the `newOutRate`.
+Validates that new mToken rate does not exceed variation tolerance
+Mints mToken to request users.
+Sets request flags to Processed.
-```solidity
-function calculateUstbIn(uint256 usdcOutAmount) external view returns (uint256 ustbInAmount, uint256 usdPerUstbChainlinkRaw)
-```
+#### Parameters
-## DecimalsCorrectionLibrary
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestIds | uint256[] | request ids array |
+| newOutRate | uint256 | new mToken rate inputted by vault admin |
-### convert
+### safeBulkApproveRequestAvgRate
```solidity
-function convert(uint256 originalAmount, uint256 originalDecimals, uint256 decidedDecimals) internal pure returns (uint256)
+function safeBulkApproveRequestAvgRate(uint256[] requestIds, uint256 avgMTokenRate) external
```
-_converts `originalAmount` with `originalDecimals` into
-amount with `decidedDecimals`_
+approving requests from the `requestIds` array using the `newOutRate`.
+Validates that new mToken rate does not exceed variation tolerance
+Mints mToken to request users.
+Sets request flags to Processed.
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| originalAmount | uint256 | amount to convert |
-| originalDecimals | uint256 | decimals of the original amount |
-| decidedDecimals | uint256 | decimals for the output amount |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint256 | amount converted amount with `decidedDecimals` |
+| requestIds | uint256[] | request ids array |
+| avgMTokenRate | uint256 | avg mToken rate inputted by vault admin |
-### convertFromBase18
+### approveRequest
```solidity
-function convertFromBase18(uint256 originalAmount, uint256 decidedDecimals) internal pure returns (uint256)
+function approveRequest(uint256 requestId, uint256 newOutRate, bool isAvgRate) external
```
-_converts `originalAmount` with decimals 18 into
-amount with `decidedDecimals`_
+approving request without price deviation check
+Mints mToken to user.
+Sets request flag to Processed.
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| originalAmount | uint256 | amount to convert |
-| decidedDecimals | uint256 | decimals for the output amount |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint256 | amount converted amount with `decidedDecimals` |
+| requestId | uint256 | request id |
+| newOutRate | uint256 | mToken rate inputted by vault admin |
+| isAvgRate | bool | if true, newOutRate is avg rate |
-### convertToBase18
+### rejectRequest
```solidity
-function convertToBase18(uint256 originalAmount, uint256 originalDecimals) internal pure returns (uint256)
+function rejectRequest(uint256 requestId) external
```
-_converts `originalAmount` with `originalDecimals` into
-amount with decimals 18_
+rejecting request
+Sets request flag to Canceled.
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| originalAmount | uint256 | amount to convert |
-| originalDecimals | uint256 | decimals of the original amount |
+| requestId | uint256 | request id |
-#### Return Values
+### setMinMTokenAmountForFirstDeposit
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint256 | amount converted amount with 18 decimals |
+```solidity
+function setMinMTokenAmountForFirstDeposit(uint256 newValue) external
+```
+
+sets new minimal amount to deposit in EUR.
+can be called only from vault`s admin
-## MBtcMidasAccessControlRoles
+#### Parameters
-Base contract that stores all roles descriptors for mBTC contracts
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newValue | uint256 | new min. deposit value |
-### M_BTC_DEPOSIT_VAULT_ADMIN_ROLE
+### setMaxSupplyCap
```solidity
-bytes32 M_BTC_DEPOSIT_VAULT_ADMIN_ROLE
+function setMaxSupplyCap(uint256 newValue) external
```
-actor that can manage MBtcDepositVault
-
-### M_BTC_REDEMPTION_VAULT_ADMIN_ROLE
+sets new max supply cap value
+can be called only from vault`s admin
-```solidity
-bytes32 M_BTC_REDEMPTION_VAULT_ADMIN_ROLE
-```
+#### Parameters
-actor that can manage MBtcRedemptionVault
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newValue | uint256 | new max supply cap value |
-### M_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+### setMaxAmountPerRequest
```solidity
-bytes32 M_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+function setMaxAmountPerRequest(uint256 newValue) external
```
-actor that can manage MBtcCustomAggregatorFeed and MBtcDataFeed
+sets new max amount per request
+can be called only from vault`s admin
-## MBtcRedemptionVault
+#### Parameters
-Smart contract that handles mBTC redemption
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newValue | uint256 | new max amount per request |
-### vaultRole
+### getEffectiveMTokenSupply
```solidity
-function vaultRole() public pure returns (bytes32)
+function getEffectiveMTokenSupply() external view returns (uint256)
```
-## TACmBtcMidasAccessControlRoles
+calculates effective mToken supply including upcoming supply
+
+#### Return Values
-Base contract that stores all roles descriptors for TACmBTC contracts
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | effective mToken supply |
-### TAC_M_BTC_DEPOSIT_VAULT_ADMIN_ROLE
+### _depositInstant
```solidity
-bytes32 TAC_M_BTC_DEPOSIT_VAULT_ADMIN_ROLE
+function _depositInstant(address tokenIn, uint256 amountToken, uint256 minReceiveAmount, address recipient) internal virtual returns (struct DepositVault.CalcAndValidateDepositResult result)
```
-actor that can manage TACmBtcDepositVault
-
-### TAC_M_BTC_REDEMPTION_VAULT_ADMIN_ROLE
+_internal deposit instant logic_
-```solidity
-bytes32 TAC_M_BTC_REDEMPTION_VAULT_ADMIN_ROLE
-```
+#### Parameters
-actor that can manage TACmBtcRedemptionVault
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| tokenIn | address | tokenIn address |
+| amountToken | uint256 | amount of tokenIn (decimals 18) |
+| minReceiveAmount | uint256 | min amount of mToken to receive (decimals 18) |
+| recipient | address | recipient address |
-## TACmBtcRedemptionVault
+#### Return Values
-Smart contract that handles TACmBTC redemption
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| result | struct DepositVault.CalcAndValidateDepositResult | calculated deposit result |
-### vaultRole
+### _instantTransferTokensToTokensReceiver
```solidity
-function vaultRole() public pure returns (bytes32)
+function _instantTransferTokensToTokensReceiver(address tokenIn, uint256 amountToken, uint256 tokensDecimals) internal virtual
```
-## MBasisMidasAccessControlRoles
+_internal transfer tokens to tokens receiver (instant deposits)_
+
+#### Parameters
-Base contract that stores all roles descriptors for mBASIS contracts
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| tokenIn | address | tokenIn address |
+| amountToken | uint256 | amount of tokenIn (decimals 18) |
+| tokensDecimals | uint256 | tokens decimals |
-### M_BASIS_DEPOSIT_VAULT_ADMIN_ROLE
+### _requestTransferTokensToTokensReceiver
```solidity
-bytes32 M_BASIS_DEPOSIT_VAULT_ADMIN_ROLE
+function _requestTransferTokensToTokensReceiver(address tokenIn, uint256 amountToken, uint256 tokensDecimals) internal virtual
```
-actor that can manage MBasisDepositVault
+_internal transfer tokens to tokens receiver (deposit requests)_
-### M_BASIS_REDEMPTION_VAULT_ADMIN_ROLE
-
-```solidity
-bytes32 M_BASIS_REDEMPTION_VAULT_ADMIN_ROLE
-```
+#### Parameters
-actor that can manage MBasisRedemptionVault
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| tokenIn | address | tokenIn address |
+| amountToken | uint256 | amount of tokenIn (decimals 18) |
+| tokensDecimals | uint256 | tokens decimals |
-### M_BASIS_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+### _validateRequest
```solidity
-bytes32 M_BASIS_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+function _validateRequest(uint256 requestId, address validateAddress, enum RequestStatus status) internal pure
```
-actor that can manage MBasisCustomAggregatorFeed and MBasisDataFeed
+validates request
+if exist
+if status is expected
-## MBasisRedemptionVault
+#### Parameters
-Smart contract that handles mBASIS minting
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestId | uint256 | request id |
+| validateAddress | address | address to check if not zero |
+| status | enum RequestStatus | request status |
-### vaultRole
+### _calcAndValidateDeposit
```solidity
-function vaultRole() public pure returns (bytes32)
+function _calcAndValidateDeposit(address user, address tokenIn, uint256 amountToken, bool isInstant) internal returns (struct DepositVault.CalcAndValidateDepositResult result)
```
-## MBasisRedemptionVaultWithBUIDL
-
-Smart contract that handles mBASIS minting
+_validate deposit and calculate mint amount_
-### vaultRole
+#### Parameters
-```solidity
-function vaultRole() public pure returns (bytes32)
-```
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| user | address | user address |
+| tokenIn | address | tokenIn address |
+| amountToken | uint256 | tokenIn amount (decimals 18) |
+| isInstant | bool | is instant operation |
-## MBasisRedemptionVaultWithSwapper
+#### Return Values
-Smart contract that handles mBASIS redemptions
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| result | struct DepositVault.CalcAndValidateDepositResult | calculated deposit result |
-### vaultRole
+### _validateMaxSupplyCap
```solidity
-function vaultRole() public pure returns (bytes32)
+function _validateMaxSupplyCap(bool revertOnError) internal view returns (bool)
```
-## MEdgeMidasAccessControlRoles
+_validates that mToken.totalSupply() <= maxSupplyCap_
-Base contract that stores all roles descriptors for mEDGE contracts
+#### Parameters
-### M_EDGE_DEPOSIT_VAULT_ADMIN_ROLE
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| revertOnError | bool | if true, will revert if supply is exceeded if false, will return false if supply is exceeded without reverting |
-```solidity
-bytes32 M_EDGE_DEPOSIT_VAULT_ADMIN_ROLE
-```
+#### Return Values
-actor that can manage MEdgeDepositVault
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bool | true if supply is valid, false otherwise |
-### M_EDGE_REDEMPTION_VAULT_ADMIN_ROLE
+### _convertTokenToUsd
```solidity
-bytes32 M_EDGE_REDEMPTION_VAULT_ADMIN_ROLE
+function _convertTokenToUsd(address tokenIn, uint256 amount) internal view virtual returns (uint256 amountInUsd, uint256 rate)
```
-actor that can manage MEdgeRedemptionVault
-
-### M_EDGE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+_calculates USD amount from tokenIn amount_
-```solidity
-bytes32 M_EDGE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
-```
+#### Parameters
-actor that can manage MEdgeCustomAggregatorFeed and MEdgeDataFeed
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| tokenIn | address | tokenIn address |
+| amount | uint256 | amount of tokenIn (decimals 18) |
-## MEdgeRedemptionVaultWithSwapper
+#### Return Values
-Smart contract that handles mEDGE redemptions
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| amountInUsd | uint256 | converted amount to USD |
+| rate | uint256 | conversion rate |
-### vaultRole
+### _convertUsdToMToken
```solidity
-function vaultRole() public pure returns (bytes32)
+function _convertUsdToMToken(uint256 amountUsd) internal view virtual returns (uint256 amountMToken, uint256 mTokenRate)
```
-## TACmEdgeMidasAccessControlRoles
+_calculates mToken amount from USD amount_
-Base contract that stores all roles descriptors for TACmEdge contracts
+#### Parameters
-### TAC_M_EDGE_DEPOSIT_VAULT_ADMIN_ROLE
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| amountUsd | uint256 | amount of USD (decimals 18) |
-```solidity
-bytes32 TAC_M_EDGE_DEPOSIT_VAULT_ADMIN_ROLE
-```
+#### Return Values
-actor that can manage TACmEdgeDepositVault
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| amountMToken | uint256 | converted USD to mToken |
+| mTokenRate | uint256 | conversion rate |
-### TAC_M_EDGE_REDEMPTION_VAULT_ADMIN_ROLE
+### _calculateHoldbackPartRateFromAvg
```solidity
-bytes32 TAC_M_EDGE_REDEMPTION_VAULT_ADMIN_ROLE
+function _calculateHoldbackPartRateFromAvg(struct Request request, uint256 avgMTokenRate) internal pure returns (uint256)
```
-actor that can manage TACmEdgeRedemptionVault
+_calculates holdback part rate from avg rate_
+
+#### Parameters
-## TACmEdgeRedemptionVault
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| request | struct Request | request |
+| avgMTokenRate | uint256 | avg mToken rate |
-Smart contract that handles TACmEDGE redemption
+#### Return Values
-### vaultRole
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | holdback part rate |
-```solidity
-function vaultRole() public pure returns (bytes32)
-```
+## DepositVaultWithAave
-## MFOneMidasAccessControlRoles
+Smart contract that handles mToken minting and invests
+proceeds into Aave V3 Pool
-Base contract that stores all roles descriptors for mF-ONE contracts
+_If `aaveDepositsEnabled` is false, regular deposit flow is used_
-### M_FONE_DEPOSIT_VAULT_ADMIN_ROLE
+### aavePools
```solidity
-bytes32 M_FONE_DEPOSIT_VAULT_ADMIN_ROLE
+mapping(address => contract IAaveV3Pool) aavePools
```
-actor that can manage MFOneDepositVault
+mapping payment token to Aave V3 Pool
-### M_FONE_REDEMPTION_VAULT_ADMIN_ROLE
+### aaveDepositsEnabled
```solidity
-bytes32 M_FONE_REDEMPTION_VAULT_ADMIN_ROLE
+bool aaveDepositsEnabled
```
-actor that can manage MFOneRedemptionVault
+Whether Aave auto-invest deposits are enabled
-### M_FONE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+_if false, regular deposit flow will be used_
+
+### autoInvestFallbackEnabled
```solidity
-bytes32 M_FONE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+bool autoInvestFallbackEnabled
```
-actor that can manage MFOneCustomAggregatorFeed and MFOneDataFeed
-
-## MFOneRedemptionVaultWithSwapper
+Whether to fall back to raw token transfer on auto-invest failure
-Smart contract that handles mF-ONE redemptions
+_if false, the transaction will revert when auto-invest fails_
-### vaultRole
+### SetAavePool
```solidity
-function vaultRole() public pure returns (bytes32)
+event SetAavePool(address token, address pool)
```
-## MLiquidityMidasAccessControlRoles
+Emitted when an Aave V3 Pool is configured for a payment token
+
+#### Parameters
-Base contract that stores all roles descriptors for mLIQUIDITY contracts
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | payment token address |
+| pool | address | Aave V3 Pool address |
-### M_LIQUIDITY_DEPOSIT_VAULT_ADMIN_ROLE
+### RemoveAavePool
```solidity
-bytes32 M_LIQUIDITY_DEPOSIT_VAULT_ADMIN_ROLE
+event RemoveAavePool(address token)
```
-actor that can manage MLiquidityDepositVault
+Emitted when an Aave V3 Pool is removed for a payment token
-### M_LIQUIDITY_REDEMPTION_VAULT_ADMIN_ROLE
-
-```solidity
-bytes32 M_LIQUIDITY_REDEMPTION_VAULT_ADMIN_ROLE
-```
+#### Parameters
-actor that can manage MLiquidityRedemptionVault
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | payment token address |
-### M_LIQUIDITY_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+### SetAaveDepositsEnabled
```solidity
-bytes32 M_LIQUIDITY_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+event SetAaveDepositsEnabled(bool enabled)
```
-actor that can manage MLiquidityCustomAggregatorFeed and MLiquidityDataFeed
+Emitted when `aaveDepositsEnabled` flag is updated
-## MLiquidityRedemptionVault
+#### Parameters
-Smart contract that handles mLIQUIDITY redemptions
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| enabled | bool | Whether Aave deposits are enabled |
-### vaultRole
+### SetAutoInvestFallbackEnabled
```solidity
-function vaultRole() public pure returns (bytes32)
+event SetAutoInvestFallbackEnabled(bool enabled)
```
-## MMevMidasAccessControlRoles
+Emitted when `autoInvestFallbackEnabled` flag is updated
+
+#### Parameters
-Base contract that stores all roles descriptors for mMEV contracts
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| enabled | bool | Whether fallback to raw transfer is enabled |
-### M_MEV_DEPOSIT_VAULT_ADMIN_ROLE
+### TokenNotInPool
```solidity
-bytes32 M_MEV_DEPOSIT_VAULT_ADMIN_ROLE
+error TokenNotInPool(address aavePool, address token)
```
-actor that can manage MMevDepositVault
+when token is not in pool
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| aavePool | address | Aave V3 Pool address |
+| token | address | token address |
-### M_MEV_REDEMPTION_VAULT_ADMIN_ROLE
+### PoolNotSet
```solidity
-bytes32 M_MEV_REDEMPTION_VAULT_ADMIN_ROLE
+error PoolNotSet(address token)
```
-actor that can manage MMevRedemptionVault
+when pool is not set
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | token address |
-### M_MEV_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+### AutoInvestFailed
```solidity
-bytes32 M_MEV_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+error AutoInvestFailed(bytes err)
```
-actor that can manage MMevCustomAggregatorFeed and MMevDataFeed
+when auto-invest fails
-## MMevRedemptionVaultWithSwapper
+#### Parameters
-Smart contract that handles mMEV redemptions
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| err | bytes | error bytes |
-### vaultRole
+### constructor
```solidity
-function vaultRole() public pure returns (bytes32)
+constructor(bytes32 _contractAdminRole, bytes32 _greenlistedRole) public
```
-## TACmMevMidasAccessControlRoles
+Passes role identifiers to the base DepositVault constructor
+
+#### Parameters
-Base contract that stores all roles descriptors for TACmMEV contracts
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _contractAdminRole | bytes32 | contract admin role identifier |
+| _greenlistedRole | bytes32 | greenlisted role identifier |
-### TAC_M_MEV_DEPOSIT_VAULT_ADMIN_ROLE
+### setAavePool
```solidity
-bytes32 TAC_M_MEV_DEPOSIT_VAULT_ADMIN_ROLE
+function setAavePool(address _token, address _aavePool) external
```
-actor that can manage TACmMevDepositVault
+Sets the Aave V3 Pool for a specific payment token
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _token | address | payment token address |
+| _aavePool | address | Aave V3 Pool address for this token |
-### TAC_M_MEV_REDEMPTION_VAULT_ADMIN_ROLE
+### removeAavePool
```solidity
-bytes32 TAC_M_MEV_REDEMPTION_VAULT_ADMIN_ROLE
+function removeAavePool(address _token) external
```
-actor that can manage TACmMevRedemptionVault
+Removes the Aave V3 Pool for a specific payment token
-## TACmMevRedemptionVault
+#### Parameters
-Smart contract that handles TACmMEV redemption
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _token | address | payment token address |
-### vaultRole
+### setAaveDepositsEnabled
```solidity
-function vaultRole() public pure returns (bytes32)
+function setAaveDepositsEnabled(bool enabled) external
```
-## MRe7MidasAccessControlRoles
+Updates `aaveDepositsEnabled` value
+
+#### Parameters
-Base contract that stores all roles descriptors for mRE7 contracts
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| enabled | bool | whether Aave auto-invest deposits are enabled |
-### M_RE7_DEPOSIT_VAULT_ADMIN_ROLE
+### setAutoInvestFallbackEnabled
```solidity
-bytes32 M_RE7_DEPOSIT_VAULT_ADMIN_ROLE
+function setAutoInvestFallbackEnabled(bool enabled) external
```
-actor that can manage MRe7DepositVault
+Updates `autoInvestFallbackEnabled` value
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| enabled | bool | whether fallback to raw transfer is enabled on auto-invest failure |
-### M_RE7_REDEMPTION_VAULT_ADMIN_ROLE
+### _instantTransferTokensToTokensReceiver
```solidity
-bytes32 M_RE7_REDEMPTION_VAULT_ADMIN_ROLE
+function _instantTransferTokensToTokensReceiver(address tokenIn, uint256 amountToken, uint256 tokensDecimals) internal virtual
```
-actor that can manage MRe7RedemptionVault
+_overrides instant deposit transfer hook to auto-invest into Aave_
-### M_RE7_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+### _requestTransferTokensToTokensReceiver
```solidity
-bytes32 M_RE7_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+function _requestTransferTokensToTokensReceiver(address tokenIn, uint256 amountToken, uint256 tokensDecimals) internal virtual
```
-actor that can manage MRe7CustomAggregatorFeed and MRe7DataFeed
+_overrides request deposit transfer hook to auto-invest into Aave_
-## MRe7RedemptionVaultWithSwapper
+## DepositVaultWithMToken
-Smart contract that handles mRE7 redemptions
+Smart contract that handles mToken minting and invests
+proceeds into another mToken's DepositVault
-### vaultRole
+_If `mTokenDepositsEnabled` is false, regular deposit flow is used_
+
+### mTokenDepositVault
```solidity
-function vaultRole() public pure returns (bytes32)
+contract IDepositVault mTokenDepositVault
```
-## MRe7SolMidasAccessControlRoles
-
-Base contract that stores all roles descriptors for mRE7SOL contracts
+Target mToken DepositVault for auto-invest
-### M_RE7SOL_DEPOSIT_VAULT_ADMIN_ROLE
+### mTokenDepositsEnabled
```solidity
-bytes32 M_RE7SOL_DEPOSIT_VAULT_ADMIN_ROLE
+bool mTokenDepositsEnabled
```
-actor that can manage MRe7SolDepositVault
+Whether mToken auto-invest deposits are enabled
-### M_RE7SOL_REDEMPTION_VAULT_ADMIN_ROLE
+_if false, regular deposit flow will be used_
+
+### autoInvestFallbackEnabled
```solidity
-bytes32 M_RE7SOL_REDEMPTION_VAULT_ADMIN_ROLE
+bool autoInvestFallbackEnabled
```
-actor that can manage MRe7SolRedemptionVault
+Whether to fall back to raw token transfer on auto-invest failure
+
+_if false, the transaction will revert when auto-invest fails_
-### M_RE7SOL_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+### SetMTokenDepositVault
```solidity
-bytes32 M_RE7SOL_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+event SetMTokenDepositVault(address newVault)
```
-actor that can manage MRe7SolCustomAggregatorFeed and MRe7SolDataFeed
+Emitted when the mToken DepositVault address is updated
-## MRe7SolRedemptionVault
+#### Parameters
-Smart contract that handles mRE7SOL redemptions
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newVault | address | new mToken DepositVault address |
-### vaultRole
+### SetMTokenDepositsEnabled
```solidity
-function vaultRole() public pure returns (bytes32)
+event SetMTokenDepositsEnabled(bool enabled)
```
-## MSlMidasAccessControlRoles
+Emitted when `mTokenDepositsEnabled` flag is updated
+
+#### Parameters
-Base contract that stores all roles descriptors for mSL contracts
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| enabled | bool | Whether mToken deposits are enabled |
-### M_SL_DEPOSIT_VAULT_ADMIN_ROLE
+### SetAutoInvestFallbackEnabled
```solidity
-bytes32 M_SL_DEPOSIT_VAULT_ADMIN_ROLE
+event SetAutoInvestFallbackEnabled(bool enabled)
```
-actor that can manage MSlDepositVault
-
-### M_SL_REDEMPTION_VAULT_ADMIN_ROLE
+Emitted when `autoInvestFallbackEnabled` flag is updated
-```solidity
-bytes32 M_SL_REDEMPTION_VAULT_ADMIN_ROLE
-```
+#### Parameters
-actor that can manage MSlRedemptionVault
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| enabled | bool | Whether fallback to raw transfer is enabled |
-### M_SL_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+### ZeroMTokenReceived
```solidity
-bytes32 M_SL_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+error ZeroMTokenReceived(uint256 mTokenReceived)
```
-actor that can manage MSlCustomAggregatorFeed and MSlDataFeed
+when zero mToken is received
-## MSlRedemptionVaultWithSwapper
+#### Parameters
-Smart contract that handles mSL redemptions
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| mTokenReceived | uint256 | mToken received |
-### vaultRole
+### AutoInvestFailed
```solidity
-function vaultRole() public pure returns (bytes32)
+error AutoInvestFailed(bytes err)
```
-## MevBtcMidasAccessControlRoles
+when auto-invest fails
-Base contract that stores all roles descriptors for mevBTC contracts
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| err | bytes | error bytes |
-### MEV_BTC_DEPOSIT_VAULT_ADMIN_ROLE
+### constructor
```solidity
-bytes32 MEV_BTC_DEPOSIT_VAULT_ADMIN_ROLE
+constructor(bytes32 _contractAdminRole, bytes32 _greenlistedRole) public
```
-actor that can manage MevBtcDepositVault
+Passes role identifiers to the base DepositVault constructor
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _contractAdminRole | bytes32 | contract admin role identifier |
+| _greenlistedRole | bytes32 | greenlisted role identifier |
-### MEV_BTC_REDEMPTION_VAULT_ADMIN_ROLE
+### initialize
```solidity
-bytes32 MEV_BTC_REDEMPTION_VAULT_ADMIN_ROLE
+function initialize(struct CommonVaultInitParams _commonVaultInitParams, struct DepositVaultInitParams _depositVaultInitParams, address _mTokenDepositVault) external
```
-actor that can manage MevBtcRedemptionVault
+upgradeable pattern contract`s initializer
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _commonVaultInitParams | struct CommonVaultInitParams | init params for common vault |
+| _depositVaultInitParams | struct DepositVaultInitParams | init params for deposit vault |
+| _mTokenDepositVault | address | target mToken DepositVault address |
-### MEV_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+### setMTokenDepositVault
```solidity
-bytes32 MEV_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+function setMTokenDepositVault(address _mTokenDepositVault) external
```
-actor that can manage MevBtcCustomAggregatorFeed and MevBtcDataFeed
+Sets the target mToken DepositVault address
-## MevBtcRedemptionVaultWithSwapper
+#### Parameters
-Smart contract that handles mevBTC redemptions
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _mTokenDepositVault | address | new mToken DepositVault address |
-### vaultRole
+### setMTokenDepositsEnabled
```solidity
-function vaultRole() public pure returns (bytes32)
+function setMTokenDepositsEnabled(bool enabled) external
```
-## TBtcMidasAccessControlRoles
+Updates `mTokenDepositsEnabled` value
-Base contract that stores all roles descriptors for tBTC contracts
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| enabled | bool | whether mToken auto-invest deposits are enabled |
-### T_BTC_DEPOSIT_VAULT_ADMIN_ROLE
+### setAutoInvestFallbackEnabled
```solidity
-bytes32 T_BTC_DEPOSIT_VAULT_ADMIN_ROLE
+function setAutoInvestFallbackEnabled(bool enabled) external
```
-actor that can manage TBtcDepositVault
+Updates `autoInvestFallbackEnabled` value
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| enabled | bool | whether fallback to raw transfer is enabled on auto-invest failure |
-### T_BTC_REDEMPTION_VAULT_ADMIN_ROLE
+### _instantTransferTokensToTokensReceiver
```solidity
-bytes32 T_BTC_REDEMPTION_VAULT_ADMIN_ROLE
+function _instantTransferTokensToTokensReceiver(address tokenIn, uint256 amountToken, uint256 tokensDecimals) internal virtual
```
-actor that can manage TBtcRedemptionVault
+_overrides instant deposit transfer hook to auto-invest into target mToken DV_
-### T_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+### _requestTransferTokensToTokensReceiver
```solidity
-bytes32 T_BTC_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+function _requestTransferTokensToTokensReceiver(address tokenIn, uint256 amountToken, uint256 tokensDecimals) internal virtual
```
-actor that can manage TBtcCustomAggregatorFeed and TBtcDataFeed
+_overrides request deposit transfer hook to auto-invest into target mToken DV_
+
+## DepositVaultWithMorpho
-## TBtcRedemptionVaultWithSwapper
+Smart contract that handles mToken minting and invests
+proceeds into Morpho Vaults
-Smart contract that handles tBTC redemptions
+_If `morphoDepositsEnabled` is false, regular deposit flow is used_
-### vaultRole
+### morphoVaults
```solidity
-function vaultRole() public pure returns (bytes32)
+mapping(address => contract IMorphoVault) morphoVaults
```
-## TEthMidasAccessControlRoles
+mapping payment token to Morpho Vault
-Base contract that stores all roles descriptors for tETH contracts
-
-### T_ETH_DEPOSIT_VAULT_ADMIN_ROLE
+### morphoDepositsEnabled
```solidity
-bytes32 T_ETH_DEPOSIT_VAULT_ADMIN_ROLE
+bool morphoDepositsEnabled
```
-actor that can manage TEthDepositVault
+Whether Morpho auto-invest deposits are enabled
+
+_if false, regular deposit flow will be used_
-### T_ETH_REDEMPTION_VAULT_ADMIN_ROLE
+### autoInvestFallbackEnabled
```solidity
-bytes32 T_ETH_REDEMPTION_VAULT_ADMIN_ROLE
+bool autoInvestFallbackEnabled
```
-actor that can manage TEthRedemptionVault
+Whether to fall back to raw token transfer on auto-invest failure
-### T_ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+_if false, the transaction will revert when auto-invest fails_
+
+### SetMorphoVault
```solidity
-bytes32 T_ETH_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+event SetMorphoVault(address token, address vault)
```
-actor that can manage TEthCustomAggregatorFeed and TEthDataFeed
+Emitted when a Morpho Vault is configured for a payment token
-## TEthRedemptionVaultWithSwapper
+#### Parameters
-Smart contract that handles tETH redemptions
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | payment token address |
+| vault | address | Morpho Vault address |
-### vaultRole
+### RemoveMorphoVault
```solidity
-function vaultRole() public pure returns (bytes32)
+event RemoveMorphoVault(address token)
```
-## TUsdeMidasAccessControlRoles
+Emitted when a Morpho Vault is removed for a payment token
-Base contract that stores all roles descriptors for tUSDe contracts
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | payment token address |
-### T_USDE_DEPOSIT_VAULT_ADMIN_ROLE
+### SetMorphoDepositsEnabled
```solidity
-bytes32 T_USDE_DEPOSIT_VAULT_ADMIN_ROLE
+event SetMorphoDepositsEnabled(bool enabled)
```
-actor that can manage TUsdeDepositVault
+Emitted when `morphoDepositsEnabled` flag is updated
-### T_USDE_REDEMPTION_VAULT_ADMIN_ROLE
-
-```solidity
-bytes32 T_USDE_REDEMPTION_VAULT_ADMIN_ROLE
-```
+#### Parameters
-actor that can manage TUsdeRedemptionVault
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| enabled | bool | Whether Morpho deposits are enabled |
-### T_USDE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+### SetAutoInvestFallbackEnabled
```solidity
-bytes32 T_USDE_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+event SetAutoInvestFallbackEnabled(bool enabled)
```
-actor that can manage TUsdeCustomAggregatorFeed and TUsdeDataFeed
+Emitted when `autoInvestFallbackEnabled` flag is updated
-## TUsdeRedemptionVaultWithSwapper
+#### Parameters
-Smart contract that handles tUSDe redemptions
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| enabled | bool | Whether fallback to raw transfer is enabled |
-### vaultRole
+### AssetMismatch
```solidity
-function vaultRole() public pure returns (bytes32)
+error AssetMismatch(address morphoVault, address token)
```
-## RedemptionVaultTest
+when asset mismatch
-### _disableInitializers
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| morphoVault | address | Morpho Vault address |
+| token | address | token address |
+
+### VaultNotSet
```solidity
-function _disableInitializers() internal
+error VaultNotSet(address token)
```
-_Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
-Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
-to any version. It is recommended to use this to lock implementation contracts that are designed to be called
-through proxies.
+when vault is not set
-Emits an {Initialized} event the first time it is successfully executed._
+#### Parameters
-### initializeWithoutInitializer
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | token address |
+
+### ZeroShares
```solidity
-function initializeWithoutInitializer(address _ac, struct MTokenInitParams _mTokenInitParams, struct ReceiversInitParams _receiversInitParams, struct InstantInitParams _instantInitParams, address _sanctionsList, uint256 _variationTolerance, uint256 _minAmount, struct FiatRedeptionInitParams _fiatRedemptionInitParams, address _requestRedeemer) external
+error ZeroShares(uint256 shares)
```
-### setOverrideGetTokenRate
+when zero shares are received
-```solidity
-function setOverrideGetTokenRate(bool val) external
-```
+#### Parameters
-### setGetTokenRateValue
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| shares | uint256 | shares |
+
+### AutoInvestFailed
```solidity
-function setGetTokenRateValue(uint256 val) external
+error AutoInvestFailed(bytes err)
```
-### calcAndValidateRedeemTest
+when auto-invest fails
-```solidity
-function calcAndValidateRedeemTest(address user, address tokenOut, uint256 amountMTokenIn, bool isInstant, bool isFiat) external returns (struct RedemptionVault.CalcAndValidateRedeemResult calcResult)
-```
+#### Parameters
-### convertUsdToTokenTest
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| err | bytes | error bytes |
+
+### constructor
```solidity
-function convertUsdToTokenTest(uint256 amountUsd, address tokenOut) external returns (uint256 amountToken, uint256 tokenRate)
+constructor(bytes32 _contractAdminRole, bytes32 _greenlistedRole) public
```
-### convertMTokenToUsdTest
+Passes role identifiers to the base DepositVault constructor
-```solidity
-function convertMTokenToUsdTest(uint256 amountMToken) external returns (uint256 amountUsd, uint256 mTokenRate)
-```
+#### Parameters
-### _getTokenRate
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _contractAdminRole | bytes32 | contract admin role identifier |
+| _greenlistedRole | bytes32 | greenlisted role identifier |
+
+### setMorphoVault
```solidity
-function _getTokenRate(address dataFeed, bool stable) internal view returns (uint256)
+function setMorphoVault(address _token, address _morphoVault) external
```
-_get token rate depends on data feed and stablecoin flag_
+Sets the Morpho Vault for a specific payment token
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| dataFeed | address | address of dataFeed from token config |
-| stable | bool | is stablecoin |
+| _token | address | payment token address |
+| _morphoVault | address | Morpho Vault (ERC-4626) address for this token |
-## RedemptionVaultWithBUIDLTest
-
-### _disableInitializers
+### removeMorphoVault
```solidity
-function _disableInitializers() internal
+function removeMorphoVault(address _token) external
```
-_Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
-Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
-to any version. It is recommended to use this to lock implementation contracts that are designed to be called
-through proxies.
+Removes the Morpho Vault for a specific payment token
-Emits an {Initialized} event the first time it is successfully executed._
+#### Parameters
-## RedemptionVaultWithSwapperTest
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _token | address | payment token address |
-### _disableInitializers
+### setMorphoDepositsEnabled
```solidity
-function _disableInitializers() internal
+function setMorphoDepositsEnabled(bool enabled) external
```
-_Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
-Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
-to any version. It is recommended to use this to lock implementation contracts that are designed to be called
-through proxies.
+Updates `morphoDepositsEnabled` value
-Emits an {Initialized} event the first time it is successfully executed._
+#### Parameters
-## RedemptionVaultWithUSTBTest
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| enabled | bool | whether Morpho auto-invest deposits are enabled |
-### _disableInitializers
+### setAutoInvestFallbackEnabled
```solidity
-function _disableInitializers() internal
+function setAutoInvestFallbackEnabled(bool enabled) external
```
-_Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
-Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
-to any version. It is recommended to use this to lock implementation contracts that are designed to be called
-through proxies.
+Updates `autoInvestFallbackEnabled` value
-Emits an {Initialized} event the first time it is successfully executed._
+#### Parameters
-### checkAndRedeemUSTB
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| enabled | bool | whether fallback to raw transfer is enabled on auto-invest failure |
+
+### _instantTransferTokensToTokensReceiver
```solidity
-function checkAndRedeemUSTB(address token, uint256 amount) external
+function _instantTransferTokensToTokensReceiver(address tokenIn, uint256 amountToken, uint256 tokensDecimals) internal virtual
```
-## DepositVault
-
-Smart contract that handles mTBILL minting
+_overrides instant deposit transfer hook to auto-invest into Morpho_
-### CalcAndValidateDepositResult
+### _requestTransferTokensToTokensReceiver
```solidity
-struct CalcAndValidateDepositResult {
- uint256 tokenAmountInUsd;
- uint256 feeTokenAmount;
- uint256 amountTokenWithoutFee;
- uint256 mintAmount;
- uint256 tokenInRate;
- uint256 tokenOutRate;
- uint256 tokenDecimals;
-}
+function _requestTransferTokensToTokensReceiver(address tokenIn, uint256 amountToken, uint256 tokensDecimals) internal virtual
```
-### minMTokenAmountForFirstDeposit
+_overrides request deposit transfer hook to auto-invest into Morpho_
-```solidity
-uint256 minMTokenAmountForFirstDeposit
-```
+## DepositVaultWithUSTB
-minimal USD amount for first user`s deposit
+Smart contract that handles mToken minting and invests
+proceeds into USTB
-### mintRequests
+### ustb
```solidity
-mapping(uint256 => struct Request) mintRequests
+address ustb
```
-mapping, requestId => request data
+USTB token address
-### totalMinted
+### ustbDepositsEnabled
```solidity
-mapping(address => uint256) totalMinted
+bool ustbDepositsEnabled
```
-_depositor address => amount minted_
+Whether USTB deposits are enabled
-### initialize
+_if false, regular deposit flow will be used_
+
+### SetUstbDepositsEnabled
```solidity
-function initialize(address _ac, struct MTokenInitParams _mTokenInitParams, struct ReceiversInitParams _receiversInitParams, struct InstantInitParams _instantInitParams, address _sanctionsList, uint256 _variationTolerance, uint256 _minAmount, uint256 _minMTokenAmountForFirstDeposit) external
+event SetUstbDepositsEnabled(bool enabled)
```
-upgradeable pattern contract`s initializer
+Emitted when `ustbDepositsEnabled` flag is updated
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| _ac | address | address of MidasAccessControll contract |
-| _mTokenInitParams | struct MTokenInitParams | init params for mToken |
-| _receiversInitParams | struct ReceiversInitParams | init params for receivers |
-| _instantInitParams | struct InstantInitParams | init params for instant operations |
-| _sanctionsList | address | address of sanctionsList contract |
-| _variationTolerance | uint256 | percent of prices diviation 1% = 100 |
-| _minAmount | uint256 | basic min amount for operations in mToken |
-| _minMTokenAmountForFirstDeposit | uint256 | min amount for first deposit in mToken |
+| enabled | bool | Whether USTB deposits are enabled |
-### depositInstant
+### UnsupportedUSTBToken
```solidity
-function depositInstant(address tokenIn, uint256 amountToken, uint256 minReceiveAmount, bytes32 referrerId) external
+error UnsupportedUSTBToken(address token)
```
-depositing proccess with auto mint if
-account fit daily limit and token allowance.
-Transfers token from the user.
-Transfers fee in tokenIn to feeReceiver.
-Mints mToken to user.
+when USTB token is not supported
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| tokenIn | address | address of tokenIn |
-| amountToken | uint256 | amount of `tokenIn` that will be taken from user (decimals 18) |
-| minReceiveAmount | uint256 | minimum expected amount of mToken to receive (decimals 18) |
-| referrerId | bytes32 | referrer id |
+| token | address | token address |
-### depositInstant
+### USTBFeeNotZero
```solidity
-function depositInstant(address tokenIn, uint256 amountToken, uint256 minReceiveAmount, bytes32 referrerId, address recipient) external
+error USTBFeeNotZero(uint256 fee)
```
-Does the same as `depositInstant` but allows specifying a custom tokensReceiver address.
+when USTB fee is not zero
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| tokenIn | address | address of tokenIn |
-| amountToken | uint256 | amount of `tokenIn` that will be taken from user (decimals 18) |
-| minReceiveAmount | uint256 | minimum expected amount of mToken to receive (decimals 18) |
-| referrerId | bytes32 | referrer id |
-| recipient | address | |
+| fee | uint256 | fee |
-### depositRequest
+### constructor
```solidity
-function depositRequest(address tokenIn, uint256 amountToken, bytes32 referrerId) external returns (uint256)
+constructor(bytes32 _contractAdminRole, bytes32 _greenlistedRole) public
```
-depositing proccess with mint request creating if
-account fit token allowance.
-Transfers token from the user.
-Transfers fee in tokenIn to feeReceiver.
-Creates mint request.
+Passes role identifiers to the base DepositVault constructor
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| tokenIn | address | address of tokenIn |
-| amountToken | uint256 | amount of `tokenIn` that will be taken from user (decimals 18) |
-| referrerId | bytes32 | referrer id |
+| _contractAdminRole | bytes32 | contract admin role identifier |
+| _greenlistedRole | bytes32 | greenlisted role identifier |
-#### Return Values
+### initialize
+
+```solidity
+function initialize(struct CommonVaultInitParams _commonVaultInitParams, struct DepositVaultInitParams _depositVaultInitParams, address _ustb) external
+```
+
+upgradeable pattern contract`s initializer
+
+#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | uint256 | request id |
+| _commonVaultInitParams | struct CommonVaultInitParams | init params for common vault |
+| _depositVaultInitParams | struct DepositVaultInitParams | init params for deposit vault |
+| _ustb | address | USTB token address |
-### depositRequest
+### setUstbDepositsEnabled
```solidity
-function depositRequest(address tokenIn, uint256 amountToken, bytes32 referrerId, address recipient) external returns (uint256)
+function setUstbDepositsEnabled(bool enabled) external
```
-Does the same as `depositRequest` but allows specifying a custom tokensReceiver address.
+Updates `ustbDepositsEnabled` value
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| tokenIn | address | address of tokenIn |
-| amountToken | uint256 | amount of `tokenIn` that will be taken from user (decimals 18) |
-| referrerId | bytes32 | referrer id |
-| recipient | address | address that receives the mTokens |
+| enabled | bool | whether USTB deposits are enabled |
-#### Return Values
+### _instantTransferTokensToTokensReceiver
+
+```solidity
+function _instantTransferTokensToTokensReceiver(address tokenIn, uint256 amountToken, uint256 tokensDecimals) internal virtual
+```
+
+_overrides original transfer to tokens receiver function
+in case of USTB deposits are disabled or invest token is not supported
+by USTB, it will act as the original transfer
+otherwise it will take payment tokens from user, invest them into USTB
+and will transfer USTB to tokens receiver_
+
+#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | uint256 | request id |
+| tokenIn | address | token address |
+| amountToken | uint256 | amount of tokens to transfer in base18 |
+| tokensDecimals | uint256 | decimals of tokens |
+
+## RedemptionVault
-### safeApproveRequest
+Smart contract that handles mToken redemptions
+
+### CalcAndValidateRedeemResult
+
+return data of _calcAndValidateRedeem
+packed into a struct to avoid stack too deep errors
```solidity
-function safeApproveRequest(uint256 requestId, uint256 newOutRate) external
+struct CalcAndValidateRedeemResult {
+ uint256 feeAmount;
+ uint256 amountTokenOutWithoutFee;
+ uint256 amountTokenOut;
+ uint256 tokenOutRate;
+ uint256 mTokenRate;
+ uint256 tokenOutDecimals;
+}
```
-approving request if inputted token rate fit price diviation percent
-Mints mToken to user.
-Sets request flag to Processed.
+### redeemRequests
-#### Parameters
+```solidity
+mapping(uint256 => struct Request) redeemRequests
+```
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| requestId | uint256 | request id |
-| newOutRate | uint256 | mToken rate inputted by vault admin |
+mapping, requestId to request data
-### approveRequest
+### loanRequests
```solidity
-function approveRequest(uint256 requestId, uint256 newOutRate) external
+mapping(uint256 => struct LiquidityProviderLoanRequest) loanRequests
```
-approving request without price diviation check
-Mints mToken to user.
-Sets request flag to Processed.
+mapping, loanRequestId to loan request data
-#### Parameters
+### requestRedeemer
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| requestId | uint256 | request id |
-| newOutRate | uint256 | mToken rate inputted by vault admin |
+```solidity
+address requestRedeemer
+```
-### rejectRequest
+address is designated for standard redemptions, allowing tokens to be pulled from this address
+
+### loanLp
```solidity
-function rejectRequest(uint256 requestId) external
+address loanLp
```
-rejecting request
-Sets request flag to Canceled.
+address of loan liquidity provider
-#### Parameters
+### loanRepaymentAddress
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| requestId | uint256 | request id |
+```solidity
+address loanRepaymentAddress
+```
-### setMinMTokenAmountForFirstDeposit
+address from which payment tokens will be pulled during loan repayment
+
+### loanApr
```solidity
-function setMinMTokenAmountForFirstDeposit(uint256 newValue) external
+uint256 loanApr
```
-sets new minimal amount to deposit in EUR.
-can be called only from vault`s admin
+loan APR value in basis points (100 = 1%)
-#### Parameters
+### preferLoanLiquidity
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| newValue | uint256 | new min. deposit value |
+```solidity
+bool preferLoanLiquidity
+```
+
+flag to determine if the loan LP liquidity should be used first
-### vaultRole
+### currentLoanRequestId
```solidity
-function vaultRole() public pure virtual returns (bytes32)
+uint256 currentLoanRequestId
```
-AC role of vault administrator
+last loan request id
-#### Return Values
+### loanSwapperVault
+
+```solidity
+contract IRedemptionVault loanSwapperVault
+```
+
+address of loan RedemptionVault-compatible vault
+
+### constructor
+
+```solidity
+constructor(bytes32 _contractAdminRole, bytes32 _greenlistedRole) public
+```
+
+Passes role identifiers to the base ManageableVault constructor
+
+#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | bytes32 | role bytes32 role |
+| _contractAdminRole | bytes32 | contract admin role identifier |
+| _greenlistedRole | bytes32 | greenlisted role identifier |
-### _validateMinAmount
+### initialize
```solidity
-function _validateMinAmount(address user, uint256 amountMTokenWithoutFee) internal view
+function initialize(struct CommonVaultInitParams _commonVaultInitParams, struct RedemptionVaultInitParams _redemptionVaultInitParams) public
```
-_validates that inputted USD amount >= minAmountToDepositInUsd()
-and amount >= minAmount()_
+upgradeable pattern contract`s initializer
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| user | address | user address |
-| amountMTokenWithoutFee | uint256 | amount of mToken without fee (decimals 18) |
+| _commonVaultInitParams | struct CommonVaultInitParams | init params for common vault |
+| _redemptionVaultInitParams | struct RedemptionVaultInitParams | init params for redemption vault |
-### _depositInstant
+### redeemInstant
```solidity
-function _depositInstant(address tokenIn, uint256 amountToken, uint256 minReceiveAmount, address recipient) internal virtual returns (struct DepositVault.CalcAndValidateDepositResult result)
+function redeemInstant(address tokenOut, uint256 amountMTokenIn, uint256 minReceiveAmount) external returns (uint256)
```
-_internal deposit instant logic_
+redeem mToken to tokenOut if daily limit and allowance not exceeded
+Burns mToken from the user.
+Transfers tokenOut to user.
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| tokenIn | address | tokenIn address |
-| amountToken | uint256 | amount of tokenIn (decimals 18) |
-| minReceiveAmount | uint256 | min amount of mToken to receive (decimals 18) |
-| recipient | address | recipient address |
+| tokenOut | address | stable coin token address to redeem to |
+| amountMTokenIn | uint256 | amount of mToken to redeem (decimals 18) |
+| minReceiveAmount | uint256 | minimum expected amount of tokenOut to receive (decimals 18) |
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| result | struct DepositVault.CalcAndValidateDepositResult | calculated deposit result |
+| [0] | uint256 | amountTokenOut amount of tokenOut that was received in original decimals |
-### _calcAndValidateDeposit
+### redeemInstant
```solidity
-function _calcAndValidateDeposit(address user, address tokenIn, uint256 amountToken, bool isInstant) internal returns (struct DepositVault.CalcAndValidateDepositResult result)
+function redeemInstant(address tokenOut, uint256 amountMTokenIn, uint256 minReceiveAmount, address recipient) external returns (uint256)
```
-_validate deposit and calculate mint amount_
+Does the same as original `redeemInstant` but allows specifying a custom tokensReceiver address.
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| user | address | user address |
-| tokenIn | address | tokenIn address |
-| amountToken | uint256 | tokenIn amount (decimals 18) |
-| isInstant | bool | is instant operation |
+| tokenOut | address | stable coin token address to redeem to |
+| amountMTokenIn | uint256 | amount of mToken to redeem (decimals 18) |
+| minReceiveAmount | uint256 | minimum expected amount of tokenOut to receive (decimals 18) |
+| recipient | address | address that receives tokens |
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| result | struct DepositVault.CalcAndValidateDepositResult | calculated deposit result |
+| [0] | uint256 | amountTokenOut amount of tokenOut that was received in original decimals |
-### _convertTokenToUsd
+### redeemRequest
```solidity
-function _convertTokenToUsd(address tokenIn, uint256 amount) internal view virtual returns (uint256 amountInUsd, uint256 rate)
+function redeemRequest(address tokenOut, uint256 amountMTokenIn) external returns (uint256 requestId)
```
-_calculates USD amount from tokenIn amount_
+creating redeem request
+Transfers amount in mToken to contract
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| tokenIn | address | tokenIn address |
-| amount | uint256 | amount of tokenIn (decimals 18) |
+| tokenOut | address | stable coin token address to redeem to |
+| amountMTokenIn | uint256 | amount of mToken to redeem (decimals 18) |
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| amountInUsd | uint256 | converted amount to USD |
-| rate | uint256 | conversion rate |
+| requestId | uint256 | request id |
-### _convertUsdToMToken
+### redeemRequest
```solidity
-function _convertUsdToMToken(uint256 amountUsd) internal view virtual returns (uint256 amountMToken, uint256 mTokenRate)
+function redeemRequest(address tokenOut, uint256 amountMTokenIn, address recipientRequest, uint256 instantShare, uint256 minReceiveAmountInstantShare, address recipientInstant) external returns (uint256, uint256)
```
-_calculates mToken amount from USD amount_
+Instantly redeems `instantShare` amount of `amountMTokenIn` and creates a request for the remaining amount.
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| amountUsd | uint256 | amount of USD (decimals 18) |
+| tokenOut | address | stable coin token address to redeem to |
+| amountMTokenIn | uint256 | amount of mToken to redeem (decimals 18) |
+| recipientRequest | address | address that receives tokens for the request part |
+| instantShare | uint256 | % amount of `amountMTokenIn` that will be redeemed instantly |
+| minReceiveAmountInstantShare | uint256 | min receive amount for the instant share |
+| recipientInstant | address | address that receives tokens for the instant part |
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| amountMToken | uint256 | converted USD to mToken |
-| mTokenRate | uint256 | conversion rate |
-
-## EUsdDepositVault
-
-Smart contract that handles eUSD minting
+| [0] | uint256 | request id |
+| [1] | uint256 | instantReceivedAmount amount of tokenOut that was received instantly in original decimals |
-### vaultRole
+### safeBulkApproveRequestAtSavedRate
```solidity
-function vaultRole() public pure returns (bytes32)
+function safeBulkApproveRequestAtSavedRate(uint256[] requestIds) external
```
-### greenlistedRole
+approving requests from the `requestIds` array with the mToken rate
+from the request. WONT fail even if there is not enough liquidity
+to process all requests.
+Validates that new mToken rate does not exceed variation tolerance
+Transfers tokenOut to users
+Sets request flags to Processed.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestIds | uint256[] | request ids array |
+
+### safeBulkApproveRequest
```solidity
-function greenlistedRole() public pure returns (bytes32)
+function safeBulkApproveRequest(uint256[] requestIds) external
```
-AC role of a greenlist
+approving requests from the `requestIds` array with the
+current mToken rate. WONT fail even if there is not enough liquidity
+to process all requests.
+Validates that new mToken rate does not exceed variation tolerance
+Transfers tokenOut to users
+Sets request flags to Processed.
-#### Return Values
+#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | bytes32 | role bytes32 role |
+| requestIds | uint256[] | request ids array |
-## HBUsdtDepositVault
-
-Smart contract that handles hbUSDT minting
-
-### vaultRole
+### safeBulkApproveRequestAvgRate
```solidity
-function vaultRole() public pure returns (bytes32)
+function safeBulkApproveRequestAvgRate(uint256[] requestIds) external
```
-## HBXautDepositVault
+approving requests from the `requestIds` array with the
+current mToken rate as avg rate. WONT fail even if there is not enough liquidity
+to process all requests.
+Validates that new mToken rate does not exceed variation tolerance
+Transfers tokenOut to users
+Sets request flags to Processed.
+
+#### Parameters
-Smart contract that handles hbXAUt minting
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestIds | uint256[] | request ids array |
-### vaultRole
+### safeBulkApproveRequest
```solidity
-function vaultRole() public pure returns (bytes32)
+function safeBulkApproveRequest(uint256[] requestIds, uint256 newOutRate) external
```
-## HypeBtcDepositVault
+approving requests from the `requestIds` array using the `newMTokenRate`.
+WONT fail even if there is not enough liquidity to process all requests.
+Validates that new mToken rate does not exceed variation tolerance
+Transfers tokenOut to user
+Sets request flags to Processed.
+
+#### Parameters
-Smart contract that handles hypeBTC minting
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestIds | uint256[] | request ids array |
+| newOutRate | uint256 | |
-### vaultRole
+### safeBulkApproveRequestAvgRate
```solidity
-function vaultRole() public pure returns (bytes32)
+function safeBulkApproveRequestAvgRate(uint256[] requestIds, uint256 avgMTokenRate) external
```
-## HypeEthDepositVault
+approving requests from the `requestIds` array using the `avgMTokenRate`.
+WONT fail even if there is not enough liquidity to process all requests.
+Validates that new mToken rate does not exceed variation tolerance
+Transfers tokenOut to user
+Sets request flags to Processed.
-Smart contract that handles hypeETH minting
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestIds | uint256[] | request ids array |
+| avgMTokenRate | uint256 | avg mToken rate inputted by vault admin |
-### vaultRole
+### approveRequest
```solidity
-function vaultRole() public pure returns (bytes32)
+function approveRequest(uint256 requestId, uint256 newMTokenRate, bool isAvgRate) external
```
-## HypeUsdDepositVault
+approving redeem request if not exceed tokenOut allowance
+Burns amount mToken from contract
+Transfers tokenOut to user
+Sets flag Processed
+
+#### Parameters
-Smart contract that handles hypeUSD minting
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestId | uint256 | request id |
+| newMTokenRate | uint256 | new mToken rate inputted by vault admin |
+| isAvgRate | bool | if true, newMTokenRate is avg rate |
-### vaultRole
+### rejectRequest
```solidity
-function vaultRole() public pure returns (bytes32)
+function rejectRequest(uint256 requestId) external
```
-## Request
+rejecting request
+Sets request flag to Canceled.
-```solidity
-struct Request {
- address sender;
- address tokenIn;
- enum RequestStatus status;
- uint256 depositedUsdAmount;
- uint256 usdAmountWithoutFees;
- uint256 tokenOutRate;
-}
-```
+#### Parameters
-## IDepositVault
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestId | uint256 | request id |
-### SetMinMTokenAmountForFirstDeposit
+### bulkRepayLpLoanRequest
```solidity
-event SetMinMTokenAmountForFirstDeposit(address caller, uint256 newValue)
+function bulkRepayLpLoanRequest(uint256[] requestIds) external
```
+repaying loan requests from the `requestIds` array
+Transfers tokenOut to loan repayment address
+Sets request flags to Processed.
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| caller | address | function caller (msg.sender) |
-| newValue | uint256 | new min amount to deposit value |
+| requestIds | uint256[] | request ids array |
-### DepositInstant
+### cancelLpLoanRequest
```solidity
-event DepositInstant(address user, address tokenIn, uint256 amountUsd, uint256 amountToken, uint256 fee, uint256 minted, bytes32 referrerId)
+function cancelLpLoanRequest(uint256 requestId) external
```
+canceling loan request
+Sets request flags to Canceled.
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| user | address | function caller (msg.sender) |
-| tokenIn | address | address of tokenIn |
-| amountUsd | uint256 | amount of tokenIn converted to USD |
-| amountToken | uint256 | amount of tokenIn |
-| fee | uint256 | fee amount in tokenIn |
-| minted | uint256 | amount of minted mTokens |
-| referrerId | bytes32 | referrer id |
+| requestId | uint256 | request id |
-### DepositInstantWithCustomRecipient
+### setRequestRedeemer
```solidity
-event DepositInstantWithCustomRecipient(address user, address tokenIn, address recipient, uint256 amountUsd, uint256 amountToken, uint256 fee, uint256 minted, bytes32 referrerId)
+function setRequestRedeemer(address redeemer) external
```
+set address which is designated for standard redemptions, allowing tokens to be pulled from this address
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| user | address | function caller (msg.sender) |
-| tokenIn | address | address of tokenIn |
-| recipient | address | address that receives the mTokens |
-| amountUsd | uint256 | amount of tokenIn converted to USD |
-| amountToken | uint256 | amount of tokenIn |
-| fee | uint256 | fee amount in tokenIn |
-| minted | uint256 | amount of minted mTokens |
-| referrerId | bytes32 | referrer id |
+| redeemer | address | new address of request redeemer |
-### DepositRequest
+### setLoanLp
```solidity
-event DepositRequest(uint256 requestId, address user, address tokenIn, uint256 amountToken, uint256 amountUsd, uint256 fee, uint256 tokenOutRate, bytes32 referrerId)
+function setLoanLp(address newLoanLp) external
```
+set address of loan liquidity provider
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| requestId | uint256 | mint request id |
-| user | address | function caller (msg.sender) |
-| tokenIn | address | address of tokenIn |
-| amountToken | uint256 | amount of tokenIn |
-| amountUsd | uint256 | amount of tokenIn converted to USD |
-| fee | uint256 | fee amount in tokenIn |
-| tokenOutRate | uint256 | mToken rate |
-| referrerId | bytes32 | referrer id |
+| newLoanLp | address | new address of loan liquidity provider |
-### DepositRequestWithCustomRecipient
+### setLoanRepaymentAddress
```solidity
-event DepositRequestWithCustomRecipient(uint256 requestId, address user, address tokenIn, address recipient, uint256 amountToken, uint256 amountUsd, uint256 fee, uint256 tokenOutRate, bytes32 referrerId)
+function setLoanRepaymentAddress(address newLoanRepaymentAddress) external
```
+set address of loan repayment address
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| requestId | uint256 | mint request id |
-| user | address | function caller (msg.sender) |
-| tokenIn | address | address of tokenIn |
-| recipient | address | address that receives the mTokens |
-| amountToken | uint256 | amount of tokenIn |
-| amountUsd | uint256 | amount of tokenIn converted to USD |
-| fee | uint256 | fee amount in tokenIn |
-| tokenOutRate | uint256 | mToken rate |
-| referrerId | bytes32 | referrer id |
+| newLoanRepaymentAddress | address | new address of loan repayment address |
-### ApproveRequest
+### setLoanSwapperVault
```solidity
-event ApproveRequest(uint256 requestId, uint256 newOutRate)
+function setLoanSwapperVault(address newLoanSwapperVault) external
```
+set address of loan swapper vault
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| requestId | uint256 | mint request id |
-| newOutRate | uint256 | mToken rate inputted by admin |
+| newLoanSwapperVault | address | new address of loan swapper vault |
-### SafeApproveRequest
+### setLoanApr
```solidity
-event SafeApproveRequest(uint256 requestId, uint256 newOutRate)
+function setLoanApr(uint256 newLoanApr) external
```
+set loan APR value in basis points (100 = 1%)
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| requestId | uint256 | mint request id |
-| newOutRate | uint256 | mToken rate inputted by admin |
+| newLoanApr | uint256 | new loan APR value in basis points (100 = 1%) |
-### RejectRequest
+### setPreferLoanLiquidity
```solidity
-event RejectRequest(uint256 requestId, address user)
+function setPreferLoanLiquidity(bool newLoanLpFirst) external
```
+set flag to determine if the loan LP liquidity should be used first
+
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| requestId | uint256 | mint request id |
-| user | address | address of user |
+| newLoanLpFirst | bool | new flag to determine if the loan LP liquidity should be used first |
-### FreeFromMinDeposit
+### _obtainVaultLiquidity
```solidity
-event FreeFromMinDeposit(address user)
+function _obtainVaultLiquidity(address, uint256, uint256, uint256, uint256) internal virtual returns (uint256)
```
-#### Parameters
+_Check if contract has enough tokenOut balance for redeem,
+if not, obtains liquidity trough the custom strategies.
+In default implementation it does nothing._
+
+#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| user | address | address that was freed from min deposit check |
+| [0] | uint256 | obtainedLiquidityBase18 amount of tokenOut obtained |
-### depositInstant
+### _obtainVaultLiquidityExternal
```solidity
-function depositInstant(address tokenIn, uint256 amountToken, uint256 minReceiveAmount, bytes32 referrerId) external
+function _obtainVaultLiquidityExternal(address tokenOut, uint256 missingAmountBase18, uint256 tokenOutRate, uint256 currentTokenOutBalanceBase18, uint256 tokenOutDecimals) external returns (uint256)
```
-depositing proccess with auto mint if
-account fit daily limit and token allowance.
-Transfers token from the user.
-Transfers fee in tokenIn to feeReceiver.
-Mints mToken to user.
+This function can only be called by the contract itself (self-call restriction)
+
+_only calls _obtainVaultLiquidity internally and external because its used with try/catch_
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| tokenIn | address | address of tokenIn |
-| amountToken | uint256 | amount of `tokenIn` that will be taken from user (decimals 18) |
-| minReceiveAmount | uint256 | minimum expected amount of mToken to receive (decimals 18) |
-| referrerId | bytes32 | referrer id |
+| tokenOut | address | tokenOut address |
+| missingAmountBase18 | uint256 | amount of tokenOut needed in base 18 |
+| tokenOutRate | uint256 | tokenOut rate |
+| currentTokenOutBalanceBase18 | uint256 | current balance of tokenOut in the vault in base 18 |
+| tokenOutDecimals | uint256 | decimals of tokenOut |
-### depositInstant
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | obtainedLiquidityBase18 amount of tokenOut obtained |
+
+### _obtainLoanLpLiquidityExternal
```solidity
-function depositInstant(address tokenIn, uint256 amountToken, uint256 minReceiveAmount, bytes32 referrerId, address tokensReceiver) external
+function _obtainLoanLpLiquidityExternal(address tokenOut, uint256 missingAmountBase18, uint256 totalAmount, uint256 tokenOutRate, uint256 totalFee, uint256 tokenOutDecimals) external returns (uint256, uint256)
```
-Does the same as `depositInstant` but allows specifying a custom tokensReceiver address.
+This function can only be called by the contract itself (self-call restriction)
+
+_Check if contract has enough tokenOut balance for redeem;
+if not, redeem the missing amount via loan LP liquidity_
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| tokenIn | address | address of tokenIn |
-| amountToken | uint256 | amount of `tokenIn` that will be taken from user (decimals 18) |
-| minReceiveAmount | uint256 | minimum expected amount of mToken to receive (decimals 18) |
-| referrerId | bytes32 | referrer id |
-| tokensReceiver | address | address to receive the tokens (instead of msg.sender) |
+| tokenOut | address | tokenOut address |
+| missingAmountBase18 | uint256 | amount of tokenOut needed in base 18 |
+| totalAmount | uint256 | total amount of tokenOut needed in base 18 |
+| tokenOutRate | uint256 | tokenOut rate |
+| totalFee | uint256 | total fee of tokenOut |
+| tokenOutDecimals | uint256 | decimals of tokenOut |
-### depositRequest
+### _convertUsdToToken
```solidity
-function depositRequest(address tokenIn, uint256 amountToken, bytes32 referrerId) external returns (uint256)
+function _convertUsdToToken(uint256 amountUsd, address tokenOut, uint256 overrideTokenRate) internal view returns (uint256 amountToken, uint256 tokenRate)
```
-depositing proccess with mint request creating if
-account fit token allowance.
-Transfers token from the user.
-Transfers fee in tokenIn to feeReceiver.
-Creates mint request.
+_calculates tokenOut amount from USD amount_
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| tokenIn | address | address of tokenIn |
-| amountToken | uint256 | amount of `tokenIn` that will be taken from user (decimals 18) |
-| referrerId | bytes32 | referrer id |
+| amountUsd | uint256 | amount of USD (decimals 18) |
+| tokenOut | address | tokenOut address |
+| overrideTokenRate | uint256 | override token rate if not zero |
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | uint256 | request id |
+| amountToken | uint256 | converted USD to tokenOut |
+| tokenRate | uint256 | conversion rate |
-### depositRequest
+### _convertMTokenToUsd
```solidity
-function depositRequest(address tokenIn, uint256 amountToken, bytes32 referrerId, address recipient) external returns (uint256)
+function _convertMTokenToUsd(uint256 amountMToken, uint256 overrideTokenRate) internal view returns (uint256 amountUsd, uint256 mTokenRate)
```
-Does the same as `depositRequest` but allows specifying a custom tokensReceiver address.
+_calculates USD amount from mToken amount_
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| tokenIn | address | address of tokenIn |
-| amountToken | uint256 | amount of `tokenIn` that will be taken from user (decimals 18) |
-| referrerId | bytes32 | referrer id |
-| recipient | address | address that receives the mTokens |
+| amountMToken | uint256 | amount of mToken (decimals 18) |
+| overrideTokenRate | uint256 | override mToken rate if not zero |
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | uint256 | request id |
+| amountUsd | uint256 | converted amount to USD |
+| mTokenRate | uint256 | conversion rate |
-### safeApproveRequest
+### _calcAndValidateRedeem
```solidity
-function safeApproveRequest(uint256 requestId, uint256 newOutRate) external
+function _calcAndValidateRedeem(address user, address tokenOut, uint256 amountMTokenIn, uint256 overrideMTokenRate, uint256 overrideTokenOutRate, bool shouldOverrideFeePercent, uint256 overrideFeePercent, bool isInstant) internal view virtual returns (struct RedemptionVault.CalcAndValidateRedeemResult result)
```
-approving request if inputted token rate fit price diviation percent
-Mints mToken to user.
-Sets request flag to Processed.
+_validate redeem and calculate fee_
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| requestId | uint256 | request id |
-| newOutRate | uint256 | mToken rate inputted by vault admin |
+| user | address | user address |
+| tokenOut | address | tokenOut address |
+| amountMTokenIn | uint256 | mToken amount (decimals 18) |
+| overrideMTokenRate | uint256 | override mToken rate if not zero |
+| overrideTokenOutRate | uint256 | override token rate if not zero |
+| shouldOverrideFeePercent | bool | should override fee percent if true |
+| overrideFeePercent | uint256 | override fee percent if shouldOverrideFeePercent is true |
+| isInstant | bool | is instant operation |
-### approveRequest
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| result | struct RedemptionVault.CalcAndValidateRedeemResult | calc result |
+
+### _calculateHoldbackPartRateFromAvg
```solidity
-function approveRequest(uint256 requestId, uint256 newOutRate) external
+function _calculateHoldbackPartRateFromAvg(struct Request request, uint256 avgMTokenRate) internal pure returns (uint256)
```
-approving request without price diviation check
-Mints mToken to user.
-Sets request flag to Processed.
+_calculates holdback part rate from avg rate_
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| requestId | uint256 | request id |
-| newOutRate | uint256 | mToken rate inputted by vault admin |
+| request | struct Request | request |
+| avgMTokenRate | uint256 | avg mToken rate |
-### rejectRequest
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | holdback part rate |
+
+## RedemptionVaultWithAave
+
+Smart contract that handles redemptions using Aave V3 Pool withdrawals
+
+_When the vault has insufficient payment token balance, it withdraws from
+an Aave V3 Pool by burning its aTokens to obtain the underlying asset._
+
+### aavePools
```solidity
-function rejectRequest(uint256 requestId) external
+mapping(address => contract IAaveV3Pool) aavePools
```
-rejecting request
-Sets request flag to Canceled.
+mapping payment token to Aave V3 Pool
+
+### SetAavePool
+
+```solidity
+event SetAavePool(address token, address pool)
+```
+
+Emitted when an Aave V3 Pool is configured for a payment token
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| requestId | uint256 | request id |
+| token | address | payment token address |
+| pool | address | Aave V3 Pool address |
-### setMinMTokenAmountForFirstDeposit
+### RemoveAavePool
```solidity
-function setMinMTokenAmountForFirstDeposit(uint256 newValue) external
+event RemoveAavePool(address token)
```
-sets new minimal amount to deposit in EUR.
-can be called only from vault`s admin
+Emitted when an Aave V3 Pool is removed for a payment token
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| newValue | uint256 | new min. deposit value |
-
-## MBtcDepositVault
+| token | address | payment token address |
-Smart contract that handles mBTC minting
-
-### vaultRole
+### TokenNotInPool
```solidity
-function vaultRole() public pure returns (bytes32)
+error TokenNotInPool(address aavePool, address token)
```
-## TACmBtcDepositVault
+when token is not in aave pool
+
+#### Parameters
-Smart contract that handles TACmBTC minting
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| aavePool | address | Aave V3 Pool address |
+| token | address | token address |
-### vaultRole
+### PoolNotSet
```solidity
-function vaultRole() public pure returns (bytes32)
+error PoolNotSet(address token)
```
-## MBasisDepositVault
+when pool is not set
-Smart contract that handles mBASIS minting
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | token address |
-### vaultRole
+### InsufficientWithdrawnAmount
```solidity
-function vaultRole() public pure returns (bytes32)
+error InsufficientWithdrawnAmount(uint256 withdrawnAmount, uint256 toWithdraw)
```
-## MEdgeDepositVault
+when insufficient withdrawn amount
+
+#### Parameters
-Smart contract that handles mEDGE minting
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| withdrawnAmount | uint256 | withdrawn amount |
+| toWithdraw | uint256 | amount to withdraw |
-### vaultRole
+### constructor
```solidity
-function vaultRole() public pure returns (bytes32)
+constructor(bytes32 _contractAdminRole, bytes32 _greenlistedRole) public
```
-## TACmEdgeDepositVault
+Passes role identifiers to the base RedemptionVault constructor
+
+#### Parameters
-Smart contract that handles TACmEdge minting
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _contractAdminRole | bytes32 | contract admin role identifier |
+| _greenlistedRole | bytes32 | greenlisted role identifier |
-### vaultRole
+### setAavePool
```solidity
-function vaultRole() public pure returns (bytes32)
+function setAavePool(address _token, address _aavePool) external
```
-## MFOneDepositVault
+Sets the Aave V3 Pool for a specific payment token
+
+#### Parameters
-Smart contract that handles mF-ONE minting
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _token | address | payment token address |
+| _aavePool | address | Aave V3 Pool address for this token |
-### vaultRole
+### removeAavePool
```solidity
-function vaultRole() public pure returns (bytes32)
+function removeAavePool(address _token) external
```
-## MLiquidityDepositVault
+Removes the Aave V3 Pool for a specific payment token
+
+#### Parameters
-Smart contract that handles mLIQUIDITY minting
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _token | address | payment token address |
-### vaultRole
+### _obtainVaultLiquidity
```solidity
-function vaultRole() public pure returns (bytes32)
+function _obtainVaultLiquidity(address tokenOut, uint256 missingAmountBase18, uint256, uint256, uint256 tokenOutDecimals) internal virtual returns (uint256)
```
-## MMevDepositVault
+Check if contract has enough tokenOut balance for redeem;
+if not, withdraw the missing amount from the Aave V3 Pool
-Smart contract that handles mMEV minting
+_The Aave Pool burns the vault's aTokens and transfers the underlying
+asset directly to this contract. No approval is needed because the Pool
+burns aTokens from msg.sender (this contract) internally._
-### vaultRole
+#### Parameters
-```solidity
-function vaultRole() public pure returns (bytes32)
-```
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| tokenOut | address | tokenOut address |
+| missingAmountBase18 | uint256 | amount of tokenOut needed in base 18 |
+| | uint256 | |
+| | uint256 | |
+| tokenOutDecimals | uint256 | decimals of tokenOut |
-## TACmMevDepositVault
+## RedemptionVaultWithMToken
-Smart contract that handles TACmMEV minting
+Smart contract that handles redemptions using mToken RedemptionVault withdrawals
-### vaultRole
+_Storage layout is preserved for safe upgrades from RedemptionVaultWithSwapper_
+
+### redemptionVault
```solidity
-function vaultRole() public pure returns (bytes32)
+contract IRedemptionVault redemptionVault
```
-## MRe7DepositVault
-
-Smart contract that handles mRE7 minting
+mToken RedemptionVault used for fallback redemptions
-### vaultRole
+### SetRedemptionVault
```solidity
-function vaultRole() public pure returns (bytes32)
+event SetRedemptionVault(address newVault)
```
-## MRe7SolDepositVault
+Emitted when the redemption vault address is updated
+
+#### Parameters
-Smart contract that handles mRE7SOL minting
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newVault | address | new redemption vault address |
-### vaultRole
+### constructor
```solidity
-function vaultRole() public pure returns (bytes32)
+constructor(bytes32 _contractAdminRole, bytes32 _greenlistedRole) public
```
-## MSlDepositVault
+Passes role identifiers to the base RedemptionVault constructor
+
+#### Parameters
-Smart contract that handles mSL minting
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _contractAdminRole | bytes32 | contract admin role identifier |
+| _greenlistedRole | bytes32 | greenlisted role identifier |
-### vaultRole
+### initialize
```solidity
-function vaultRole() public pure returns (bytes32)
+function initialize(struct CommonVaultInitParams _commonVaultInitParams, struct RedemptionVaultInitParams _redemptionInitParams, address _redemptionVault) external
```
-## MevBtcDepositVault
+upgradeable pattern contract`s initializer
+
+#### Parameters
-Smart contract that handles mevBTC minting
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _commonVaultInitParams | struct CommonVaultInitParams | init params for common vault |
+| _redemptionInitParams | struct RedemptionVaultInitParams | init params for redemption vault state values |
+| _redemptionVault | address | address of the mTokenA RedemptionVault |
-### vaultRole
+### setRedemptionVault
```solidity
-function vaultRole() public pure returns (bytes32)
+function setRedemptionVault(address _redemptionVault) external
```
-## TBtcDepositVault
+Sets the mTokenA RedemptionVault address
+
+#### Parameters
-Smart contract that handles tBTC minting
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _redemptionVault | address | new RedemptionVault address |
-### vaultRole
+### _obtainVaultLiquidity
```solidity
-function vaultRole() public pure returns (bytes32)
+function _obtainVaultLiquidity(address tokenOut, uint256 missingAmountBase18, uint256 tokenOutRate, uint256, uint256 tokenOutDecimals) internal virtual returns (uint256)
```
-## TEthDepositVault
+Check if contract has enough tokenOut balance for redeem;
+if not, redeem the missing amount via mToken RedemptionVault
-Smart contract that handles tETH minting
+_The other vault burns this contract's mToken and transfers the
+underlying asset to this contract_
-### vaultRole
+#### Parameters
-```solidity
-function vaultRole() public pure returns (bytes32)
-```
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| tokenOut | address | tokenOut address |
+| missingAmountBase18 | uint256 | amount of tokenOut needed in base 18 |
+| tokenOutRate | uint256 | tokenOut rate |
+| | uint256 | |
+| tokenOutDecimals | uint256 | |
+
+## RedemptionVaultWithMorpho
-## TUsdeDepositVault
+Smart contract that handles redemptions using Morpho Vault withdrawals
-Smart contract that handles tUSDe minting
+_When the vault has insufficient payment token balance, it withdraws from
+a Morpho Vault (ERC-4626) by burning its vault shares to obtain the underlying asset.
+Works with both Morpho Vaults V1 (MetaMorpho) and V2._
-### vaultRole
+### morphoVaults
```solidity
-function vaultRole() public pure returns (bytes32)
+mapping(address => contract IMorphoVault) morphoVaults
```
-## DepositVaultTest
+mapping payment token to Morpho Vault
-### _disableInitializers
+### SetMorphoVault
```solidity
-function _disableInitializers() internal
+event SetMorphoVault(address token, address vault)
```
-_Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
-Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
-to any version. It is recommended to use this to lock implementation contracts that are designed to be called
-through proxies.
+Emitted when a Morpho Vault is configured for a payment token
-Emits an {Initialized} event the first time it is successfully executed._
+#### Parameters
-### tokenTransferFromToTester
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | payment token address |
+| vault | address | Morpho Vault address |
+
+### RemoveMorphoVault
```solidity
-function tokenTransferFromToTester(address token, address from, address to, uint256 amount, uint256 tokenDecimals) external
+event RemoveMorphoVault(address token)
```
-### tokenTransferToUserTester
+Emitted when a Morpho Vault is removed for a payment token
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | payment token address |
+
+### AssetMismatch
```solidity
-function tokenTransferToUserTester(address token, address to, uint256 amount, uint256 tokenDecimals) external
+error AssetMismatch(address morphoVault, address token)
```
-### setOverrideGetTokenRate
+when asset mismatch
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| morphoVault | address | Morpho Vault address |
+| token | address | token address |
+
+### VaultNotSet
+
+```solidity
+error VaultNotSet(address token)
+```
+
+when vault is not set
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | token address |
+
+### constructor
+
+```solidity
+constructor(bytes32 _contractAdminRole, bytes32 _greenlistedRole) public
+```
+
+Passes role identifiers to the base RedemptionVault constructor
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _contractAdminRole | bytes32 | contract admin role identifier |
+| _greenlistedRole | bytes32 | greenlisted role identifier |
+
+### setMorphoVault
+
+```solidity
+function setMorphoVault(address _token, address _morphoVault) external
+```
+
+Sets the Morpho Vault for a specific payment token
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _token | address | payment token address |
+| _morphoVault | address | Morpho Vault (ERC-4626) address for this token |
+
+### removeMorphoVault
+
+```solidity
+function removeMorphoVault(address _token) external
+```
+
+Removes the Morpho Vault for a specific payment token
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _token | address | payment token address |
+
+### _obtainVaultLiquidity
+
+```solidity
+function _obtainVaultLiquidity(address tokenOut, uint256 missingAmountBase18, uint256, uint256, uint256 tokenOutDecimals) internal virtual returns (uint256)
+```
+
+Check if contract has enough tokenOut balance for redeem;
+if not, withdraw the missing amount from the Morpho Vault
+
+_The Morpho Vault burns the vault's shares and transfers the underlying
+asset directly to this contract. No approval is needed because the vault
+burns shares from msg.sender (this contract) when msg.sender == owner._
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| tokenOut | address | tokenOut address |
+| missingAmountBase18 | uint256 | amount of tokenOut needed in base 18 |
+| | uint256 | |
+| | uint256 | |
+| tokenOutDecimals | uint256 | decimals of tokenOut |
+
+## RedemptionVaultWithUSTB
+
+Smart contract that handles redemptions using USTB
+
+### ustbRedemption
+
+```solidity
+contract IUSTBRedemption ustbRedemption
+```
+
+USTB redemption contract address
+
+_Used to handle USTB redemptions when vault has insufficient USDC_
+
+### constructor
+
+```solidity
+constructor(bytes32 _contractAdminRole, bytes32 _greenlistedRole) public
+```
+
+Passes role identifiers to the base RedemptionVault constructor
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _contractAdminRole | bytes32 | contract admin role identifier |
+| _greenlistedRole | bytes32 | greenlisted role identifier |
+
+### initialize
+
+```solidity
+function initialize(struct CommonVaultInitParams _commonVaultInitParams, struct RedemptionVaultInitParams _redemptionInitParams, address _ustbRedemption) external
+```
+
+upgradeable pattern contract`s initializer
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _commonVaultInitParams | struct CommonVaultInitParams | init params for common vault |
+| _redemptionInitParams | struct RedemptionVaultInitParams | init params for redemption vault state values |
+| _ustbRedemption | address | USTB redemption contract address |
+
+### _obtainVaultLiquidity
+
+```solidity
+function _obtainVaultLiquidity(address tokenOut, uint256 missingAmountBase18, uint256, uint256 currentTokenOutBalanceBase18, uint256 tokenOutDecimals) internal virtual returns (uint256)
+```
+
+Check if contract has enough USDC balance for redeem
+if not, trigger USTB redemption flow to redeem exactly the missing amount
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| tokenOut | address | tokenOut address |
+| missingAmountBase18 | uint256 | amount of tokenOut needed in base 18 |
+| | uint256 | |
+| currentTokenOutBalanceBase18 | uint256 | current balance of tokenOut in the vault in base 18 |
+| tokenOutDecimals | uint256 | decimals of tokenOut |
+
+## ManageableVault
+
+Contract with base Vault methods
+
+### STABLECOIN_RATE
+
+```solidity
+uint256 STABLECOIN_RATE
+```
+
+stable coin static rate 1:1 USD in 18 decimals
+
+### ONE_HUNDRED_PERCENT
+
+```solidity
+uint256 ONE_HUNDRED_PERCENT
+```
+
+100 percent with base 100
+
+_for example, 10% will be (10 * 100)%_
+
+### tokensConfig
+
+```solidity
+mapping(address => struct TokenConfig) tokensConfig
+```
+
+mapping, token address to token config
+
+### isFreeFromMinAmount
+
+```solidity
+mapping(address => bool) isFreeFromMinAmount
+```
+
+mapping, user address => is free frmo min amounts
+
+### waivedFeeRestriction
+
+```solidity
+mapping(address => bool) waivedFeeRestriction
+```
+
+address restriction with zero fees
+
+### _paymentTokens
+
+```solidity
+struct EnumerableSetUpgradeable.AddressSet _paymentTokens
+```
+
+_tokens that can be used as USD representation_
+
+### currentRequestId
+
+```solidity
+uint256 currentRequestId
+```
+
+last request id
+
+### nextExpectedRequestIdToProcess
+
+```solidity
+uint256 nextExpectedRequestIdToProcess
+```
+
+next expected request id to process
+
+### maxApproveRequestId
+
+```solidity
+uint256 maxApproveRequestId
+```
+
+max requestId that can be approved
+
+### mToken
+
+```solidity
+contract IMToken mToken
+```
+
+mToken token
+
+### mTokenDataFeed
+
+```solidity
+contract IDataFeed mTokenDataFeed
+```
+
+mToken data feed contract
+
+### tokensReceiver
+
+```solidity
+address tokensReceiver
+```
+
+address to which tokens and mTokens will be sent
+
+### variationTolerance
+
+```solidity
+uint256 variationTolerance
+```
+
+variation tolerance of tokenOut rates for "safe" requests approve
+
+### minAmount
+
+```solidity
+uint256 minAmount
+```
+
+basic min operations amount
+
+### instantFee
+
+```solidity
+uint256 instantFee
+```
+
+_fee for initial operations 1% = 100_
+
+### minInstantFee
+
+```solidity
+uint256 minInstantFee
+```
+
+minimum instant fee
+
+### maxInstantFee
+
+```solidity
+uint256 maxInstantFee
+```
+
+maximum instant fee
+
+### maxInstantShare
+
+```solidity
+uint256 maxInstantShare
+```
+
+maximum instant share value in basis points (100 = 1%)
+
+### sequentialRequestProcessing
+
+```solidity
+bool sequentialRequestProcessing
+```
+
+enforce sequential request processing flag
+
+### validateUserAccess
+
+```solidity
+modifier validateUserAccess(address recipient)
+```
+
+_validate msg.sender and recipient access, validates if function is not paused_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| recipient | address | recipient address |
+
+### constructor
+
+```solidity
+constructor(bytes32 _contractAdminRole, bytes32 _greenlistedRole) internal
+```
+
+constructor
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _contractAdminRole | bytes32 | contract admin role |
+| _greenlistedRole | bytes32 | greenlisted role |
+
+### __ManageableVault_init
+
+```solidity
+function __ManageableVault_init(struct CommonVaultInitParams _commonVaultInitParams) internal
+```
+
+_upgradeable pattern contract`s initializer_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _commonVaultInitParams | struct CommonVaultInitParams | init params for common vault |
+
+### addPaymentToken
+
+```solidity
+function addPaymentToken(address token, address dataFeed, uint256 tokenFee, uint256 allowance, bool stable) external
+```
+
+adds a token to the stablecoins list.
+can be called only from permissioned actor.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | token address |
+| dataFeed | address | dataFeed address |
+| tokenFee | uint256 | |
+| allowance | uint256 | token allowance (decimals 18) |
+| stable | bool | is stablecoin flag |
+
+### removePaymentToken
+
+```solidity
+function removePaymentToken(address token) external
+```
+
+removes a token from stablecoins list.
+can be called only from permissioned actor.
+
+_reverts if token is not presented_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | token address |
+
+### changeTokenAllowance
+
+```solidity
+function changeTokenAllowance(address token, uint256 allowance) external
+```
+
+set new token allowance.
+if type(uint256).max = infinite allowance
+prev allowance rewrites by new
+can be called only from permissioned actor.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | token address |
+| allowance | uint256 | new allowance (decimals 18) |
+
+### changeTokenFee
+
+```solidity
+function changeTokenFee(address token, uint256 fee) external
+```
+
+set new token fee.
+can be called only from permissioned actor.
+
+_reverts if new fee > 100%_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | token address |
+| fee | uint256 | new fee percent 1% = 100 |
+
+### setVariationTolerance
+
+```solidity
+function setVariationTolerance(uint256 tolerance) external
+```
+
+set new prices diviation percent.
+can be called only from permissioned actor.
+
+_reverts if new tolerance > 100%_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| tolerance | uint256 | new prices diviation percent 1% = 100 |
+
+### setMinAmount
+
+```solidity
+function setMinAmount(uint256 newAmount) external
+```
+
+set new min amount.
+can be called only from permissioned actor.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newAmount | uint256 | min amount for operations in mToken |
+
+### setWaivedFeeAccount
+
+```solidity
+function setWaivedFeeAccount(address account, bool enable) external
+```
+
+sets a account to waived fee restriction.
+can be called only from permissioned actor.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| account | address | user address |
+| enable | bool | is enabled |
+
+### setTokensReceiver
+
+```solidity
+function setTokensReceiver(address receiver) external
+```
+
+set new receiver for tokens.
+can be called only from permissioned actor.
+
+_reverts address zero or equal address(this)_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| receiver | address | new token receiver address |
+
+### setInstantFee
+
+```solidity
+function setInstantFee(uint256 newInstantFee) external
+```
+
+set operation fee percent.
+can be called only from permissioned actor.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newInstantFee | uint256 | new instant operations fee percent 1& = 100 |
+
+### setMinMaxInstantFee
+
+```solidity
+function setMinMaxInstantFee(uint256 newMinInstantFee, uint256 newMaxInstantFee) external
+```
+
+set new minimum/maximum instant fee
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newMinInstantFee | uint256 | new minimum instant fee |
+| newMaxInstantFee | uint256 | new maximum instant fee |
+
+### setMaxInstantShare
+
+```solidity
+function setMaxInstantShare(uint256 newMaxInstantShare) external
+```
+
+set maximum instant share value in basis points (100 = 1%)
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newMaxInstantShare | uint256 | new maximum instant share value in basis points (100 = 1%) |
+
+### setMaxApproveRequestId
+
+```solidity
+function setMaxApproveRequestId(uint256 newMaxApproveRequestId) external
+```
+
+sets max requestId that can be approved
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newMaxApproveRequestId | uint256 | new max requestId that can be approved |
+
+### setInstantLimitConfig
+
+```solidity
+function setInstantLimitConfig(uint256 window, uint256 limit) external
+```
+
+set operation limit configs.
+can be called only from permissioned actor.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| window | uint256 | window duration in seconds |
+| limit | uint256 | limit amount per window |
+
+### removeInstantLimitConfig
+
+```solidity
+function removeInstantLimitConfig(uint256 window) external
+```
+
+remove operation limit config.
+can be called only from permissioned actor.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| window | uint256 | window duration in seconds |
+
+### freeFromMinAmount
+
+```solidity
+function freeFromMinAmount(address user, bool enable) external
+```
+
+frees given `user` from the minimal deposit
+amount validation in `initiateDepositRequest`
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| user | address | address of user |
+| enable | bool | |
+
+### setSequentialRequestProcessing
+
+```solidity
+function setSequentialRequestProcessing(bool enforce) external
+```
+
+set enforce sequential request processing flag
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| enforce | bool | enforce sequential request processing flag |
+
+### withdrawToken
+
+```solidity
+function withdrawToken(address token, uint256 amount) external
+```
+
+withdraws `amount` of a given `token` from the contract
+to the `tokensReceiver` address
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | token address |
+| amount | uint256 | token amount |
+
+### getPaymentTokens
+
+```solidity
+function getPaymentTokens() external view returns (address[])
+```
+
+returns array of stablecoins supported by the vault
+can be called only from permissioned actor.
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | address[] | paymentTokens array of payment tokens |
+
+### getInstantLimitStatuses
+
+```solidity
+function getInstantLimitStatuses() external view returns (struct RateLimitLibrary.WindowRateLimitStatus[])
+```
+
+returns array of instant rate limit statuses
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | struct RateLimitLibrary.WindowRateLimitStatus[] | statuses array of instant rate limit statuses |
+
+### greenlistedRole
+
+```solidity
+function greenlistedRole() public view virtual returns (bytes32)
+```
+
+AC role of a greenlist
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bytes32 | role bytes32 role |
+
+### _tokenTransferFromUser
+
+```solidity
+function _tokenTransferFromUser(address token, address to, uint256 amount, uint256 tokenDecimals) internal returns (uint256 transferAmount)
+```
+
+_do safeTransferFrom on a given token
+and converts `amount` from base18
+to amount with a correct precision. Sends tokens
+from `msg.sender` to `tokensReceiver`_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | address of token |
+| to | address | address of user |
+| amount | uint256 | amount of `token` to transfer from `user` (decimals 18) |
+| tokenDecimals | uint256 | token decimals |
+
+### _tokenTransferFromTo
+
+```solidity
+function _tokenTransferFromTo(address token, address from, address to, uint256 amount, uint256 tokenDecimals) internal returns (uint256 transferAmount)
+```
+
+_do safeTransfer or safeTransferFrom on a given token
+and converts `amount` from base18
+to amount with a correct precision._
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | address of token |
+| from | address | address. If its address(this) the safeTransfer will be used instead of safeTransferFrom |
+| to | address | address |
+| amount | uint256 | amount of `token` to transfer from `user` |
+| tokenDecimals | uint256 | token decimals |
+
+### _requireAndUpdateLimit
+
+```solidity
+function _requireAndUpdateLimit(uint256 amount) internal
+```
+
+_check if operation exceed daily limit and update limit data_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| amount | uint256 | operation amount (decimals 18) |
+
+### _validateAndUpdateNextRequestIdToProcess
+
+```solidity
+function _validateAndUpdateNextRequestIdToProcess(uint256 requestId, bool revertIfInvalid) internal returns (bool isValid)
+```
+
+_check if request id is sequential and update next expected request id to process_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestId | uint256 | request id |
+| revertIfInvalid | bool | if true, reverts if request id is not sequential, otherwise returns false |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| isValid | bool | true if request id is sequential or sequentialRequestProcessing is disabled |
+
+### _tokenDecimals
+
+```solidity
+function _tokenDecimals(address token) internal view returns (uint8)
+```
+
+_retreives decimals of a given `token`_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | address of token |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint8 | decimals decinmals value of a given `token` |
+
+### _requireTokenExists
+
+```solidity
+function _requireTokenExists(address token) internal view virtual
+```
+
+_checks that `token` is presented in `_paymentTokens`_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | address of token |
+
+### _requireAndUpdateAllowance
+
+```solidity
+function _requireAndUpdateAllowance(address token, uint256 amount) internal
+```
+
+_check if operation exceed token allowance and update allowance_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | address of token |
+| amount | uint256 | operation amount (decimals 18) |
+
+### _getFeeAmount
+
+```solidity
+function _getFeeAmount(uint256 feePercent, uint256 amount) internal pure returns (uint256)
+```
+
+_returns calculated fee amount depends on the provided fee percent and amount_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| feePercent | uint256 | fee percent |
+| amount | uint256 | amount of token (decimals 18) |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | feeAmount calculated fee amount |
+
+### _getFee
+
+```solidity
+function _getFee(address sender, address token, bool isInstant) internal view returns (uint256 feePercent)
+```
+
+_returns calculated fee percent depends on parameters_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| sender | address | sender address |
+| token | address | token address |
+| isInstant | bool | is instant operation |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| feePercent | uint256 | calculated fee percent |
+
+### _validateInstantFee
+
+```solidity
+function _validateInstantFee() internal view
+```
+
+_validates instant fee is within the range of min/max instant fee_
+
+### _requireVariationTolerance
+
+```solidity
+function _requireVariationTolerance(uint256 prevPrice, uint256 newPrice) internal view
+```
+
+_check if prev and new prices diviation fit variationTolerance_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| prevPrice | uint256 | previous rate |
+| newPrice | uint256 | new rate |
+
+### _validateMTokenAmount
+
+```solidity
+function _validateMTokenAmount(address user, uint256 amountMToken) internal view returns (bool)
+```
+
+_validates that inputted mToken amount is >= minAmount()
+only if the `user` is not free from min amount_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| user | address | user address |
+| amountMToken | uint256 | amount of mToken |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bool | isFreeFromMinAmount if the `user` is free from min amount |
+
+### _validateUserAccess
+
+```solidity
+function _validateUserAccess(address user, bool validatePaused) internal view
+```
+
+_validate user access_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| user | address | user address |
+| validatePaused | bool | if true, validates if function is not paused |
+
+### _validateUserAccess
+
+```solidity
+function _validateUserAccess(address user, address recipient) internal view
+```
+
+_validate user access and validates if function is not paused_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| user | address | user address |
+| recipient | address | recipient address |
+
+### contractAdminRole
+
+```solidity
+function contractAdminRole() public view virtual returns (bytes32)
+```
+
+_main admin role for the contract_
+
+### _validateFunctionAccessWithTimelock
+
+```solidity
+function _validateFunctionAccessWithTimelock(bytes32 role, uint32 overrideDelay, bool roleIsFunctionOperator, address account, bool validateFunctionRole) internal view
+```
+
+_validates that the function access is valid with timelock_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| role | bytes32 | base role to validate |
+| overrideDelay | uint32 | override delay for the invocation |
+| roleIsFunctionOperator | bool | whether the role is a function operator |
+| account | address | account to validate |
+| validateFunctionRole | bool | whether to validate the function role |
+
+### _truncate
+
+```solidity
+function _truncate(uint256 value, uint256 decimals) internal pure returns (uint256)
+```
+
+_convert value to inputted decimals precision_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| value | uint256 | value for format |
+| decimals | uint256 | decimals |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | converted amount |
+
+### _validateFee
+
+```solidity
+function _validateFee(uint256 fee, bool checkMin) internal pure
+```
+
+_check if fee <= 100% and check > 0 if needs_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| fee | uint256 | fee value |
+| checkMin | bool | if need to check minimum |
+
+### _validateAddress
+
+```solidity
+function _validateAddress(address addr, bool selfCheck) internal view
+```
+
+_check if address not zero and not address(this)_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| addr | address | address to check |
+| selfCheck | bool | check if address not address(this) |
+
+### _getTokenRate
+
+```solidity
+function _getTokenRate(address dataFeed, bool stable) internal view virtual returns (uint256)
+```
+
+_get token rate depends on data feed and stablecoin flag_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| dataFeed | address | address of dataFeed from token config |
+| stable | bool | is stablecoin |
+
+### _getMTokenRate
+
+```solidity
+function _getMTokenRate() internal view returns (uint256 mTokenRate)
+```
+
+_gets and validates mToken rate_
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| mTokenRate | uint256 | mToken rate |
+
+### _getPTokenRate
+
+```solidity
+function _getPTokenRate(address token) internal view returns (uint256 tokenRate)
+```
+
+_gets and validates pToken rate_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | address of pToken |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| tokenRate | uint256 | token rate |
+
+### _requireSlippageNotExceeded
+
+```solidity
+function _requireSlippageNotExceeded(uint256 actualReceiveAmount, uint256 minReceiveAmount) internal pure
+```
+
+_validates that actual receive amount is greater than or equal to minimum receive amount_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| actualReceiveAmount | uint256 | actual receive amount |
+| minReceiveAmount | uint256 | minimum receive amount |
+
+### _validateMaxApproveRequestId
+
+```solidity
+function _validateMaxApproveRequestId(uint256 requestId, bool revertIfInvalid) internal view returns (bool isValid)
+```
+
+_validates that request id is less than or equal to max approve request id_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestId | uint256 | request id |
+| revertIfInvalid | bool | |
+
+## WithSanctionsList
+
+Base contract that uses sanctions oracle from
+Chainalysis to check that user is not sanctioned
+
+### sanctionsList
+
+```solidity
+address sanctionsList
+```
+
+address of Chainalysis sanctions oracle
+
+### SetSanctionsList
+
+```solidity
+event SetSanctionsList(address newSanctionsList)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newSanctionsList | address | new address of `sanctionsList` |
+
+### Sanctioned
+
+```solidity
+error Sanctioned(address user)
+```
+
+when user is sanctioned on sanctions list contract
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| user | address | user address |
+
+### onlyNotSanctioned
+
+```solidity
+modifier onlyNotSanctioned(address user)
+```
+
+_checks that a given `user` is not sanctioned_
+
+### __WithSanctionsList_init_unchained
+
+```solidity
+function __WithSanctionsList_init_unchained(address _sanctionsList) internal
+```
+
+_upgradeable pattern contract`s initializer unchained_
+
+### setSanctionsList
+
+```solidity
+function setSanctionsList(address newSanctionsList) external
+```
+
+updates `sanctionsList` address.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newSanctionsList | address | new sanctions list address |
+
+## Blacklistable
+
+Base contract that implements basic functions and modifiers
+to work with blacklistable
+
+### onlyNotBlacklisted
+
+```solidity
+modifier onlyNotBlacklisted(address account)
+```
+
+_checks that a given `account` doesnt have blacklisted role_
+
+### _onlyNotBlacklisted
+
+```solidity
+function _onlyNotBlacklisted(address account) internal view
+```
+
+_checks that a given `account` doesnt have blacklisted role_
+
+## Greenlistable
+
+Base contract that implements basic functions and modifiers
+to work with greenlistable
+
+### greenlistEnabled
+
+```solidity
+bool greenlistEnabled
+```
+
+is greenlist enabled
+
+### SetGreenlistEnable
+
+```solidity
+event SetGreenlistEnable(bool enable)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| enable | bool | enable |
+
+### onlyGreenlisted
+
+```solidity
+modifier onlyGreenlisted(address account)
+```
+
+_checks that a given `account` has `greenlistedRole()`_
+
+### setGreenlistEnable
+
+```solidity
+function setGreenlistEnable(bool enable) external
+```
+
+enable or disable greenlist.
+can be called only from permissioned actor.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| enable | bool | enable |
+
+### greenlistedRole
+
+```solidity
+function greenlistedRole() public view virtual returns (bytes32)
+```
+
+AC role of a greenlist
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bytes32 | role bytes32 role |
+
+## MidasAccessControl
+
+Smart contract that stores all roles for Midas project
+
+### GREENLIST_OPERATOR_ROLE
+
+```solidity
+bytes32 GREENLIST_OPERATOR_ROLE
+```
+
+actor that can change green list statuses of addresses
+
+### BLACKLIST_OPERATOR_ROLE
+
+```solidity
+bytes32 BLACKLIST_OPERATOR_ROLE
+```
+
+actor that can change black list statuses of addresses
+
+### isUserFacingRole
+
+```solidity
+mapping(bytes32 => bool) isUserFacingRole
+```
+
+roles that are held by users
+
+### timelockManager
+
+```solidity
+address timelockManager
+```
+
+address of MidasAccessControlTimelockController contract
+
+### pauseManager
+
+```solidity
+address pauseManager
+```
+
+address of MidasAccessControlTimelockController contract
+
+### defaultDelay
+
+```solidity
+uint32 defaultDelay
+```
+
+default delay for all of the roles
+
+### onlyRoleWithTimelock
+
+```solidity
+modifier onlyRoleWithTimelock(bytes32 role)
+```
+
+_validates that the msg.sender has the role_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| role | bytes32 | role to check access for |
+
+### onlyRoleDelayOverride
+
+```solidity
+modifier onlyRoleDelayOverride(bytes32 role, uint32 overrideDelay)
+```
+
+_validates that the caller has the function role with timelock_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| role | bytes32 | base role to validate |
+| overrideDelay | uint32 | override delay for the invocation |
+
+### initialize
+
+```solidity
+function initialize(uint32 _defaultDelay, bytes32[] _userFacingRoles) external
+```
+
+upgradeable pattern contract`s initializer
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _defaultDelay | uint32 | default delay |
+| _userFacingRoles | bytes32[] | array of additional user facing roles |
+
+### initializeV2
+
+```solidity
+function initializeV2(uint32 _defaultDelay, bytes32[] _userFacingRoles) public
+```
+
+initializerV2. Initializes user facing roles
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _defaultDelay | uint32 | |
+| _userFacingRoles | bytes32[] | array of additional user facing roles |
+
+### initializeRelationships
+
+```solidity
+function initializeRelationships(address _timelockManager, address _pauseManager) external
+```
+
+initializes timelock manager. Moved to a searate initializer
+as its 2-way dependency between the contracts.
+
+_can be called only by DEFAULT_ADMIN_ROLE_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _timelockManager | address | address of the timelock manager |
+| _pauseManager | address | address of the pause manager |
+
+### setDefaultDelay
+
+```solidity
+function setDefaultDelay(uint32 _defaultDelay) external
+```
+
+Sets the default delay
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _defaultDelay | uint32 | default delay in seconds |
+
+### setRoleDelayMult
+
+```solidity
+function setRoleDelayMult(struct IMidasAccessControl.SetRoleDelayParams[] params) external
+```
+
+Sets timelock delay per role
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| params | struct IMidasAccessControl.SetRoleDelayParams[] | array of SetRoleDelayParams |
+
+### setUserFacingRoleMult
+
+```solidity
+function setUserFacingRoleMult(struct IMidasAccessControl.SetUserFacingRoleParams[] params) external
+```
+
+Enable or disable which OZ role may administer function-access scopes for that role.
+
+_Only `DEFAULT_ADMIN_ROLE` can call this function.
+Prevents unrelated role admins from spamming access mappings._
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| params | struct IMidasAccessControl.SetUserFacingRoleParams[] | array of SetUserFacingRoleParams |
+
+### setGrantOperatorRoleMult
+
+```solidity
+function setGrantOperatorRoleMult(address targetContract, struct IMidasAccessControl.SetGrantOperatorRoleParams[] params) external
+```
+
+Add or remove a grant operator for a specific contract function scope.
+
+_`targetContract` must implement `IMidasAccessControlManaged` interface;
+Caller must hold `contractAdminRole` of a target contract;_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| targetContract | address | scoped contract |
+| params | struct IMidasAccessControl.SetGrantOperatorRoleParams[] | array of SetGrantOperatorRoleParams |
+
+### setPermissionRoleMult
+
+```solidity
+function setPermissionRoleMult(bytes32 masterRole, address targetContract, bytes4 functionSelector, uint32 delay, struct IMidasAccessControl.SetPermissionRoleParams[] params) public
+```
+
+Grant or revoke function access for an account
+
+_caller must be a grant operator for the scope or have the master role_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| masterRole | bytes32 | OZ role for the scope |
+| targetContract | address | scoped contract |
+| functionSelector | bytes4 | scoped function |
+| delay | uint32 | delay value |
+| params | struct IMidasAccessControl.SetPermissionRoleParams[] | array of SetPermissionRoleParams |
+
+### setPermissionRoleMult
+
+```solidity
+function setPermissionRoleMult(address targetContract, bytes4 functionSelector, uint32 delay, struct IMidasAccessControl.SetPermissionRoleParams[] params) external
+```
+
+Grant or revoke function access for an account
+
+_caller must be a grant operator for the scope or have the master role
+target contract must implement `IMidasAccessControlManaged` interface;_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| targetContract | address | scoped contract |
+| functionSelector | bytes4 | scoped function |
+| delay | uint32 | delay value |
+| params | struct IMidasAccessControl.SetPermissionRoleParams[] | array of SetPermissionRoleParams |
+
+### grantRoleMult
+
+```solidity
+function grantRoleMult(struct IMidasAccessControl.GrantRoleMultParams[] params) external
+```
+
+grant multiple roles to multiple users in one transaction
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| params | struct IMidasAccessControl.GrantRoleMultParams[] | array of GrantRoleMultParams |
+
+### revokeRoleMult
+
+```solidity
+function revokeRoleMult(struct IMidasAccessControl.RevokeRoleMultParams[] params) external
+```
+
+revoke multiple roles from multiple users in one transaction
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| params | struct IMidasAccessControl.RevokeRoleMultParams[] | array of RevokeRoleMultParams |
+
+### grantRole
+
+```solidity
+function grantRole(bytes32 role, address account) public
+```
+
+_Grants `role` to `account`.
+
+If `account` had not been already granted `role`, emits a {RoleGranted}
+event.
+
+Requirements:
+
+- the caller must have ``role``'s admin role.
+
+May emit a {RoleGranted} event._
+
+### grantRole
+
+```solidity
+function grantRole(bytes32 role, address account, uint32 delay) public
+```
+
+Grant a role to an account with a delay
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| role | bytes32 | role id |
+| account | address | account to grant the role to |
+| delay | uint32 | delay value |
+
+### revokeRole
+
+```solidity
+function revokeRole(bytes32 role, address account) public
+```
+
+_Revokes `role` from `account`.
+
+If `account` had been granted `role`, emits a {RoleRevoked} event.
+
+Requirements:
+
+- the caller must have ``role``'s admin role.
+
+May emit a {RoleRevoked} event._
+
+### setRoleAdmin
+
+```solidity
+function setRoleAdmin(bytes32 role, bytes32 newAdminRole) external
+```
+
+set the admin role for a specific role
+
+_can be called only by the address that holds `DEFAULT_ADMIN_ROLE`_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| role | bytes32 | the role to set the admin role for |
+| newAdminRole | bytes32 | the new admin role |
+
+### renounceRole
+
+```solidity
+function renounceRole(bytes32, address) public pure
+```
+
+renouce role is forbidden
+
+### isFunctionAccessGrantOperator
+
+```solidity
+function isFunctionAccessGrantOperator(bytes32 masterRole, address targetContract, bytes4 functionSelector, address operator) external view returns (bool)
+```
+
+Whether `operator` may call `setFunctionPermission` for the function scope
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| masterRole | bytes32 | OZ role for the scope |
+| targetContract | address | scoped contract |
+| functionSelector | bytes4 | scoped function |
+| operator | address | address checked for grant-operator status |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bool | allowed whether `operator` is a grant operator for the scope |
+
+### isFunctionAccessGrantOperator
+
+```solidity
+function isFunctionAccessGrantOperator(bytes32 key, address operator) public view returns (bool)
+```
+
+Whether `operator` may call `setFunctionPermission` for the function scope
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| key | bytes32 | operator permission key |
+| operator | address | address checked for grant-operator status |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bool | allowed whether `operator` is a grant operator for the scope |
+
+### hasFunctionPermission
+
+```solidity
+function hasFunctionPermission(bytes32 masterRole, address targetContract, bytes4 functionSelector, address account) external view returns (bool)
+```
+
+Whether `account` may call the scoped function on `targetContract`.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| masterRole | bytes32 | OZ role for the scope |
+| targetContract | address | scoped contract |
+| functionSelector | bytes4 | scoped function |
+| account | address | address checked for permissio. |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bool | allowed whether `account` has function access for the scope |
+
+### hasFunctionPermission
+
+```solidity
+function hasFunctionPermission(bytes32 key, address account) external view returns (bool)
+```
+
+Whether `account` has function access for the scope.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| key | bytes32 | the base key for function permission mappings |
+| account | address | address checked for permission |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bool | allowed whether `account` has function access for the scope |
+
+### permissionRoleKey
+
+```solidity
+function permissionRoleKey(bytes32 masterRole, address targetContract, bytes4 functionSelector) public pure returns (bytes32)
+```
+
+calculates the base key for function permission mappings
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| masterRole | bytes32 | OZ role |
+| targetContract | address | scoped contract |
+| functionSelector | bytes4 | scoped function of a `targetContract` |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bytes32 | key the base key for function permission mappings |
+
+### grantOperatorRoleKey
+
+```solidity
+function grantOperatorRoleKey(bytes32 masterRole, address targetContract, bytes4 functionSelector) public pure returns (bytes32)
+```
+
+calculates the base key for function permission mappings
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| masterRole | bytes32 | OZ role |
+| targetContract | address | scoped contract |
+| functionSelector | bytes4 | scoped function of a `targetContract` |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bytes32 | key the base key for function permission mappings |
+
+### getRoleTimelockDelay
+
+```solidity
+function getRoleTimelockDelay(bytes32 role, uint32 overrideDelay) public view returns (uint32, bool)
+```
+
+Returns timelock delay for a role
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| role | bytes32 | role id |
+| overrideDelay | uint32 | override delay for the invocation |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint32 | |
+| [1] | bool | |
+
+### contractAdminRole
+
+```solidity
+function contractAdminRole() public view returns (bytes32)
+```
+
+returns the role that can pause the contract
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bytes32 | role role descriptor |
+
+### _validateRoleAccess
+
+```solidity
+function _validateRoleAccess(bytes32 role, uint32 overrideDelay) internal view returns (address)
+```
+
+validates that the msg.sender with a role has access to the function
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| role | bytes32 | role to check access for |
+| overrideDelay | uint32 | override delay for the invocation |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | address | actualAccount actual account that has access to the function |
+
+### _validateRoleAccess
+
+```solidity
+function _validateRoleAccess(bytes32 role) internal view returns (address)
+```
+
+validates that the msg.sender with a role has access to the function
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| role | bytes32 | role to check access for |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | address | actualAccount actual account that has access to the function |
+
+### _validateOperatorRoleAccess
+
+```solidity
+function _validateOperatorRoleAccess(bytes32 masterRole, bytes32 operatorRole, address account) internal view
+```
+
+_validates that the account with a master or operator role has access to the function
+selects a role with a shortest delay in case if has both roles_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| masterRole | bytes32 | master role |
+| operatorRole | bytes32 | operator role |
+| account | address | account to check access for |
+
+### _resolveOperatorRole
+
+```solidity
+function _resolveOperatorRole(bytes32 masterRole, bytes32 operatorRole, address account) internal view returns (bytes32)
+```
+
+_validates that the account has either operator or master role and uses the role with a shortest delay_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| masterRole | bytes32 | master role |
+| operatorRole | bytes32 | operator role |
+| account | address | account to check access for |
+
+## MidasPauseManager
+
+Global manager for pausing and unpausing functions
+
+### DELAY_FOR_SET_DELAY
+
+```solidity
+uint32 DELAY_FOR_SET_DELAY
+```
+
+static delay for setting pause delay
+
+### contractPaused
+
+```solidity
+mapping(address => bool) contractPaused
+```
+
+contract => paused status
+
+### contractFnPaused
+
+```solidity
+mapping(address => mapping(bytes4 => bool)) contractFnPaused
+```
+
+contract => function id => paused status
+
+### pauseDelay
+
+```solidity
+uint32 pauseDelay
+```
+
+pause delay
+
+### unpauseDelay
+
+```solidity
+uint32 unpauseDelay
+```
+
+unpause delay
+
+### globalPaused
+
+```solidity
+bool globalPaused
+```
+
+global paused status
+
+### onlyPausableContractAdminPause
+
+```solidity
+modifier onlyPausableContractAdminPause(address contractAddr)
+```
+
+_validates that caller has access to the `contractAddr` contract admin role
+overrides delay for the invocation with pause delay_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| contractAddr | address | address of the contract |
+
+### onlyPausableContractAdminUnpause
+
+```solidity
+modifier onlyPausableContractAdminUnpause(address contractAddr)
+```
+
+_validates that caller has access to the `contractAddr` contract admin role
+overrides delay for the invocation with unpause delay_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| contractAddr | address | address of the contract |
+
+### onlyAdminPause
+
+```solidity
+modifier onlyAdminPause()
+```
+
+_validates that caller has access to the pause admin role
+overrides delay for the invocation with pause delay_
+
+### onlyAdminUnpause
+
+```solidity
+modifier onlyAdminUnpause()
+```
+
+_validates that caller has access to the unpause admin role
+overrides delay for the invocation with unpause delay_
+
+### initialize
+
+```solidity
+function initialize(address _accessControl, uint32 _pauseDelay, uint32 _unpauseDelay) external
+```
+
+upgradeable pattern contract`s initializer
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _accessControl | address | address of MidasAccessControl contract |
+| _pauseDelay | uint32 | pause delay |
+| _unpauseDelay | uint32 | unpause delay |
+
+### setPauseDelay
+
+```solidity
+function setPauseDelay(uint32 _pauseDelay) external
+```
+
+sets the pause delay
+
+_can be called only by the pause manager admin or function admin_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _pauseDelay | uint32 | pause delay |
+
+### setUnpauseDelay
+
+```solidity
+function setUnpauseDelay(uint32 _unpauseDelay) external
+```
+
+sets the unpause delay
+
+_can be called only by the pause manager admin or function admin_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _unpauseDelay | uint32 | unpause delay |
+
+### globalPause
+
+```solidity
+function globalPause() external
+```
+
+pauses the protocol
+
+_can be called only by the pause manager admin_
+
+### globalUnpause
+
+```solidity
+function globalUnpause() external
+```
+
+unpauses the protocol
+
+_can be called only by the pause manager admin_
+
+### bulkPauseContract
+
+```solidity
+function bulkPauseContract(address[] contractAddrs) external
+```
+
+pauses an array of contracts
+
+_can be called only by the pause manager admin or function admin_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| contractAddrs | address[] | array of contract addresses |
+
+### bulkUnpauseContract
+
+```solidity
+function bulkUnpauseContract(address[] contractAddrs) external
+```
+
+unpauses an array of contracts
+
+_can be called only by the pause manager admin or function admin_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| contractAddrs | address[] | array of contract addresses |
+
+### bulkPauseContractFn
+
+```solidity
+function bulkPauseContractFn(address[] contractAddrs, bytes4[] selectors) external
+```
+
+pauses functions on an array of contracts
+
+_can be called only by the pause manager admin or function admin_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| contractAddrs | address[] | array of contract addresses |
+| selectors | bytes4[] | function ids to pause on the contracts |
+
+### bulkUnpauseContractFn
+
+```solidity
+function bulkUnpauseContractFn(address[] contractAddrs, bytes4[] selectors) external
+```
+
+unpauses functions on an array of contracts
+
+_can be called only by the pause manager admin or function admin_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| contractAddrs | address[] | array of contract addresses |
+| selectors | bytes4[] | function ids to unpause on the contracts |
+
+### contractAdminPause
+
+```solidity
+function contractAdminPause(address contractAddr) external
+```
+
+pauses a contract
+
+_can be called only by admin of a contract or function admin that
+is managed by the admin of the contract_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| contractAddr | address | address of the contract |
+
+### contractAdminUnpause
+
+```solidity
+function contractAdminUnpause(address contractAddr) external
+```
+
+unpauses a contract
+
+_can be called only by admin of a contract or function admin that
+is managed by the admin of the contract_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| contractAddr | address | address of the contract |
+
+### isPaused
+
+```solidity
+function isPaused(address contractAddr, bytes4 selector) external view returns (bool)
+```
+
+checks if function or contract or protocol is paused
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| contractAddr | address | contract address |
+| selector | bytes4 | |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bool | paused true if paused |
+
+### isFunctionPaused
+
+```solidity
+function isFunctionPaused(address contractAddr, bytes4 selector) public view returns (bool)
+```
+
+checks if function of a contract is paused
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| contractAddr | address | contract address |
+| selector | bytes4 | function id |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bool | paused true if the function is paused |
+
+### pauseAdminRole
+
+```solidity
+function pauseAdminRole() public view returns (bytes32)
+```
+
+returns the admin role for the pause manager
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bytes32 | role admin role |
+
+### contractAdminRole
+
+```solidity
+function contractAdminRole() public pure returns (bytes32)
+```
+
+_main admin role for the contract_
+
+## MidasTimelockManager
+
+Manages timelock scheduling, security council votes and operation details
+
+### TimelockOperationDetails
+
+_internal storage for a timelock operation details_
+
+```solidity
+struct TimelockOperationDetails {
+ struct EnumerableSetUpgradeable.AddressSet votersForExecution;
+ struct EnumerableSetUpgradeable.AddressSet votersForVeto;
+ uint256 councilVersion;
+ bytes32 dataHash;
+ enum TimelockOperationStatus status;
+ uint8 pauseReasonCode;
+ bool isSetCouncilOperation;
+ uint32 createdAt;
+ uint32 executionApprovedAt;
+ address operationProposer;
+ address pauser;
+}
+```
+
+### TIMELOCK_OPERATION_PAUSER_ROLE
+
+```solidity
+bytes32 TIMELOCK_OPERATION_PAUSER_ROLE
+```
+
+role that can pause timelock operations
+
+### SECURITY_COUNCIL_MANAGER_ROLE
+
+```solidity
+bytes32 SECURITY_COUNCIL_MANAGER_ROLE
+```
+
+role that can set security council
+
+### SECURITY_COUNCIL_MIN_MEMBERS
+
+```solidity
+uint256 SECURITY_COUNCIL_MIN_MEMBERS
+```
+
+min security council members
+
+### SECURITY_COUNCIL_MAX_MEMBERS
+
+```solidity
+uint256 SECURITY_COUNCIL_MAX_MEMBERS
+```
+
+max security council members
+
+### EXPIRY_PERIOD
+
+```solidity
+uint256 EXPIRY_PERIOD
+```
+
+time after schedule when operation expires
+
+### DISPUTE_PERIOD
+
+```solidity
+uint256 DISPUTE_PERIOD
+```
+
+dispute period after execution approval
+
+### MAX_PENDING_OPERATIONS_PER_PROPOSER
+
+```solidity
+uint256 MAX_PENDING_OPERATIONS_PER_PROPOSER
+```
+
+hard cap for max pending operations per proposer
+
+### dataHashIndexes
+
+```solidity
+mapping(bytes32 => uint256) dataHashIndexes
+```
+
+Data hash index used for operation id salt
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+
+### proposerPendingOperationsCount
+
+```solidity
+mapping(address => uint256) proposerPendingOperationsCount
+```
+
+Pending operations count for a proposer
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+
+### timelock
+
+```solidity
+address timelock
+```
+
+Timelock controller address
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+
+### maxPendingOperationsPerProposer
+
+```solidity
+uint256 maxPendingOperationsPerProposer
+```
+
+Max pending operations per proposer
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+
+### securityCouncilVersion
+
+```solidity
+uint256 securityCouncilVersion
+```
+
+Current security council version
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+
+### pendingSetCouncilOperationId
+
+```solidity
+bytes32 pendingSetCouncilOperationId
+```
+
+Pending set-security-council operation id, if any
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+
+### onlyContractAdminNoTimelock
+
+```solidity
+modifier onlyContractAdminNoTimelock(bool validateFunctionRole)
+```
+
+_validates that the caller has the contract admin role without timelock_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| validateFunctionRole | bool | whether to validate the function role |
+
+### onlyContractAdminNoFunctionRole
+
+```solidity
+modifier onlyContractAdminNoFunctionRole()
+```
+
+_validates that the caller has the contract admin role without function role_
+
+### initialize
+
+```solidity
+function initialize(address _accessControl, uint256 _maxPendingOperationsPerProposer, address[] _initSecurityCouncil) external
+```
+
+Initializes the contract
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _accessControl | address | MidasAccessControl address |
+| _maxPendingOperationsPerProposer | uint256 | max pending ops per proposer |
+| _initSecurityCouncil | address[] | initial security council members |
+
+### initializeTimelock
+
+```solidity
+function initializeTimelock(address _timelock) external
+```
+
+Initializes the timelock controller
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _timelock | address | timelock controller address |
+
+### setMaxPendingOperationsPerProposer
+
+```solidity
+function setMaxPendingOperationsPerProposer(uint256 _maxPendingOperationsPerProposer) external
+```
+
+Sets max pending operations per proposer
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _maxPendingOperationsPerProposer | uint256 | new limit |
+
+### setSecurityCouncil
+
+```solidity
+function setSecurityCouncil(address[] members) external
+```
+
+Sets a new security council version
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| members | address[] | council member addresses |
+
+### bulkScheduleTimelockOperation
+
+```solidity
+function bulkScheduleTimelockOperation(struct IMidasTimelockManager.ScheduleTimelockOperationParams[] params) external
+```
+
+Schedules multiple timelock operations
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| params | struct IMidasTimelockManager.ScheduleTimelockOperationParams[] | array of schedule timelock operation parameters |
+
+### scheduleTimelockOperation
+
+```solidity
+function scheduleTimelockOperation(struct IMidasTimelockManager.ScheduleTimelockOperationParams params) external
+```
+
+Schedules one timelock operation
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| params | struct IMidasTimelockManager.ScheduleTimelockOperationParams | schedule timelock operation parameters |
+
+### executeTimelockOperation
+
+```solidity
+function executeTimelockOperation(address target, bytes data, bool revertOnFailure) external
+```
+
+Executes a scheduled timelock operation
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| target | address | target contract |
+| data | bytes | operation data |
+| revertOnFailure | bool | true if execution should revert on failure |
+
+### pauseOperation
+
+```solidity
+function pauseOperation(bytes32 operationId, uint8 pauseReasonCode) external
+```
+
+Pauses a pending operation
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| operationId | bytes32 | operation id |
+| pauseReasonCode | uint8 | reason code set by pauser |
+
+### voteForVeto
+
+```solidity
+function voteForVeto(bytes32 operationId) external
+```
+
+Security council votes to abort the operation
+
+_can vote even if member is already voted for execution_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| operationId | bytes32 | operation id |
+
+### voteForExecution
+
+```solidity
+function voteForExecution(bytes32 operationId) external
+```
+
+Security council votes to allow execution
+
+_cannot vote if member is already voted for veto_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| operationId | bytes32 | operation id |
+
+### abortOperation
+
+```solidity
+function abortOperation(bytes32 operationId) external
+```
+
+Aborts operation after veto quorum or expiry
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| operationId | bytes32 | operation id |
+
+### getOriginalProposer
+
+```solidity
+function getOriginalProposer(address target, bytes data) external view returns (address)
+```
+
+Returns original proposer for a pending operation
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| target | address | target contract |
+| data | bytes | operation data |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | address | proposer address |
+
+### councilQuorum
+
+```solidity
+function councilQuorum(uint256 version) public view returns (uint8)
+```
+
+Votes needed for council quorum at a version
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| version | uint256 | security council version |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint8 | |
+
+### getCouncilMemberVoteStatus
+
+```solidity
+function getCouncilMemberVoteStatus(bytes32 operationId, address councilMember) external view returns (bool votedForExecution, bool votedForVeto)
+```
+
+Whether a council member voted on an operation
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| operationId | bytes32 | operation id |
+| councilMember | address | member address |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| votedForExecution | bool | true if voted for execution |
+| votedForVeto | bool | true if voted for veto |
+
+### getPendingOperations
+
+```solidity
+function getPendingOperations() external view returns (bytes32[])
+```
+
+Returns all pending operation ids
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bytes32[] | |
+
+### getOperationDetails
+
+```solidity
+function getOperationDetails(bytes32 operationId) external view returns (struct GetOperationStatusResult result)
+```
+
+Returns full operation details
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| operationId | bytes32 | operation id |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| result | struct GetOperationStatusResult | operation details |
+
+### getOperationStatus
+
+```solidity
+function getOperationStatus(bytes32 operationId) external view returns (enum TimelockOperationStatus status)
+```
+
+Returns operation status (with expiry/dispute rules applied)
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| operationId | bytes32 | operation id |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| status | enum TimelockOperationStatus | current status |
+
+### getOperationStatusRaw
+
+```solidity
+function getOperationStatusRaw(bytes32 operationId) external view returns (enum TimelockOperationStatus status)
+```
+
+Returns stored operation status without adjustments
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| operationId | bytes32 | operation id |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| status | enum TimelockOperationStatus | stored status |
+
+### getSecurityCouncilMembers
+
+```solidity
+function getSecurityCouncilMembers(uint256 version) external view returns (address[])
+```
+
+Returns security council members for a version
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| version | uint256 | security council version |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | address[] | |
+
+### getOperationId
+
+```solidity
+function getOperationId(address target, bytes data) external view returns (bytes32 operationId)
+```
+
+Returns operation id for target and data
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| target | address | target contract |
+| data | bytes | operation data |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| operationId | bytes32 | operation id |
+
+### getTargetRole
+
+```solidity
+function getTargetRole(address target, bytes data, address proposer) public view returns (bytes32, uint32)
+```
+
+_gets the target role for a given operation_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| target | address | target contract |
+| data | bytes | operation data |
+| proposer | address | operation proposer address |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bytes32 | |
+| [1] | uint32 | |
+
+### isInSecurityCouncil
+
+```solidity
+function isInSecurityCouncil(uint256 version, address account) public view returns (bool)
+```
+
+Checks if an account is in the security council for a given version
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| version | uint256 | security council version |
+| account | address | account to check |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bool | true if the account is in the security council |
+
+### contractAdminRole
+
+```solidity
+function contractAdminRole() public pure returns (bytes32)
+```
+
+_main admin role for the contract_
+
+## CustomAggregatorV3CompatibleFeed
+
+AggregatorV3 compatible feed, where price is submitted manually by feed admins
+
+### RoundData
+
+```solidity
+struct RoundData {
+ uint80 roundId;
+ int256 answer;
+ uint256 startedAt;
+ uint256 updatedAt;
+ uint80 answeredInRound;
+}
+```
+
+### description
+
+```solidity
+string description
+```
+
+feed description
+
+### latestRound
+
+```solidity
+uint80 latestRound
+```
+
+last round id
+
+### maxAnswerDeviation
+
+```solidity
+uint256 maxAnswerDeviation
+```
+
+max deviation from lattest price in %
+
+_10 ** decimals() is a percentage precision_
+
+### minAnswer
+
+```solidity
+int192 minAnswer
+```
+
+minimal possible answer that feed can return
+
+### maxAnswer
+
+```solidity
+int192 maxAnswer
+```
+
+maximal possible answer that feed can return
+
+### AnswerUpdated
+
+```solidity
+event AnswerUpdated(int256 data, uint256 roundId, uint256 timestamp)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| data | int256 | data value |
+| roundId | uint256 | round id |
+| timestamp | uint256 | timestamp |
+
+### MaxAnswerDeviationUpdated
+
+```solidity
+event MaxAnswerDeviationUpdated(uint256 maxAnswerDeviation)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| maxAnswerDeviation | uint256 | the new max answer deviation |
+
+### SetMinMaxAnswer
+
+```solidity
+event SetMinMaxAnswer(int192 minAnswer, int192 maxAnswer)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| minAnswer | int192 | the new min answer |
+| maxAnswer | int192 | the new max answer |
+
+### constructor
+
+```solidity
+constructor(bytes32 _contractAdminRole) public
+```
+
+constructor
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _contractAdminRole | bytes32 | contract admin role |
+
+### initialize
+
+```solidity
+function initialize(address _accessControl, int192 _minAnswer, int192 _maxAnswer, uint256 _maxAnswerDeviation, string _description) public virtual
+```
+
+upgradeable pattern contract`s initializer
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _accessControl | address | address of MidasAccessControll contract |
+| _minAnswer | int192 | init value for `minAnswer`. Should be < `_maxAnswer` |
+| _maxAnswer | int192 | init value for `maxAnswer`. Should be > `_minAnswer` |
+| _maxAnswerDeviation | uint256 | init value for `maxAnswerDeviation` |
+| _description | string | init value for `description` |
+
+### setMinMaxAnswer
+
+```solidity
+function setMinMaxAnswer(int192 _minAnswer, int192 _maxAnswer) external
+```
+
+sets the min and max answer
+
+_the min and max answer are the minimum and maximum allowed values for the answer_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _minAnswer | int192 | the new min answer |
+| _maxAnswer | int192 | the new max answer |
+
+### setRoundDataSafe
+
+```solidity
+function setRoundDataSafe(int256 _data) external
+```
+
+works as `setRoundData()`, but also checks the
+deviation with the lattest submitted data, and that at least
+1 hour passed since the lattest submission
+
+_deviation with previous data needs to be <= `maxAnswerDeviation`_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _data | int256 | data value |
+
+### setRoundData
+
+```solidity
+function setRoundData(int256 _data) public
+```
+
+sets the data for `latestRound` + 1 round id
+
+_`_data` should be >= `minAnswer` and <= `maxAnswer`.
+Function should be called only from address with `contractAdminRole()`_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _data | int256 | data value |
+
+### setMaxAnswerDeviation
+
+```solidity
+function setMaxAnswerDeviation(uint256 _maxAnswerDeviation) external
+```
+
+sets the max answer deviation
+
+_the max answer deviation is the maximum allowed deviation from the latest price_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _maxAnswerDeviation | uint256 | the new max answer deviation |
+
+### latestRoundData
+
+```solidity
+function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
+```
+
+### version
+
+```solidity
+function version() external pure returns (uint256)
+```
+
+### lastAnswer
+
+```solidity
+function lastAnswer() public view returns (int256)
+```
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | int256 | answer of lattest price submission |
+
+### lastTimestamp
+
+```solidity
+function lastTimestamp() public view returns (uint256)
+```
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | timestamp of lattest price submission |
+
+### getRoundData
+
+```solidity
+function getRoundData(uint80 _roundId) public view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
+```
+
+### contractAdminRole
+
+```solidity
+function contractAdminRole() public view returns (bytes32)
+```
+
+_main admin role for the contract_
+
+### decimals
+
+```solidity
+function decimals() public pure returns (uint8)
+```
+
+### _getDeviation
+
+```solidity
+function _getDeviation(int256 _lastPrice, int256 _newPrice) internal pure returns (uint256)
+```
+
+_calculates a deviation in % between `_lastPrice` and `_newPrice`_
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | deviation in `10 ** decimals()` precision |
+
+## Request
+
+Mint request scruct
+
+```solidity
+struct Request {
+ address recipient;
+ address tokenIn;
+ enum RequestStatus status;
+ uint256 depositedUsdAmount;
+ uint256 usdAmountWithoutFees;
+ uint256 tokenOutRate;
+ uint256 depositedInstantUsdAmount;
+ uint256 approvedTokenOutRate;
+ uint256 amountMToken;
+}
+```
+
+## DepositVaultInitParams
+
+Deposit vault init params
+
+```solidity
+struct DepositVaultInitParams {
+ uint256 minMTokenAmountForFirstDeposit;
+ uint256 maxSupplyCap;
+ uint256 maxAmountPerRequest;
+}
+```
+
+## IDepositVault
+
+### SetMinMTokenAmountForFirstDeposit
+
+```solidity
+event SetMinMTokenAmountForFirstDeposit(uint256 newValue)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newValue | uint256 | new min amount to deposit value |
+
+### SetMaxSupplyCap
+
+```solidity
+event SetMaxSupplyCap(uint256 newValue)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newValue | uint256 | new max supply cap value |
+
+### SetMaxAmountPerRequest
+
+```solidity
+event SetMaxAmountPerRequest(uint256 newValue)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newValue | uint256 | new max amount per request |
+
+### DepositInstant
+
+```solidity
+event DepositInstant(address user, address tokenIn, address recipient, uint256 amountTokenIn, uint256 feeAmount, uint256 amountMToken, uint256 mTokenRate, uint256 tokenInRate, bytes32 referrerId)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| user | address | function caller (msg.sender) |
+| tokenIn | address | address of tokenIn |
+| recipient | address | address that receives the mTokens |
+| amountTokenIn | uint256 | amount of tokenIn |
+| feeAmount | uint256 | fee amount in tokenIn |
+| amountMToken | uint256 | amount of minted mTokens |
+| mTokenRate | uint256 | mToken rate |
+| tokenInRate | uint256 | tokenIn rate |
+| referrerId | bytes32 | referrer id |
+
+### DepositRequest
+
+```solidity
+event DepositRequest(uint256 requestId, address user, address tokenIn, address recipient, uint256 amountTokenIn, uint256 amountTokenInInstant, uint256 feeAmount, uint256 mTokenRate, uint256 tokenInRate, bytes32 referrerId)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestId | uint256 | mint request id |
+| user | address | function caller (msg.sender) |
+| tokenIn | address | address of tokenIn |
+| recipient | address | address that receives the mTokens |
+| amountTokenIn | uint256 | amount of tokenIn |
+| amountTokenInInstant | uint256 | amount of tokenIn that was deposited instantly |
+| feeAmount | uint256 | fee amount in tokenIn |
+| mTokenRate | uint256 | mToken rate |
+| tokenInRate | uint256 | tokenIn rate |
+| referrerId | bytes32 | referrer id |
+
+### ApproveRequest
+
+```solidity
+event ApproveRequest(uint256 requestId, uint256 newOutRate, bool isSafe, bool isAvgRate)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestId | uint256 | mint request id |
+| newOutRate | uint256 | mToken rate inputted by admin |
+| isSafe | bool | if true, approval is safe |
+| isAvgRate | bool | if true, newOutRate is avg rate |
+
+### RejectRequest
+
+```solidity
+event RejectRequest(uint256 requestId)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestId | uint256 | mint request id |
+
+### LessThanMinAmountFirstDeposit
+
+```solidity
+error LessThanMinAmountFirstDeposit(uint256 amountMTokenWithoutFee, uint256 minAmount)
+```
+
+first deposit mint amount is below minimum
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| amountMTokenWithoutFee | uint256 | mint amount after fee (decimals 18) |
+| minAmount | uint256 | minimum first deposit mint amount |
+
+### SupplyCapExceeded
+
+```solidity
+error SupplyCapExceeded()
+```
+
+when token supply cap is exceeded
+
+### MaxAmountPerRequestExceeded
+
+```solidity
+error MaxAmountPerRequestExceeded(uint256 estimatedMintAmount)
+```
+
+when max amount per request is exceeded
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| estimatedMintAmount | uint256 | estimated mint amount |
+
+### depositInstant
+
+```solidity
+function depositInstant(address tokenIn, uint256 amountToken, uint256 minReceiveAmount, bytes32 referrerId) external returns (uint256)
+```
+
+depositing proccess with auto mint if
+account fit daily limit and token allowance.
+Transfers token from the user.
+Transfers fee in tokenIn to tokensReceiver.
+Mints mToken to user.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| tokenIn | address | address of tokenIn |
+| amountToken | uint256 | amount of `tokenIn` that will be taken from user (decimals 18) |
+| minReceiveAmount | uint256 | minimum expected amount of mToken to receive (decimals 18) |
+| referrerId | bytes32 | referrer id |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | mintAmount amount of mToken that was minted |
+
+### depositInstant
+
+```solidity
+function depositInstant(address tokenIn, uint256 amountToken, uint256 minReceiveAmount, bytes32 referrerId, address tokensReceiver) external returns (uint256)
+```
+
+Does the same as original `depositInstant` but allows specifying a custom tokensReceiver address.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| tokenIn | address | address of tokenIn |
+| amountToken | uint256 | amount of `tokenIn` that will be taken from user (decimals 18) |
+| minReceiveAmount | uint256 | minimum expected amount of mToken to receive (decimals 18) |
+| referrerId | bytes32 | referrer id |
+| tokensReceiver | address | address to receive the tokens (instead of msg.sender) |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | mintAmount amount of mToken that was minted |
+
+### depositRequest
+
+```solidity
+function depositRequest(address tokenIn, uint256 amountToken, bytes32 referrerId) external returns (uint256)
+```
+
+depositing proccess with mint request creating if
+account fit token allowance.
+Transfers token from the user.
+Transfers fee in tokenIn to tokensReceiver.
+Creates mint request.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| tokenIn | address | address of tokenIn |
+| amountToken | uint256 | amount of `tokenIn` that will be taken from user (decimals 18) |
+| referrerId | bytes32 | referrer id |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | request id |
+
+### depositRequest
+
+```solidity
+function depositRequest(address tokenIn, uint256 amountToken, bytes32 referrerId, address recipientRequest, uint256 instantShare, uint256 minReceiveAmountInstantShare, address recipientInstant) external returns (uint256, uint256)
+```
+
+Instantly deposits `instantShare` amount of `amountMTokenIn` and creates a request for the remaining amount.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| tokenIn | address | address of tokenIn |
+| amountToken | uint256 | amount of `tokenIn` that will be taken from user (decimals 18) |
+| referrerId | bytes32 | referrer id |
+| recipientRequest | address | address that receives the mTokens for the request part |
+| instantShare | uint256 | % amount of `amountToken` that will be deposited instantly |
+| minReceiveAmountInstantShare | uint256 | min receive amount for the instant share |
+| recipientInstant | address | address that receives the mTokens for the instant part |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | request id |
+| [1] | uint256 | instantMintAmount amount of mToken that was minted instantly |
+
+### safeBulkApproveRequestAtSavedRate
+
+```solidity
+function safeBulkApproveRequestAtSavedRate(uint256[] requestIds) external
+```
+
+approving requests from the `requestIds` array
+with the mToken rate from the request.
+Validates that new mToken rate does not exceed variation tolerance
+Mints mToken to request users.
+Sets request flags to Processed.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestIds | uint256[] | request ids array |
+
+### safeBulkApproveRequest
+
+```solidity
+function safeBulkApproveRequest(uint256[] requestIds) external
+```
+
+approving requests from the `requestIds` array
+with the current mToken rate.
+Validates that new mToken rate does not exceed variation tolerance
+Mints mToken to request users.
+Sets request flags to Processed.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestIds | uint256[] | request ids array |
+
+### safeBulkApproveRequestAvgRate
+
+```solidity
+function safeBulkApproveRequestAvgRate(uint256[] requestIds) external
+```
+
+approving requests from the `requestIds` array
+with the current mToken rate.
+Validates that new mToken rate does not exceed variation tolerance
+Mints mToken to request users.
+Sets request flags to Processed.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestIds | uint256[] | request ids array |
+
+### safeBulkApproveRequest
+
+```solidity
+function safeBulkApproveRequest(uint256[] requestIds, uint256 newOutRate) external
+```
+
+approving requests from the `requestIds` array using the `newOutRate`.
+Validates that new mToken rate does not exceed variation tolerance
+Mints mToken to request users.
+Sets request flags to Processed.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestIds | uint256[] | request ids array |
+| newOutRate | uint256 | new mToken rate inputted by vault admin |
+
+### safeBulkApproveRequestAvgRate
+
+```solidity
+function safeBulkApproveRequestAvgRate(uint256[] requestIds, uint256 avgMTokenRate) external
+```
+
+approving requests from the `requestIds` array using the `newOutRate`.
+Validates that new mToken rate does not exceed variation tolerance
+Mints mToken to request users.
+Sets request flags to Processed.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestIds | uint256[] | request ids array |
+| avgMTokenRate | uint256 | avg mToken rate inputted by vault admin |
+
+### approveRequest
+
+```solidity
+function approveRequest(uint256 requestId, uint256 newOutRate, bool isAvgRate) external
+```
+
+approving request without price deviation check
+Mints mToken to user.
+Sets request flag to Processed.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestId | uint256 | request id |
+| newOutRate | uint256 | mToken rate inputted by vault admin |
+| isAvgRate | bool | if true, newOutRate is avg rate |
+
+### rejectRequest
+
+```solidity
+function rejectRequest(uint256 requestId) external
+```
+
+rejecting request
+Sets request flag to Canceled.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestId | uint256 | request id |
+
+### setMinMTokenAmountForFirstDeposit
+
+```solidity
+function setMinMTokenAmountForFirstDeposit(uint256 newValue) external
+```
+
+sets new minimal amount to deposit in EUR.
+can be called only from vault`s admin
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newValue | uint256 | new min. deposit value |
+
+### setMaxSupplyCap
+
+```solidity
+function setMaxSupplyCap(uint256 newValue) external
+```
+
+sets new max supply cap value
+can be called only from vault`s admin
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newValue | uint256 | new max supply cap value |
+
+### setMaxAmountPerRequest
+
+```solidity
+function setMaxAmountPerRequest(uint256 newValue) external
+```
+
+sets new max amount per request
+can be called only from vault`s admin
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newValue | uint256 | new max amount per request |
+
+## IMToken
+
+### ClawbackReceiverSet
+
+```solidity
+event ClawbackReceiverSet(address clawbackReceiver)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| clawbackReceiver | address | address to which clawback tokens will be sent |
+
+### SetNameSymbol
+
+```solidity
+event SetNameSymbol(string name, string symbol)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| name | string | new name |
+| symbol | string | new symbol |
+
+### SetIsPermissioned
+
+```solidity
+event SetIsPermissioned(bool isPermissioned)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| isPermissioned | bool | if true then the token is permissioned |
+
+### SetIsMinHoldingBalanceEnforced
+
+```solidity
+event SetIsMinHoldingBalanceEnforced(bool isMinHoldingBalanceEnforced)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| isMinHoldingBalanceEnforced | bool | if true then the token has a minimum holding balance enforced |
+
+### SetMetadata
+
+```solidity
+event SetMetadata(bytes32 key, bytes data)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| key | bytes32 | metadata key |
+| data | bytes | metadata data |
+
+### Clawback
+
+```solidity
+event Clawback(address from, address to, uint256 amount)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| from | address | address to clawback tokens from |
+| to | address | address to clawback tokens to |
+| amount | uint256 | amount to clawback |
+
+### InvalidNewLimit
+
+```solidity
+error InvalidNewLimit(uint256 newLimit, uint256 existingLimit)
+```
+
+when new limit is invalid
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newLimit | uint256 | new limit |
+| existingLimit | uint256 | existing limit |
+
+### MinBalanceNotMet
+
+```solidity
+error MinBalanceNotMet(uint256 balance)
+```
+
+when the balance is not met
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| balance | uint256 | balance |
+
+### mint
+
+```solidity
+function mint(address to, uint256 amount) external
+```
+
+mints mToken token `amount` to a given `to` address.
+should be called only from permissioned actor
+bypasses the timelock entirely
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| to | address | addres to mint tokens to |
+| amount | uint256 | amount to mint |
+
+### burn
+
+```solidity
+function burn(address from, uint256 amount) external
+```
+
+burns mToken token `amount` from a given `from` address.
+should be called only from permissioned actor
+bypasses the timelock entirely
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| from | address | addres to burn tokens from |
+| amount | uint256 | amount to burn |
+
+### mintGoverned
+
+```solidity
+function mintGoverned(address to, uint256 amount) external
+```
+
+mints mToken token `amount` to a given `to` address,
+requires the timelock to pass
+should be called only from permissioned actor
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| to | address | address to mint tokens to |
+| amount | uint256 | amount to mint |
+
+### burnGoverned
+
+```solidity
+function burnGoverned(address from, uint256 amount) external
+```
+
+burns mToken token `amount` from a given `from` address,
+bypassing blacklist checks.
+requires the timelock to pass
+should be called only from permissioned actor
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| from | address | address to burn tokens from |
+| amount | uint256 | amount to burn |
+
+### clawback
+
+```solidity
+function clawback(uint256 amount, address from) external
+```
+
+claws back tokens from a given address
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| amount | uint256 | amount to clawback |
+| from | address | address to clawback tokens from |
+
+### setClawbackReceiver
+
+```solidity
+function setClawbackReceiver(address clawbackReceiver) external
+```
+
+sets the address to which clawback tokens will be sent
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| clawbackReceiver | address | address to which clawback tokens will be sent |
+
+### setNameSymbol
+
+```solidity
+function setNameSymbol(string name_, string symbol_) external
+```
+
+sets the name and symbol of the token
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| name_ | string | new name |
+| symbol_ | string | new symbol |
+
+### setMetadata
+
+```solidity
+function setMetadata(bytes32 key, bytes data) external
+```
+
+updates contract`s metadata.
+should be called only from permissioned actor
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| key | bytes32 | metadata map. key |
+| data | bytes | metadata map. value |
+
+### increaseMintRateLimit
+
+```solidity
+function increaseMintRateLimit(uint256 window, uint256 newLimit) external
+```
+
+increases mint rate limit for a given window
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| window | uint256 | window duration in seconds |
+| newLimit | uint256 | limit amount per window |
+
+### decreaseMintRateLimit
+
+```solidity
+function decreaseMintRateLimit(uint256 window, uint256 newLimit) external
+```
+
+decreases mint rate limit for a given window
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| window | uint256 | window duration in seconds |
+| newLimit | uint256 | limit amount per window |
+
+### removeMintRateLimitConfig
+
+```solidity
+function removeMintRateLimitConfig(uint256 window) external
+```
+
+removes mint rate limit config for a given window
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| window | uint256 | window duration in seconds |
+
+### setIsPermissioned
+
+```solidity
+function setIsPermissioned(bool isPermissioned) external
+```
+
+sets the permissioned status of the token
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| isPermissioned | bool | if true then the token is permissioned |
+
+### setMinHoldingBalanceEnforced
+
+```solidity
+function setMinHoldingBalanceEnforced(bool isMinHoldingBalanceEnforced) external
+```
+
+sets the min holding balance enforced status of the token
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| isMinHoldingBalanceEnforced | bool | if true then the token has a minimum holding balance enforced |
+
+### minBalanceExemptRole
+
+```solidity
+function minBalanceExemptRole() external view returns (bytes32)
+```
+
+role that grants min balance exempt rights to the contract
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bytes32 | role bytes32 role |
+
+### greenlistedRole
+
+```solidity
+function greenlistedRole() external view returns (bytes32)
+```
+
+sets the role that grants greenlisted rights to the contract
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bytes32 | role bytes32 role |
+
+## TokenConfig
+
+Payment token config
+
+```solidity
+struct TokenConfig {
+ address dataFeed;
+ uint256 fee;
+ uint256 allowance;
+ bool stable;
+}
+```
+
+## RequestStatus
+
+```solidity
+enum RequestStatus {
+ Pending,
+ Processed,
+ Canceled
+}
+```
+
+## CommonVaultInitParams
+
+Common vault init params
+
+```solidity
+struct CommonVaultInitParams {
+ uint256 variationTolerance;
+ uint256 minAmount;
+ uint256 instantFee;
+ address ac;
+ address sanctionsList;
+ address mToken;
+ address mTokenDataFeed;
+ address tokensReceiver;
+ uint256 minInstantFee;
+ uint256 maxInstantFee;
+ uint256 maxInstantShare;
+ uint256 maxApproveRequestId;
+ bool sequentialRequestProcessing;
+}
+```
+
+## IManageableVault
+
+### WithdrawToken
+
+```solidity
+event WithdrawToken(address token, address withdrawTo, uint256 amount)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | token that was withdrawn |
+| withdrawTo | address | address to which tokens were withdrawn |
+| amount | uint256 | `token` transfer amount |
+
+### AddPaymentToken
+
+```solidity
+event AddPaymentToken(address token, address dataFeed, uint256 fee, uint256 allowance, bool stable)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | address of token that |
+| dataFeed | address | token dataFeed address |
+| fee | uint256 | fee 1% = 100 |
+| allowance | uint256 | token allowance (decimals 18) |
+| stable | bool | stablecoin flag |
+
+### ChangeTokenAllowance
+
+```solidity
+event ChangeTokenAllowance(address token, uint256 allowance)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | address of token that |
+| allowance | uint256 | new allowance |
+
+### ChangeTokenFee
+
+```solidity
+event ChangeTokenFee(address token, uint256 fee)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | address of token that |
+| fee | uint256 | new fee |
+
+### RemovePaymentToken
+
+```solidity
+event RemovePaymentToken(address token)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | address of token that |
+
+### SetWaivedFeeAccount
+
+```solidity
+event SetWaivedFeeAccount(address account, bool enable)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| account | address | address of account |
+| enable | bool | is enabled |
+
+### SetInstantFee
+
+```solidity
+event SetInstantFee(uint256 newFee)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newFee | uint256 | new operation fee value |
+
+### SetMinMaxInstantFee
+
+```solidity
+event SetMinMaxInstantFee(uint256 newMinInstantFee, uint256 newMaxInstantFee)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newMinInstantFee | uint256 | new minimum instant fee |
+| newMaxInstantFee | uint256 | new maximum instant fee |
+
+### SetMinAmount
+
+```solidity
+event SetMinAmount(uint256 newAmount)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newAmount | uint256 | new min amount for operation |
+
+### SetMaxInstantShare
+
+```solidity
+event SetMaxInstantShare(uint256 newMaxInstantShare)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newMaxInstantShare | uint256 | new maximum instant share value in basis points (100 = 1%) |
+
+### SetVariationTolerance
+
+```solidity
+event SetVariationTolerance(uint256 newTolerance)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newTolerance | uint256 | percent of price diviation 1% = 100 |
+
+### SetTokensReceiver
+
+```solidity
+event SetTokensReceiver(address receiver)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| receiver | address | new receiver address |
+
+### SetMaxApproveRequestId
+
+```solidity
+event SetMaxApproveRequestId(uint256 newMaxApproveRequestId)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newMaxApproveRequestId | uint256 | new max requestId that can be approved |
+
+### FreeFromMinAmount
+
+```solidity
+event FreeFromMinAmount(address user, bool enable)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| user | address | user address |
+| enable | bool | is enabled |
+
+### SetSequentialRequestProcessing
+
+```solidity
+event SetSequentialRequestProcessing(bool enforce)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| enforce | bool | enforce sequential request processing flag |
+
+### PaymentTokenAlreadyAdded
+
+```solidity
+error PaymentTokenAlreadyAdded(address token)
+```
+
+Payment token is already added
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | token address |
+
+### PaymentTokenNotExists
+
+```solidity
+error PaymentTokenNotExists(address token)
+```
+
+Payment token is not in the list
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | token address |
+
+### SameAddressValue
+
+```solidity
+error SameAddressValue(address account)
+```
+
+Value is the same as the current one
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| account | address | account address |
+
+### InvalidMinMaxInstantFee
+
+```solidity
+error InvalidMinMaxInstantFee(uint256 minFee, uint256 maxFee)
+```
+
+Min instant fee is greater than max instant fee
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| minFee | uint256 | minimum instant fee |
+| maxFee | uint256 | maximum instant fee |
+
+### InvalidRounding
+
+```solidity
+error InvalidRounding(uint256 amount, uint256 requiredAmount)
+```
+
+Amount does not match token decimals after rounding
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| amount | uint256 | input amount |
+| requiredAmount | uint256 | amount after round-trip conversion |
+
+### UnknownPaymentToken
+
+```solidity
+error UnknownPaymentToken(address token)
+```
+
+Payment token is not supported
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | token address |
+
+### AllowanceExceeded
+
+```solidity
+error AllowanceExceeded(uint256 prevAllowance, uint256 amount)
+```
+
+Operation amount exceeds token allowance
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| prevAllowance | uint256 | current allowance |
+| amount | uint256 | requested amount |
+
+### InstantFeeOutOfBounds
+
+```solidity
+error InstantFeeOutOfBounds(uint256 instantFee)
+```
+
+Instant fee is outside min/max range
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| instantFee | uint256 | current instant fee |
+
+### PriceVariationExceeded
+
+```solidity
+error PriceVariationExceeded(uint256 difPercent, uint256 variationTolerance)
+```
+
+Price change is too large
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| difPercent | uint256 | actual price change percent (1% = 100) |
+| variationTolerance | uint256 | max allowed change percent (1% = 100) |
+
+### InvalidFee
+
+```solidity
+error InvalidFee(uint256 fee)
+```
+
+Fee is out of allowed range
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| fee | uint256 | fee value (1% = 100) |
+
+### InvalidTokenRate
+
+```solidity
+error InvalidTokenRate(uint256 tokenRate)
+```
+
+Token rate is zero or invalid
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| tokenRate | uint256 | token rate value |
+
+### SlippageExceeded
+
+```solidity
+error SlippageExceeded(uint256 minReceiveAmount, uint256 actualReceiveAmount)
+```
+
+Received amount is below minimum
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| minReceiveAmount | uint256 | minimum expected amount |
+| actualReceiveAmount | uint256 | actual received amount |
+
+### RequestIdTooHigh
+
+```solidity
+error RequestIdTooHigh(uint256 requestId, uint256 maxApproveRequestId)
+```
+
+Request id is above max approve id
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestId | uint256 | request id |
+| maxApproveRequestId | uint256 | max request id that can be approved |
+
+### InvalidNewMTokenRate
+
+```solidity
+error InvalidNewMTokenRate()
+```
+
+New mToken rate must be greater than zero
+
+### RequestNotExists
+
+```solidity
+error RequestNotExists(uint256 requestId)
+```
+
+Request does not exist
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestId | uint256 | request id |
+
+### UnexpectedRequestStatus
+
+```solidity
+error UnexpectedRequestStatus(uint256 requestId, enum RequestStatus status)
+```
+
+Request has wrong status
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestId | uint256 | request id |
+| status | enum RequestStatus | current request status |
+
+### InstantShareTooHigh
+
+```solidity
+error InstantShareTooHigh(uint256 instantShare, uint256 maxInstantShare)
+```
+
+Instant share is above max allowed
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| instantShare | uint256 | instant share in basis points (100 = 1%) |
+| maxInstantShare | uint256 | max allowed instant share |
+
+### InvalidAmount
+
+```solidity
+error InvalidAmount()
+```
+
+Amount must be greater than zero
+
+### AmountLessThanMin
+
+```solidity
+error AmountLessThanMin(uint256 amount, uint256 minAmount)
+```
+
+Amount is below minimum
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| amount | uint256 | requested amount |
+| minAmount | uint256 | minimum allowed amount |
+
+### InvalidRequestSequence
+
+```solidity
+error InvalidRequestSequence(uint256 requestId, uint256 nextExpectedRequestIdToProcess)
+```
+
+Request id is not the next expected one
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestId | uint256 | request id |
+| nextExpectedRequestIdToProcess | uint256 | next request id to process |
+
+### mTokenDataFeed
+
+```solidity
+function mTokenDataFeed() external view returns (contract IDataFeed)
+```
+
+The mTokenDataFeed contract address.
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | contract IDataFeed | The address of the mTokenDataFeed contract. |
+
+### mToken
+
+```solidity
+function mToken() external view returns (contract IMToken)
+```
+
+The mToken contract address.
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | contract IMToken | The address of the mToken contract. |
+
+### addPaymentToken
+
+```solidity
+function addPaymentToken(address token, address dataFeed, uint256 fee, uint256 allowance, bool stable) external
+```
+
+adds a token to the stablecoins list.
+can be called only from permissioned actor.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | token address |
+| dataFeed | address | dataFeed address |
+| fee | uint256 | 1% = 100 |
+| allowance | uint256 | token allowance (decimals 18) |
+| stable | bool | is stablecoin flag |
+
+### removePaymentToken
+
+```solidity
+function removePaymentToken(address token) external
+```
+
+removes a token from stablecoins list.
+can be called only from permissioned actor.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | token address |
+
+### changeTokenAllowance
+
+```solidity
+function changeTokenAllowance(address token, uint256 allowance) external
+```
+
+set new token allowance.
+if type(uint256).max = infinite allowance
+prev allowance rewrites by new
+can be called only from permissioned actor.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | token address |
+| allowance | uint256 | new allowance (decimals 18) |
+
+### changeTokenFee
+
+```solidity
+function changeTokenFee(address token, uint256 fee) external
+```
+
+set new token fee.
+can be called only from permissioned actor.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | token address |
+| fee | uint256 | new fee percent 1% = 100 |
+
+### setVariationTolerance
+
+```solidity
+function setVariationTolerance(uint256 tolerance) external
+```
+
+set new prices diviation percent.
+can be called only from permissioned actor.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| tolerance | uint256 | new prices diviation percent 1% = 100 |
+
+### setMinAmount
+
+```solidity
+function setMinAmount(uint256 newAmount) external
+```
+
+set new min amount.
+can be called only from permissioned actor.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newAmount | uint256 | min amount for operations in mToken |
+
+### setWaivedFeeAccount
+
+```solidity
+function setWaivedFeeAccount(address account, bool enable) external
+```
+
+sets a account to waived fee restriction.
+can be called only from permissioned actor.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| account | address | user address |
+| enable | bool | is enabled |
+
+### setTokensReceiver
+
+```solidity
+function setTokensReceiver(address receiver) external
+```
+
+set new receiver for tokens.
+can be called only from permissioned actor.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| receiver | address | new token receiver address |
+
+### setInstantFee
+
+```solidity
+function setInstantFee(uint256 newInstantFee) external
+```
+
+set operation fee percent.
+can be called only from permissioned actor.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newInstantFee | uint256 | new instant operations fee percent 1& = 100 |
+
+### setMinMaxInstantFee
+
+```solidity
+function setMinMaxInstantFee(uint256 newMinInstantFee, uint256 newMaxInstantFee) external
+```
+
+set new minimum/maximum instant fee
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newMinInstantFee | uint256 | new minimum instant fee |
+| newMaxInstantFee | uint256 | new maximum instant fee |
+
+### setInstantLimitConfig
+
+```solidity
+function setInstantLimitConfig(uint256 window, uint256 limit) external
+```
+
+set operation limit configs.
+can be called only from permissioned actor.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| window | uint256 | window duration in seconds |
+| limit | uint256 | limit amount per window |
+
+### setMaxInstantShare
+
+```solidity
+function setMaxInstantShare(uint256 newMaxInstantShare) external
+```
+
+set maximum instant share value in basis points (100 = 1%)
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newMaxInstantShare | uint256 | new maximum instant share value in basis points (100 = 1%) |
+
+### setMaxApproveRequestId
+
+```solidity
+function setMaxApproveRequestId(uint256 newMaxApproveRequestId) external
+```
+
+sets max requestId that can be approved
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newMaxApproveRequestId | uint256 | new max requestId that can be approved |
+
+### removeInstantLimitConfig
+
+```solidity
+function removeInstantLimitConfig(uint256 window) external
+```
+
+remove operation limit config.
+can be called only from permissioned actor.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| window | uint256 | window duration in seconds |
+
+### freeFromMinAmount
+
+```solidity
+function freeFromMinAmount(address user, bool enable) external
+```
+
+frees given `user` from the minimal deposit
+amount validation in `initiateDepositRequest`
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| user | address | address of user |
+| enable | bool | |
+
+### setSequentialRequestProcessing
+
+```solidity
+function setSequentialRequestProcessing(bool enforce) external
+```
+
+set enforce sequential request processing flag
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| enforce | bool | enforce sequential request processing flag |
+
+### withdrawToken
+
+```solidity
+function withdrawToken(address token, uint256 amount) external
+```
+
+withdraws `amount` of a given `token` from the contract
+to the `tokensReceiver` address
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| token | address | token address |
+| amount | uint256 | token amount |
+
+### waivedFeeRestriction
+
+```solidity
+function waivedFeeRestriction(address account) external view returns (bool)
+```
+
+check if the account is waived from fee restriction
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| account | address | account address |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bool | true if the account is waived from fee restriction, false otherwise |
+
+## IMidasPauseManager
+
+Interface for the MidasPauseManager
+
+### FnPauseStatusChange
+
+```solidity
+event FnPauseStatusChange(address contractAddr, bytes4 fn, bool isPaused)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| contractAddr | address | contract address |
+| fn | bytes4 | function id |
+| isPaused | bool | paused status |
+
+### ContractPauseStatusChange
+
+```solidity
+event ContractPauseStatusChange(address contractAddr, bool isPaused)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| contractAddr | address | contract address |
+| isPaused | bool | paused status |
+
+### GlobalPauseStatusChange
+
+```solidity
+event GlobalPauseStatusChange(bool isPaused)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| isPaused | bool | paused status |
+
+### SetPauseDelay
+
+```solidity
+event SetPauseDelay(uint32 pauseDelay)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| pauseDelay | uint32 | pause delay |
+
+### SetUnpauseDelay
+
+```solidity
+event SetUnpauseDelay(uint32 unpauseDelay)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| unpauseDelay | uint32 | unpause delay |
+
+### setPauseDelay
+
+```solidity
+function setPauseDelay(uint32 _pauseDelay) external
+```
+
+sets the pause delay
+
+_can be called only by the pause manager admin or function admin_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _pauseDelay | uint32 | pause delay |
+
+### setUnpauseDelay
+
+```solidity
+function setUnpauseDelay(uint32 _unpauseDelay) external
+```
+
+sets the unpause delay
+
+_can be called only by the pause manager admin or function admin_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _unpauseDelay | uint32 | unpause delay |
+
+### globalPause
+
+```solidity
+function globalPause() external
+```
+
+pauses the protocol
+
+_can be called only by the pause manager admin_
+
+### globalUnpause
+
+```solidity
+function globalUnpause() external
+```
+
+unpauses the protocol
+
+_can be called only by the pause manager admin_
+
+### bulkPauseContract
+
+```solidity
+function bulkPauseContract(address[] contractAddrs) external
+```
+
+pauses an array of contracts
+
+_can be called only by the pause manager admin or function admin_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| contractAddrs | address[] | array of contract addresses |
+
+### bulkUnpauseContract
+
+```solidity
+function bulkUnpauseContract(address[] contractAddrs) external
+```
+
+unpauses an array of contracts
+
+_can be called only by the pause manager admin or function admin_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| contractAddrs | address[] | array of contract addresses |
+
+### bulkPauseContractFn
+
+```solidity
+function bulkPauseContractFn(address[] contractAddrs, bytes4[] selectors) external
+```
+
+pauses functions on an array of contracts
+
+_can be called only by the pause manager admin or function admin_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| contractAddrs | address[] | array of contract addresses |
+| selectors | bytes4[] | function ids to pause on the contracts |
+
+### bulkUnpauseContractFn
+
+```solidity
+function bulkUnpauseContractFn(address[] contractAddrs, bytes4[] selectors) external
+```
+
+unpauses functions on an array of contracts
+
+_can be called only by the pause manager admin or function admin_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| contractAddrs | address[] | array of contract addresses |
+| selectors | bytes4[] | function ids to unpause on the contracts |
+
+### contractAdminPause
+
+```solidity
+function contractAdminPause(address contractAddr) external
+```
+
+pauses a contract
+
+_can be called only by admin of a contract or function admin that
+is managed by the admin of the contract_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| contractAddr | address | address of the contract |
+
+### contractAdminUnpause
+
+```solidity
+function contractAdminUnpause(address contractAddr) external
+```
+
+unpauses a contract
+
+_can be called only by admin of a contract or function admin that
+is managed by the admin of the contract_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| contractAddr | address | address of the contract |
+
+### isFunctionPaused
+
+```solidity
+function isFunctionPaused(address contractAddr, bytes4 selector) external view returns (bool)
+```
+
+checks if function of a contract is paused
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| contractAddr | address | contract address |
+| selector | bytes4 | function id |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bool | paused true if the function is paused |
+
+### isPaused
+
+```solidity
+function isPaused(address contractAddr, bytes4 selector) external view returns (bool)
+```
+
+checks if function or contract or protocol is paused
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| contractAddr | address | contract address |
+| selector | bytes4 | |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bool | paused true if paused |
+
+### pauseAdminRole
+
+```solidity
+function pauseAdminRole() external view returns (bytes32)
+```
+
+returns the admin role for the pause manager
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bytes32 | role admin role |
+
+### pauseDelay
+
+```solidity
+function pauseDelay() external view returns (uint32)
+```
+
+returns the pause delay
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint32 | pause delay |
+
+### unpauseDelay
+
+```solidity
+function unpauseDelay() external view returns (uint32)
+```
+
+returns the unpause delay
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint32 | unpause delay |
+
+## Request
+
+Redeem request scruct
+
+```solidity
+struct Request {
+ address recipient;
+ address tokenOut;
+ enum RequestStatus status;
+ uint256 feePercent;
+ uint256 amountMToken;
+ uint256 mTokenRate;
+ uint256 tokenOutRate;
+ uint256 amountMTokenInstant;
+ uint256 approvedMTokenRate;
+ uint256 amountTokenOut;
+}
+```
+
+## RedemptionVaultInitParams
+
+Redemption vault init params
+
+```solidity
+struct RedemptionVaultInitParams {
+ address requestRedeemer;
+ address loanLp;
+ address loanRepaymentAddress;
+ address loanSwapperVault;
+ uint256 loanApr;
+}
+```
+
+## LiquidityProviderLoanRequest
+
+Liquidity provider loan request struct
+
+```solidity
+struct LiquidityProviderLoanRequest {
+ address tokenOut;
+ uint256 amountTokenOut;
+ uint256 amountFee;
+ uint256 createdAt;
+ enum RequestStatus status;
+}
+```
+
+## IRedemptionVault
+
+### RedeemInstant
+
+```solidity
+event RedeemInstant(address user, address tokenOut, address recipient, uint256 amountMToken, uint256 feeAmount, uint256 amountTokenOut, uint256 mTokenRate, uint256 tokenOutRate)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| user | address | function caller (msg.sender) |
+| tokenOut | address | address of tokenOut |
+| recipient | address | recipient address |
+| amountMToken | uint256 | amount of mToken |
+| feeAmount | uint256 | fee amount in tokenOut |
+| amountTokenOut | uint256 | amount of tokenOut |
+| mTokenRate | uint256 | mToken rate |
+| tokenOutRate | uint256 | tokenOut rate |
+
+### RedeemRequest
+
+```solidity
+event RedeemRequest(uint256 requestId, address user, address tokenOut, address recipient, uint256 amountMToken, uint256 amountMTokenInstant, uint256 feePercent, uint256 mTokenRate, uint256 tokenOutRate)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestId | uint256 | request id |
+| user | address | function caller (msg.sender) |
+| tokenOut | address | address of tokenOut |
+| recipient | address | recipient address |
+| amountMToken | uint256 | amount of mToken |
+| amountMTokenInstant | uint256 | amount of mToken that was redeemed instantly |
+| feePercent | uint256 | fee percent |
+| mTokenRate | uint256 | mToken rate |
+| tokenOutRate | uint256 | tokenOut rate |
+
+### CreateLiquidityProviderLoanRequest
+
+```solidity
+event CreateLiquidityProviderLoanRequest(uint256 loanId, address tokenOut, uint256 amountTokenOut, uint256 amountFee, uint256 mTokenRate, uint256 tokenOutRate)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| loanId | uint256 | loan id |
+| tokenOut | address | tokenOut address |
+| amountTokenOut | uint256 | amount of tokenOut |
+| amountFee | uint256 | fee amount in payment token |
+| mTokenRate | uint256 | mToken rate |
+| tokenOutRate | uint256 | tokenOut rate |
+
+### ApproveRequest
+
+```solidity
+event ApproveRequest(uint256 requestId, uint256 newMTokenRate, bool isSafe, bool isAvgRate)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestId | uint256 | mint request id |
+| newMTokenRate | uint256 | new mToken rate |
+| isSafe | bool | if true, approval is safe |
+| isAvgRate | bool | if true, newMTokenRate is avg rate |
+
+### RejectRequest
+
+```solidity
+event RejectRequest(uint256 requestId)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestId | uint256 | mint request id |
+
+### SetRequestRedeemer
+
+```solidity
+event SetRequestRedeemer(address redeemer)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| redeemer | address | new address of request redeemer |
+
+### SetLoanLp
+
+```solidity
+event SetLoanLp(address newLoanLp)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newLoanLp | address | new address of loan liquidity provider |
+
+### SetLoanRepaymentAddress
+
+```solidity
+event SetLoanRepaymentAddress(address newLoanRepaymentAddress)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newLoanRepaymentAddress | address | new address of loan repayment address |
+
+### SetLoanSwapperVault
+
+```solidity
+event SetLoanSwapperVault(address newLoanSwapperVault)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newLoanSwapperVault | address | new address of loan swapper vault |
+
+### SetLoanApr
+
+```solidity
+event SetLoanApr(uint256 newLoanApr)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newLoanApr | uint256 | new loan APR value in basis points (100 = 1%) |
+
+### SetPreferLoanLiquidity
+
+```solidity
+event SetPreferLoanLiquidity(bool newLoanLpFirst)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newLoanLpFirst | bool | new flag to determine if the loan LP liquidity should be used first |
+
+### RepayLpLoanRequest
+
+```solidity
+event RepayLpLoanRequest(uint256 requestId, uint256 amountFee)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestId | uint256 | request id |
+| amountFee | uint256 | amount of fee in tokenOut |
+
+### CancelLpLoanRequest
+
+```solidity
+event CancelLpLoanRequest(uint256 requestId)
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestId | uint256 | request id |
+
+### FeeExceedsAmount
+
+```solidity
+error FeeExceedsAmount(uint256 fee, uint256 amount)
+```
+
+when fee exceeds amount
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| fee | uint256 | fee |
+| amount | uint256 | amount |
+
+### NotSelfCall
+
+```solidity
+error NotSelfCall()
+```
+
+when not self call
+
+### redeemInstant
+
+```solidity
+function redeemInstant(address tokenOut, uint256 amountMTokenIn, uint256 minReceiveAmount) external returns (uint256)
+```
+
+redeem mToken to tokenOut if daily limit and allowance not exceeded
+Burns mToken from the user.
+Transfers tokenOut to user.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| tokenOut | address | stable coin token address to redeem to |
+| amountMTokenIn | uint256 | amount of mToken to redeem (decimals 18) |
+| minReceiveAmount | uint256 | minimum expected amount of tokenOut to receive (decimals 18) |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | amountTokenOut amount of tokenOut that was received in original decimals |
+
+### redeemInstant
+
+```solidity
+function redeemInstant(address tokenOut, uint256 amountMTokenIn, uint256 minReceiveAmount, address recipient) external returns (uint256)
+```
+
+Does the same as original `redeemInstant` but allows specifying a custom tokensReceiver address.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| tokenOut | address | stable coin token address to redeem to |
+| amountMTokenIn | uint256 | amount of mToken to redeem (decimals 18) |
+| minReceiveAmount | uint256 | minimum expected amount of tokenOut to receive (decimals 18) |
+| recipient | address | address that receives tokens |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | amountTokenOut amount of tokenOut that was received in original decimals |
+
+### redeemRequest
+
+```solidity
+function redeemRequest(address tokenOut, uint256 amountMTokenIn) external returns (uint256)
+```
+
+creating redeem request
+Transfers amount in mToken to contract
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| tokenOut | address | stable coin token address to redeem to |
+| amountMTokenIn | uint256 | amount of mToken to redeem (decimals 18) |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | request id |
+
+### redeemRequest
+
+```solidity
+function redeemRequest(address tokenOut, uint256 amountMTokenIn, address recipientRequest, uint256 instantShare, uint256 minReceiveAmountInstantShare, address recipientInstant) external returns (uint256, uint256)
+```
+
+Instantly redeems `instantShare` amount of `amountMTokenIn` and creates a request for the remaining amount.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| tokenOut | address | stable coin token address to redeem to |
+| amountMTokenIn | uint256 | amount of mToken to redeem (decimals 18) |
+| recipientRequest | address | address that receives tokens for the request part |
+| instantShare | uint256 | % amount of `amountMTokenIn` that will be redeemed instantly |
+| minReceiveAmountInstantShare | uint256 | min receive amount for the instant share |
+| recipientInstant | address | address that receives tokens for the instant part |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | request id |
+| [1] | uint256 | instantReceivedAmount amount of tokenOut that was received instantly in original decimals |
+
+### safeBulkApproveRequestAtSavedRate
+
+```solidity
+function safeBulkApproveRequestAtSavedRate(uint256[] requestIds) external
+```
+
+approving requests from the `requestIds` array with the mToken rate
+from the request. WONT fail even if there is not enough liquidity
+to process all requests.
+Validates that new mToken rate does not exceed variation tolerance
+Transfers tokenOut to users
+Sets request flags to Processed.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestIds | uint256[] | request ids array |
+
+### safeBulkApproveRequest
+
+```solidity
+function safeBulkApproveRequest(uint256[] requestIds) external
+```
+
+approving requests from the `requestIds` array with the
+current mToken rate. WONT fail even if there is not enough liquidity
+to process all requests.
+Validates that new mToken rate does not exceed variation tolerance
+Transfers tokenOut to users
+Sets request flags to Processed.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestIds | uint256[] | request ids array |
+
+### safeBulkApproveRequestAvgRate
+
+```solidity
+function safeBulkApproveRequestAvgRate(uint256[] requestIds) external
+```
+
+approving requests from the `requestIds` array with the
+current mToken rate as avg rate. WONT fail even if there is not enough liquidity
+to process all requests.
+Validates that new mToken rate does not exceed variation tolerance
+Transfers tokenOut to users
+Sets request flags to Processed.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestIds | uint256[] | request ids array |
+
+### safeBulkApproveRequest
+
+```solidity
+function safeBulkApproveRequest(uint256[] requestIds, uint256 newMTokenRate) external
+```
+
+approving requests from the `requestIds` array using the `newMTokenRate`.
+WONT fail even if there is not enough liquidity to process all requests.
+Validates that new mToken rate does not exceed variation tolerance
+Transfers tokenOut to user
+Sets request flags to Processed.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestIds | uint256[] | request ids array |
+| newMTokenRate | uint256 | new mToken rate inputted by vault admin |
+
+### safeBulkApproveRequestAvgRate
+
+```solidity
+function safeBulkApproveRequestAvgRate(uint256[] requestIds, uint256 avgMTokenRate) external
+```
+
+approving requests from the `requestIds` array using the `avgMTokenRate`.
+WONT fail even if there is not enough liquidity to process all requests.
+Validates that new mToken rate does not exceed variation tolerance
+Transfers tokenOut to user
+Sets request flags to Processed.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestIds | uint256[] | request ids array |
+| avgMTokenRate | uint256 | avg mToken rate inputted by vault admin |
+
+### approveRequest
+
+```solidity
+function approveRequest(uint256 requestId, uint256 newMTokenRate, bool isAvgRate) external
+```
+
+approving redeem request if not exceed tokenOut allowance
+Burns amount mToken from contract
+Transfers tokenOut to user
+Sets flag Processed
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestId | uint256 | request id |
+| newMTokenRate | uint256 | new mToken rate inputted by vault admin |
+| isAvgRate | bool | if true, newMTokenRate is avg rate |
+
+### rejectRequest
+
+```solidity
+function rejectRequest(uint256 requestId) external
+```
+
+rejecting request
+Sets request flag to Canceled.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestId | uint256 | request id |
+
+### bulkRepayLpLoanRequest
+
+```solidity
+function bulkRepayLpLoanRequest(uint256[] requestIds) external
+```
+
+repaying loan requests from the `requestIds` array
+Transfers tokenOut to loan repayment address
+Sets request flags to Processed.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestIds | uint256[] | request ids array |
+
+### cancelLpLoanRequest
+
+```solidity
+function cancelLpLoanRequest(uint256 requestId) external
+```
+
+canceling loan request
+Sets request flags to Canceled.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestId | uint256 | request id |
+
+### setRequestRedeemer
+
+```solidity
+function setRequestRedeemer(address redeemer) external
+```
+
+set address which is designated for standard redemptions, allowing tokens to be pulled from this address
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| redeemer | address | new address of request redeemer |
+
+### setLoanLp
+
+```solidity
+function setLoanLp(address newLoanLp) external
+```
+
+set address of loan liquidity provider
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newLoanLp | address | new address of loan liquidity provider |
+
+### setLoanRepaymentAddress
+
+```solidity
+function setLoanRepaymentAddress(address newLoanRepaymentAddress) external
+```
+
+set address of loan repayment address
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newLoanRepaymentAddress | address | new address of loan repayment address |
+
+### setLoanSwapperVault
+
+```solidity
+function setLoanSwapperVault(address newLoanSwapperVault) external
+```
+
+set address of loan swapper vault
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newLoanSwapperVault | address | new address of loan swapper vault |
+
+### setLoanApr
+
+```solidity
+function setLoanApr(uint256 newLoanApr) external
+```
+
+set loan APR value in basis points (100 = 1%)
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newLoanApr | uint256 | new loan APR value in basis points (100 = 1%) |
+
+### setPreferLoanLiquidity
+
+```solidity
+function setPreferLoanLiquidity(bool newLoanLpFirst) external
+```
+
+set flag to determine if the loan LP liquidity should be used first
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newLoanLpFirst | bool | new flag to determine if the loan LP liquidity should be used first |
+
+## ISanctionsList
+
+Chainalysis sanctions oracle interface
+
+### isSanctioned
+
+```solidity
+function isSanctioned(address addr) external view returns (bool)
+```
+
+## IAaveV3Pool
+
+Minimal interface for the Aave V3 Pool (v3.2+)
+
+_Full interface: https://github.com/aave-dao/aave-v3-origin/blob/main/src/contracts/interfaces/IPool.sol_
+
+### withdraw
+
+```solidity
+function withdraw(address asset, uint256 amount, address to) external returns (uint256)
+```
+
+Withdraws an `amount` of underlying asset from the reserve, burning the equivalent aTokens owned
+E.g. User has 100 aUSDC, calls withdraw() and receives 100 USDC, burning the 100 aUSDC
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| asset | address | The address of the underlying asset to withdraw |
+| amount | uint256 | The underlying amount to be withdrawn - Send the value type(uint256).max in order to withdraw the whole aToken balance |
+| to | address | The address that will receive the underlying, same as msg.sender if the user wants to receive it on his own wallet, or a different address if the beneficiary is a different wallet |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | The final amount withdrawn |
+
+### supply
+
+```solidity
+function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external
+```
+
+Supplies an `amount` of underlying asset into the reserve, receiving in return overlying aTokens.
+- E.g. User supplies 100 USDC and gets in return 100 aUSDC
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| asset | address | The address of the underlying asset to supply |
+| amount | uint256 | The amount to be supplied |
+| onBehalfOf | address | The address that will receive the aTokens, same as msg.sender if the user wants to receive them on his own wallet, or a different address if the beneficiary of aTokens is a different wallet |
+| referralCode | uint16 | Code used to register the integrator originating the operation, for potential rewards. 0 if the action is executed directly by the user, without any middle-man |
+
+### getReserveAToken
+
+```solidity
+function getReserveAToken(address asset) external view returns (address)
+```
+
+Returns the aToken address of a reserve
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| asset | address | The address of the underlying asset of the reserve |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | address | The aToken address of the reserve |
+
+## IAcreAdapter
+
+Interface for the Vault contract.
+
+_This interface is used to interact with the Vault contract.
+ It is used to deposit and redeem shares.
+ It is used to get the price of the shares with convertToShares and convertToAssets.
+ It is used to request an asynchronous redemption of shares.
+ It assumes no fees are charged on deposits or redemptions._
+
+### Deposit
+
+```solidity
+event Deposit(address sender, address owner, uint256 assets, uint256 shares)
+```
+
+Emitted when assets are deposited into the vault.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| sender | address | The address that deposited the assets. |
+| owner | address | The address that received the shares. |
+| assets | uint256 | The amount of assets deposited. |
+| shares | uint256 | The amount of shares received. |
+
+### RedeemRequest
+
+```solidity
+event RedeemRequest(uint256 requestId, address sender, address receiver, uint256 shares)
+```
+
+Emitted when a redeem request is made.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| requestId | uint256 | The request ID. |
+| sender | address | The address that made the request. |
+| receiver | address | The address that will received the assets. |
+| shares | uint256 | The amount of shares that would be redeemed. |
+
+### share
+
+```solidity
+function share() external view returns (address shareTokenAddress)
+```
+
+_Returns the address of the share token. The address MAY be the same
+ as the vault address.
+
+- MUST be an ERC-20 token contract.
+- MUST NOT revert._
+
+### asset
+
+```solidity
+function asset() external view returns (address assetTokenAddress)
+```
+
+_Returns the address of the asset token.
+
+- MUST be an ERC-20 token contract.
+- MUST NOT revert._
+
+### convertToShares
+
+```solidity
+function convertToShares(uint256 assets) external view returns (uint256 shares)
+```
+
+_Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
+scenario where all the conditions are met.
+
+- MUST NOT be inclusive of any fees that are charged against assets in the Vault.
+- MUST NOT show any variations depending on the caller.
+- MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
+- MUST NOT revert.
+
+NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
+“average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
+from._
+
+### convertToAssets
+
+```solidity
+function convertToAssets(uint256 shares) external view returns (uint256 assets)
+```
+
+_Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
+scenario where all the conditions are met.
+
+- MUST NOT be inclusive of any fees that are charged against assets in the Vault.
+- MUST NOT show any variations depending on the caller.
+- MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
+- MUST NOT revert.
+
+NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
+“average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
+from._
+
+### deposit
+
+```solidity
+function deposit(uint256 assets, address receiver) external returns (uint256 shares)
+```
+
+_Mints shares Vault shares to owner by depositing exactly amount of underlying tokens.
+
+- MUST emit the Deposit event._
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| assets | uint256 | The amount of assets to be deposited. |
+| receiver | address | The address that will received the shares. NOTE: Implementation requires pre-approval of the Vault with the Vault’s underlying asset token. |
+
+### requestRedeem
+
+```solidity
+function requestRedeem(uint256 shares, address receiver) external returns (uint256 requestId)
+```
+
+_Assumes control of shares from sender into the Vault and submits a Request for asynchronous redeem.
+
+- MUST emit the RedeemRequest event.
+- Once a request is finalized MUST emit the RedeemFinalize event._
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| shares | uint256 | The amount of shares to be redeemed. |
+| receiver | address | The address that will receive assets on request finalization. NOTE: Implementations requires pre-approval of the Vault with the Vault's share token. |
+
+## IMorphoVault
+
+Morpho Vault interface extending the ERC-4626 Tokenized Vault Standard
+
+_Works with both Morpho Vaults V1 (MetaMorpho) and V2
+V1 repo: https://github.com/morpho-org/metamorpho-v1.1
+V2 repo: https://github.com/morpho-org/vault-v2_
+
+## ISuperstateToken
+
+### StablecoinConfig
+
+```solidity
+struct StablecoinConfig {
+ address sweepDestination;
+ uint96 fee;
+}
+```
+
+### subscribe
+
+```solidity
+function subscribe(address to, uint256 inAmount, address stablecoin) external
+```
+
+### setStablecoinConfig
+
+```solidity
+function setStablecoinConfig(address stablecoin, address newSweepDestination, uint96 newFee) external
+```
+
+### supportedStablecoins
+
+```solidity
+function supportedStablecoins(address stablecoin) external view returns (struct ISuperstateToken.StablecoinConfig)
+```
+
+### symbol
+
+```solidity
+function symbol() external view returns (string)
+```
+
+### owner
+
+```solidity
+function owner() external view returns (address)
+```
+
+### allowListV2
+
+```solidity
+function allowListV2() external view returns (address)
+```
+
+### isAllowed
+
+```solidity
+function isAllowed(address addr) external view returns (bool)
+```
+
+## IUSTBRedemption
+
+### SUPERSTATE_TOKEN
+
+```solidity
+function SUPERSTATE_TOKEN() external view returns (address)
+```
+
+### USDC
+
+```solidity
+function USDC() external view returns (address)
+```
+
+### owner
+
+```solidity
+function owner() external view returns (address)
+```
+
+### redeem
+
+```solidity
+function redeem(uint256 superstateTokenInAmount) external
+```
+
+### setRedemptionFee
+
+```solidity
+function setRedemptionFee(uint256 _newFee) external
+```
+
+### calculateFee
+
+```solidity
+function calculateFee(uint256 amount) external view returns (uint256)
+```
+
+### calculateUstbIn
+
+```solidity
+function calculateUstbIn(uint256 usdcOutAmount) external view returns (uint256 ustbInAmount, uint256 usdPerUstbChainlinkRaw)
+```
+
+## PauseGuardsLibrary
+
+library for checking pause statuses
+
+### Paused
+
+```solidity
+error Paused(address contractAddr, bytes4 fn)
+```
+
+error thrown when a function is paused
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| contractAddr | address | contract address |
+| fn | bytes4 | function id |
+
+### requireFnNotPaused
+
+```solidity
+function requireFnNotPaused(contract IMidasAccessControl accessControl, bytes4 fn) internal view
+```
+
+_checks that a given `fn` is not paused_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| accessControl | contract IMidasAccessControl | |
+| fn | bytes4 | function id |
+
+### requireNotPaused
+
+```solidity
+function requireNotPaused(contract IMidasAccessControl accessControl, bytes4 fn) internal view
+```
+
+_checks that a given `fn` and contract/global are not paused_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| accessControl | contract IMidasAccessControl | |
+| fn | bytes4 | function id |
+
+## RateLimitLibrary
+
+Multi-window linear-decay rate limiting (vault instant flows, mToken mint, etc.).
+
+### WindowLimitExceeded
+
+```solidity
+error WindowLimitExceeded(uint256 window, uint256 remaining, uint256 requested)
+```
+
+when window limit is exceeded
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| window | uint256 | window duration in seconds |
+| remaining | uint256 | actual remaining amount |
+| requested | uint256 | requested amount |
+
+### UnknownWindowLimit
+
+```solidity
+error UnknownWindowLimit(uint256 window)
+```
+
+when window limit is unknown
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| window | uint256 | window duration in seconds |
+
+### WindowTooShort
+
+```solidity
+error WindowTooShort(uint256 window)
+```
+
+when window is too short
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| window | uint256 | window duration in seconds |
+
+### WindowLimitSet
+
+```solidity
+event WindowLimitSet(uint256 window, uint256 limit)
+```
+
+Emitted when a window limit is set or updated.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| window | uint256 | window duration in seconds |
+| limit | uint256 | max amount per window |
+
+### WindowLimitRemoved
+
+```solidity
+event WindowLimitRemoved(uint256 window)
+```
+
+Emitted when a window limit is removed.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| window | uint256 | window duration in seconds |
+
+### WindowRateLimitConfig
+
+Per-window rate limit (linear decay over `window` seconds).
+
+```solidity
+struct WindowRateLimitConfig {
+ uint256 limit;
+ uint256 amountInFlight;
+ uint256 lastUpdated;
+ uint256 window;
+}
+```
+
+### WindowRateLimits
+
+Active windows and their configs (keyed by window duration).
+
+```solidity
+struct WindowRateLimits {
+ struct EnumerableSetUpgradeable.UintSet windows;
+ mapping(uint256 => struct RateLimitLibrary.WindowRateLimitConfig) configs;
+}
+```
+
+### WindowRateLimitStatus
+
+Snapshot for one window (view helper).
+
+```solidity
+struct WindowRateLimitStatus {
+ uint256 inFlight;
+ uint256 remaining;
+ uint256 lastUpdated;
+ uint256 window;
+ uint256 limit;
+}
+```
+
+### getWindowStatuses
+
+```solidity
+function getWindowStatuses(struct RateLimitLibrary.WindowRateLimits limits) internal view returns (struct RateLimitLibrary.WindowRateLimitStatus[] statuses)
+```
+
+Returns a status row per configured window (enumeration order).
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| limits | struct RateLimitLibrary.WindowRateLimits | aggregated window state |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| statuses | struct RateLimitLibrary.WindowRateLimitStatus[] | one entry per active window |
+
+### setWindowLimit
+
+```solidity
+function setWindowLimit(struct RateLimitLibrary.WindowRateLimits limits, uint256 window, uint256 limit) internal returns (uint256 previousLimit)
+```
+
+Sets or updates the limit for a window (checkpoints first).
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| limits | struct RateLimitLibrary.WindowRateLimits | aggregated window state |
+| window | uint256 | window duration in seconds |
+| limit | uint256 | max amount per window |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| previousLimit | uint256 | previous limit |
+
+### removeWindowLimit
+
+```solidity
+function removeWindowLimit(struct RateLimitLibrary.WindowRateLimits limits, uint256 window) internal
+```
+
+Removes a window and clears its config.
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| limits | struct RateLimitLibrary.WindowRateLimits | aggregated window state |
+| window | uint256 | window duration in seconds |
+
+### consumeLimit
+
+```solidity
+function consumeLimit(struct RateLimitLibrary.WindowRateLimits limits, uint256 amount) internal
+```
+
+Charges `amount` against every window (reverts if any lacks headroom).
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| limits | struct RateLimitLibrary.WindowRateLimits | aggregated window state |
+| amount | uint256 | amount to charge |
+
+## RedemptionSwapperHelpersLibrary
+
+### getSwapperDetails
+
+```solidity
+function getSwapperDetails(contract IRedemptionVault _redemptionVault, address _getBalanceOf) internal view returns (uint256 mTokenARate, contract IERC20Upgradeable mTokenA, uint256 mTokenABalance)
+```
+
+### redeemInstantSwapper
+
+```solidity
+function redeemInstantSwapper(contract IRedemptionVault _swapperVault, contract IERC20Upgradeable _mTokenA, address _liquiditySource, address _tokenOut, uint256 _mTokenAAmount, uint256 _tokenOutDecimals) internal returns (uint256)
+```
+
+## mToken
+
+### metadata
+
+```solidity
+mapping(bytes32 => bytes) metadata
+```
+
+metadata key => metadata value
+
+### clawbackReceiver
+
+```solidity
+address clawbackReceiver
+```
+
+address to which clawback tokens will be sent
+
+### isPermissioned
+
+```solidity
+bool isPermissioned
+```
+
+if true then the token is permissioned
+
+### isMinHoldingBalanceEnforced
+
+```solidity
+bool isMinHoldingBalanceEnforced
+```
+
+if true then the token has a minimum holding balance enforced
+
+### constructor
+
+```solidity
+constructor(bytes32 _contractAdminRole, bytes32 _minterRole, bytes32 _burnerRole, bytes32 _greenlistedRole, bytes32 _minBalanceExemptRole) public
+```
+
+constructor
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _contractAdminRole | bytes32 | contract admin role |
+| _minterRole | bytes32 | minter role |
+| _burnerRole | bytes32 | burner role |
+| _greenlistedRole | bytes32 | greenlisted role |
+| _minBalanceExemptRole | bytes32 | min balance exempt role |
+
+### initialize
+
+```solidity
+function initialize(address _accessControl, address _clawbackReceiver, bool _isPermissioned, bool _isMinHoldingBalanceEnforced, string name_, string symbol_) external
+```
+
+upgradeable pattern contract`s initializer
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _accessControl | address | address of MidasAccessControll contract |
+| _clawbackReceiver | address | address to which clawback tokens will be sent |
+| _isPermissioned | bool | |
+| _isMinHoldingBalanceEnforced | bool | |
+| name_ | string | name of the token |
+| symbol_ | string | symbol of the token |
+
+### initializeV3
+
+```solidity
+function initializeV3(address _clawbackReceiver, bool _isPermissioned, bool _isMinHoldingBalanceEnforced) public
+```
+
+v3 initializer
+
+_not v2 because some of the original product mTokens were upgraded to v2 already_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _clawbackReceiver | address | address to which clawback tokens will be sent |
+| _isPermissioned | bool | if true then the token is permissioned |
+| _isMinHoldingBalanceEnforced | bool | if true then the token has a minimum holding balance enforced |
+
+### setNameSymbol
+
+```solidity
+function setNameSymbol(string name_, string symbol_) external
+```
+
+sets the name and symbol of the token
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| name_ | string | new name |
+| symbol_ | string | new symbol |
+
+### setIsPermissioned
+
+```solidity
+function setIsPermissioned(bool _isPermissioned) external
+```
+
+sets the permissioned status of the token
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _isPermissioned | bool | |
+
+### setMinHoldingBalanceEnforced
+
+```solidity
+function setMinHoldingBalanceEnforced(bool _isMinHoldingBalanceEnforced) external
+```
+
+sets the min holding balance enforced status of the token
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _isMinHoldingBalanceEnforced | bool | |
+
+### setClawbackReceiver
+
+```solidity
+function setClawbackReceiver(address _clawbackReceiver) external
+```
+
+sets the address to which clawback tokens will be sent
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _clawbackReceiver | address | |
+
+### mint
+
+```solidity
+function mint(address to, uint256 amount) external
+```
+
+mints mToken token `amount` to a given `to` address.
+should be called only from permissioned actor
+bypasses the timelock entirely
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| to | address | addres to mint tokens to |
+| amount | uint256 | amount to mint |
+
+### mintGoverned
+
+```solidity
+function mintGoverned(address to, uint256 amount) external
+```
+
+mints mToken token `amount` to a given `to` address,
+requires the timelock to pass
+should be called only from permissioned actor
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| to | address | address to mint tokens to |
+| amount | uint256 | amount to mint |
+
+### burn
+
+```solidity
+function burn(address from, uint256 amount) external
+```
+
+burns mToken token `amount` from a given `from` address.
+should be called only from permissioned actor
+bypasses the timelock entirely
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| from | address | addres to burn tokens from |
+| amount | uint256 | amount to burn |
+
+### burnGoverned
+
+```solidity
+function burnGoverned(address from, uint256 amount) external
+```
+
+burns mToken token `amount` from a given `from` address,
+bypassing blacklist checks.
+requires the timelock to pass
+should be called only from permissioned actor
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| from | address | address to burn tokens from |
+| amount | uint256 | amount to burn |
+
+### clawback
+
+```solidity
+function clawback(uint256 amount, address from) external
+```
+
+claws back tokens from a given address
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| amount | uint256 | amount to clawback |
+| from | address | address to clawback tokens from |
+
+### setMetadata
+
+```solidity
+function setMetadata(bytes32 key, bytes data) external
+```
+
+updates contract`s metadata.
+should be called only from permissioned actor
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| key | bytes32 | metadata map. key |
+| data | bytes | metadata map. value |
+
+### increaseMintRateLimit
+
+```solidity
+function increaseMintRateLimit(uint256 window, uint256 newLimit) external
+```
+
+increases mint rate limit for a given window
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| window | uint256 | window duration in seconds |
+| newLimit | uint256 | limit amount per window |
+
+### decreaseMintRateLimit
+
+```solidity
+function decreaseMintRateLimit(uint256 window, uint256 newLimit) external
+```
+
+decreases mint rate limit for a given window
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| window | uint256 | window duration in seconds |
+| newLimit | uint256 | limit amount per window |
+
+### removeMintRateLimitConfig
+
+```solidity
+function removeMintRateLimitConfig(uint256 window) external
+```
+
+removes mint rate limit config for a given window
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| window | uint256 | window duration in seconds |
+
+### getMintRateLimitStatuses
+
+```solidity
+function getMintRateLimitStatuses() external view returns (struct RateLimitLibrary.WindowRateLimitStatus[])
+```
+
+returns array of mint rate limit configs
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | struct RateLimitLibrary.WindowRateLimitStatus[] | statuses array of mint rate limit statuses |
+
+### minterRole
+
+```solidity
+function minterRole() public view returns (bytes32)
+```
+
+AC role, owner of which can mint mToken token
+
+### burnerRole
+
+```solidity
+function burnerRole() public view returns (bytes32)
+```
+
+AC role, owner of which can burn mToken token
+
+### contractAdminRole
+
+```solidity
+function contractAdminRole() public view returns (bytes32)
+```
+
+_main admin role for the contract_
+
+### greenlistedRole
+
+```solidity
+function greenlistedRole() public view returns (bytes32)
+```
+
+sets the role that grants greenlisted rights to the contract
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bytes32 | role bytes32 role |
+
+### minBalanceExemptRole
+
+```solidity
+function minBalanceExemptRole() public view returns (bytes32)
+```
+
+role that grants min balance exempt rights to the contract
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bytes32 | role bytes32 role |
+
+### name
+
+```solidity
+function name() public view returns (string)
+```
+
+_Returns the name of the token._
+
+### symbol
+
+```solidity
+function symbol() public view returns (string)
+```
+
+_Returns the symbol of the token, usually a shorter version of the
+name._
+
+### _beforeTokenTransfer
+
+```solidity
+function _beforeTokenTransfer(address from, address to, uint256 amount) internal
+```
+
+_overrides _beforeTokenTransfer function to ban
+blaclisted users from using the token functions_
+
+### _afterTokenTransfer
+
+```solidity
+function _afterTokenTransfer(address from, address to, uint256 amount) internal
+```
+
+_overrides _afterTokenTransfer function to run custom validations_
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| from | address | address of the sender |
+| to | address | address of the recipient |
+| amount | uint256 | amount of tokens transferred |
+
+## AcreAdapter
+
+Wrapper for Midas Vaults to be used by Acre protocol
+
+### depositVault
+
+```solidity
+address depositVault
+```
+
+### redemptionVault
+
+```solidity
+address redemptionVault
+```
+
+### mTokenDataFeed
+
+```solidity
+address mTokenDataFeed
+```
+
+### assetTokenDecimals
+
+```solidity
+uint256 assetTokenDecimals
+```
+
+### constructor
+
+```solidity
+constructor(address depositVault_, address redemptionVault_, address assetToken_) public
+```
+
+constructor
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| depositVault_ | address | address of deposit vault contract (IDepositVault) |
+| redemptionVault_ | address | address of redemption vault contract (IRedemptionVault) |
+| assetToken_ | address | address of ERC20 asset token contract |
+
+### deposit
+
+```solidity
+function deposit(uint256 assets, address receiver) external returns (uint256 shares)
+```
+
+_Mints shares Vault shares to owner by depositing exactly amount of underlying tokens.
+
+- MUST emit the Deposit event._
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| assets | uint256 | The amount of assets to be deposited. |
+| receiver | address | The address that will received the shares. NOTE: Implementation requires pre-approval of the Vault with the Vault’s underlying asset token. |
+
+### requestRedeem
+
+```solidity
+function requestRedeem(uint256 shares, address receiver) external returns (uint256 requestId)
+```
+
+_Assumes control of shares from sender into the Vault and submits a Request for asynchronous redeem.
+
+- MUST emit the RedeemRequest event.
+- Once a request is finalized MUST emit the RedeemFinalize event._
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| shares | uint256 | The amount of shares to be redeemed. |
+| receiver | address | The address that will receive assets on request finalization. NOTE: Implementations requires pre-approval of the Vault with the Vault's share token. |
+
+### convertToShares
+
+```solidity
+function convertToShares(uint256 assets) external view returns (uint256)
+```
+
+_Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
+scenario where all the conditions are met.
+
+- MUST NOT be inclusive of any fees that are charged against assets in the Vault.
+- MUST NOT show any variations depending on the caller.
+- MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
+- MUST NOT revert.
+
+NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
+“average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
+from._
+
+### convertToAssets
+
+```solidity
+function convertToAssets(uint256 shares) external view returns (uint256)
+```
+
+_Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
+scenario where all the conditions are met.
+
+- MUST NOT be inclusive of any fees that are charged against assets in the Vault.
+- MUST NOT show any variations depending on the caller.
+- MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
+- MUST NOT revert.
+
+NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
+“average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
+from._
+
+### share
+
+```solidity
+function share() public view returns (address)
+```
+
+_Returns the address of the share token. The address MAY be the same
+ as the vault address.
+
+- MUST be an ERC-20 token contract.
+- MUST NOT revert._
+
+### asset
+
+```solidity
+function asset() public view returns (address)
+```
+
+_Returns the address of the asset token.
+
+- MUST be an ERC-20 token contract.
+- MUST NOT revert._
+
+## MidasAxelarVaultExecutable
+
+This contract is a InterchainTokenExecutable contract that allows deposits and redemptions operations against a
+ synchronous vault across different chains using Axelar's ITS protocol.
+
+_The contract is designed to handle deposits and redemptions of vault mTokens and paymentTokens,
+ ensuring that the mToken and paymentToken are correctly managed and transferred across chains.
+ It also includes slippage protection and refund mechanisms for failed transactions._
+
+### TokenAddressMismatch
+
+```solidity
+error TokenAddressMismatch(address itsTokenValue, address dvValue, address rvValue)
+```
+
+error for token address mismatch
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| itsTokenValue | address | address of ITS token |
+| dvValue | address | address of mToken of deposit vault |
+| rvValue | address | address of mToken of redemption vault |
+
+### depositVault
+
+```solidity
+contract IDepositVault depositVault
+```
+
+getter for the deposit vault
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+
+### redemptionVault
+
+```solidity
+contract IRedemptionVault redemptionVault
+```
+
+getter for the redemption vault
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+
+### paymentTokenId
+
+```solidity
+bytes32 paymentTokenId
+```
+
+getter for the paymentToken ITS id
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+
+### paymentTokenErc20
+
+```solidity
+address paymentTokenErc20
+```
+
+getter for the paymentToken ERC20
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+
+### mTokenId
+
+```solidity
+bytes32 mTokenId
+```
+
+getter for the mToken ITS id
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+
+### mTokenErc20
+
+```solidity
+address mTokenErc20
+```
+
+getter for the mToken ERC20
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+
+### paymentTokenDecimals
+
+```solidity
+uint8 paymentTokenDecimals
+```
+
+decimals of `paymentTokenErc20`
+
+### chainNameHash
+
+```solidity
+bytes32 chainNameHash
+```
+
+hash of the current chain name
+
+### constructor
+
+```solidity
+constructor(address _depositVault, address _redemptionVault, bytes32 _paymentTokenId, bytes32 _mTokenId, address _interchainTokenService) public
+```
+
+### initialize
+
+```solidity
+function initialize() external
+```
+
+Initializes the contract
+
+### _executeWithInterchainToken
+
+```solidity
+function _executeWithInterchainToken(bytes32 commandId, string sourceChain, bytes sourceAddress, bytes data, bytes32 tokenId, address, uint256 amount) internal
+```
+
+internal function to execute the interchain token transfer
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| commandId | bytes32 | the commandId of the operation |
+| sourceChain | string | the source chain of the operation |
+| sourceAddress | bytes | the source address of the operation |
+| data | bytes | the data of the operation |
+| tokenId | bytes32 | the ITS tokenId of the operation |
+| | address | |
+| amount | uint256 | the amount of the operation |
+
+### handleExecuteWithInterchainToken
+
+```solidity
+function handleExecuteWithInterchainToken(bytes _sourceAddress, bytes _data, bytes32 _tokenId, uint256 _amount) external
+```
+
+internal function to execute the interchain token transfer
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _sourceAddress | bytes | the source address of the operation |
+| _data | bytes | the data of the operation |
+| _tokenId | bytes32 | the ITS tokenId of the operation |
+| _amount | uint256 | the amount of the operation |
+
+### depositAndSend
+
+```solidity
+function depositAndSend(uint256 _paymentTokenAmount, bytes _data) external payable
+```
+
+deposits and sends the paymentToken to the destination chain
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _paymentTokenAmount | uint256 | the amount of payment tokens to deposit |
+| _data | bytes | encoded data for the deposit. Expected data: abi.encode(bytes receiver,uint256 minReceiveAmount,bytes32 referrerId,string receiverChainName); |
+
+### redeemAndSend
+
+```solidity
+function redeemAndSend(uint256 _mTokenAmount, bytes _data) external payable
+```
+
+redeems and sends the mToken to the destination chain
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _mTokenAmount | uint256 | the amount of m tokens to redeem |
+| _data | bytes | encoded data for the redemption Expected data: abi.encode(bytes receiver,uint256 minReceiveAmount,string receiverChainName); |
+
+### _depositAndSend
+
+```solidity
+function _depositAndSend(bytes _depositor, uint256 _paymentTokenAmount, bytes _data) internal
+```
+
+internal function to deposit and send the paymentToken to the destination chain
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _depositor | bytes | the depositor of the operation |
+| _paymentTokenAmount | uint256 | the amount of payment tokens to deposit |
+| _data | bytes | the data of the operation |
+
+### _redeemAndSend
+
+```solidity
+function _redeemAndSend(bytes _redeemer, uint256 _mTokenAmount, bytes _data) internal
+```
+
+internal function to redeem and send the mToken to the destination chain
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _redeemer | bytes | the address of the redeemer |
+| _mTokenAmount | uint256 | the amount of mTokens to redeem |
+| _data | bytes | the data of the operation |
+
+### _deposit
+
+```solidity
+function _deposit(address _receiver, uint256 _paymentTokenAmount, uint256 _minReceiveAmount, bytes32 _referrerId) internal returns (uint256 mTokenAmount)
+```
+
+function to deposit into Midas vault
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _receiver | address | the address to receive the mTokens |
+| _paymentTokenAmount | uint256 | the amount of paymentToken to deposit |
+| _minReceiveAmount | uint256 | the minimum amount of mTokens to receive |
+| _referrerId | bytes32 | the referrer id for the user |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| mTokenAmount | uint256 | the amount of mTokens received |
+
+### _redeem
+
+```solidity
+function _redeem(address _receiver, uint256 _mTokenAmount, uint256 _minReceiveAmount) internal returns (uint256 paymentTokenAmount)
+```
+
+function to redeem from Midas vault
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _receiver | address | the address to receive the paymentToken |
+| _mTokenAmount | uint256 | the amount of mTokens to redeem |
+| _minReceiveAmount | uint256 | the minimum amount of paymentToken to receive |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| paymentTokenAmount | uint256 | the amount of paymentToken received |
+
+### _balanceOf
+
+```solidity
+function _balanceOf(address _token, address _of) internal view returns (uint256)
+```
+
+function to get the balance of a token
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _token | address | the address of the token |
+| _of | address | the address of the account |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | the balance of the token |
+
+### _itsTransfer
+
+```solidity
+function _itsTransfer(string destinationChain, bytes destinationAddress, bytes32 tokenId, uint256 amount, uint256 gasValue) internal
+```
+
+internal function to transfer the token using ITS
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| destinationChain | string | the destination chain |
+| destinationAddress | bytes | the destination address |
+| tokenId | bytes32 | the ITS tokenId |
+| amount | uint256 | the amount of the token |
+| gasValue | uint256 | the gas value to be paid for the transfer |
+
+### _bytesToAddress
+
+```solidity
+function _bytesToAddress(bytes b) internal pure returns (address addr)
+```
+
+internal function to convert a bytes to an address
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| b | bytes | bytes value encode using `abi.encodePacked(address)` |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| addr | address | the address |
+
+### _tokenAmountToBase18
+
+```solidity
+function _tokenAmountToBase18(uint256 amount) internal view returns (uint256)
+```
+
+internal function to convert a token amount to base18
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| amount | uint256 | the amount of the token |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | the amount in base18 |
+
+## IMidasAxelarVaultExecutable
+
+Interface for the MidasAxelarVaultExecutable contract
+
+### Sent
+
+```solidity
+event Sent(bytes32 commandId)
+```
+
+event emitted when a operation is successful
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| commandId | bytes32 | the commandId of the send operation |
+
+### Refunded
+
+```solidity
+event Refunded(bytes32 commandId, bytes _error)
+```
+
+event emitted when a refund operation is successful
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| commandId | bytes32 | the commandId of the refund operation |
+| _error | bytes | |
+
+### Deposited
+
+```solidity
+event Deposited(bytes sender, bytes recipient, string destinationChain, uint256 paymentTokenAmount, uint256 mTokenAmount)
+```
+
+event emitted when a deposit operation is successful
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| sender | bytes | the sender of the deposit operation |
+| recipient | bytes | the recipient of the deposit operation |
+| destinationChain | string | the destination chain of the deposit operation |
+| paymentTokenAmount | uint256 | the amount of payment tokens deposited |
+| mTokenAmount | uint256 | the amount of m tokens deposited |
+
+### Redeemed
+
+```solidity
+event Redeemed(bytes sender, bytes recipient, string destinationChain, uint256 mTokenAmount, uint256 paymentTokenAmount)
+```
+
+event emitted when a redemption operation is successful
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| sender | bytes | the sender of the redemption operation |
+| recipient | bytes | the recipient of the redemption operation |
+| destinationChain | string | the destination chain of the redemption operation |
+| mTokenAmount | uint256 | the amount of m tokens redeemed |
+| paymentTokenAmount | uint256 | the amount of payment tokens redeemed |
+
+### OnlySelf
+
+```solidity
+error OnlySelf(address caller)
+```
+
+error emitted when the caller is not the self
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| caller | address | the caller of the function |
+
+### OnlyValidExecutableTokenId
+
+```solidity
+error OnlyValidExecutableTokenId(bytes32 tokenId)
+```
+
+error emitted when the tokenId is not a valid executable tokenId
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| tokenId | bytes32 | the tokenId of the ITS token |
+
+### depositVault
+
+```solidity
+function depositVault() external view returns (contract IDepositVault)
+```
+
+getter for the deposit vault
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | contract IDepositVault | the deposit vault |
+
+### redemptionVault
+
+```solidity
+function redemptionVault() external view returns (contract IRedemptionVault)
+```
+
+getter for the redemption vault
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | contract IRedemptionVault | the redemption vault |
+
+### paymentTokenId
+
+```solidity
+function paymentTokenId() external view returns (bytes32)
+```
+
+getter for the paymentToken ITS id
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bytes32 | the paymentToken ITS id |
+
+### paymentTokenErc20
+
+```solidity
+function paymentTokenErc20() external view returns (address)
+```
+
+getter for the paymentToken ERC20
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | address | the paymentToken ERC20 |
+
+### mTokenId
+
+```solidity
+function mTokenId() external view returns (bytes32)
+```
+
+getter for the mToken ITS id
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bytes32 | the mToken ITS id |
+
+### mTokenErc20
+
+```solidity
+function mTokenErc20() external view returns (address)
+```
+
+getter for the mToken ERC20
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | address | the mToken ERC20 |
+
+### depositAndSend
+
+```solidity
+function depositAndSend(uint256 _paymentTokenAmount, bytes _data) external payable
+```
+
+deposits and sends the paymentToken to the destination chain
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _paymentTokenAmount | uint256 | the amount of payment tokens to deposit |
+| _data | bytes | encoded data for the deposit. Expected data: abi.encode(bytes receiver,uint256 minReceiveAmount,bytes32 referrerId,string receiverChainName); |
+
+### redeemAndSend
+
+```solidity
+function redeemAndSend(uint256 _mTokenAmount, bytes _data) external payable
+```
+
+redeems and sends the mToken to the destination chain
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _mTokenAmount | uint256 | the amount of m tokens to redeem |
+| _data | bytes | encoded data for the redemption Expected data: abi.encode(bytes receiver,uint256 minReceiveAmount,string receiverChainName); |
+
+## MidasLzMintBurnOFTAdapter
+
+OFT MintBurn adapter implementation
+
+### SenderNotThis
+
+```solidity
+error SenderNotThis(address sender)
+```
+
+error thrown when the sender is not the contract
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| sender | address | the address of the sender |
+
+### onlyThis
+
+```solidity
+modifier onlyThis()
+```
+
+modifier to check if the sender is the contract itself
+
+### constructor
+
+```solidity
+constructor(address _token, address _lzEndpoint, address _delegate, struct RateLimiter.RateLimitConfig[] _rateLimitConfigs) public
+```
+
+constructor
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _token | address | address of the mToken |
+| _lzEndpoint | address | address of the LayerZero endpoint |
+| _delegate | address | address of the delegate |
+| _rateLimitConfigs | struct RateLimiter.RateLimitConfig[] | |
+
+### burn
```solidity
-function setOverrideGetTokenRate(bool val) external
+function burn(address _from, uint256 _amount) external returns (bool)
```
-### setGetTokenRateValue
+Burns tokens from a specified account
-```solidity
-function setGetTokenRateValue(uint256 val) external
-```
+#### Parameters
-### calcAndValidateDeposit
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _from | address | Address from which tokens will be burned |
+| _amount | uint256 | Amount of tokens to be burned |
-```solidity
-function calcAndValidateDeposit(address user, address tokenIn, uint256 amountToken, bool isInstant) external returns (struct DepositVault.CalcAndValidateDepositResult)
-```
+#### Return Values
-### convertTokenToUsdTest
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bool | |
+
+### mint
```solidity
-function convertTokenToUsdTest(address tokenIn, uint256 amount) external returns (uint256 amountInUsd, uint256 rate)
+function mint(address _to, uint256 _amount) external returns (bool)
```
-### convertUsdToMTokenTest
+Mints tokens to a specified account
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _to | address | Address to which tokens will be minted |
+| _amount | uint256 | Amount of tokens to be minted |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bool | |
+
+### setRateLimits
```solidity
-function convertUsdToMTokenTest(uint256 amountUsd) external returns (uint256 amountMToken, uint256 mTokenRate)
+function setRateLimits(struct RateLimiter.RateLimitConfig[] _rateLimitConfigs) external
```
-### _getTokenRate
+Sets the rate limits for the adapter
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _rateLimitConfigs | struct RateLimiter.RateLimitConfig[] | the rate limit configs to set |
+
+### getRateLimit
```solidity
-function _getTokenRate(address dataFeed, bool stable) internal view returns (uint256)
+function getRateLimit(uint32 _dstEid) external view returns (struct RateLimiter.RateLimit)
```
-_get token rate depends on data feed and stablecoin flag_
+Returns the rate limit for a given destination EID
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| dataFeed | address | address of dataFeed from token config |
-| stable | bool | is stablecoin |
+| _dstEid | uint32 | the destination EID |
-## ManageableVaultTester
+#### Return Values
-### _disableInitializers
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | struct RateLimiter.RateLimit | the rate limit struct |
+
+### sharedDecimals
```solidity
-function _disableInitializers() internal
+function sharedDecimals() public pure returns (uint8)
```
-_Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
-Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
-to any version. It is recommended to use this to lock implementation contracts that are designed to be called
-through proxies.
+Returns the shared decimals for the adapter
-Emits an {Initialized} event the first time it is successfully executed._
+_Overridden to 9 because default is not enough for
+some of the mTokens_
-### initialize
+#### Return Values
-```solidity
-function initialize(address _ac, struct MTokenInitParams _mTokenInitParams, struct ReceiversInitParams _receiversInitParams, struct InstantInitParams _instantInitParams, address _sanctionsList, uint256 _variationTolerance, uint256 _minAmount) external
-```
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint8 | The shared decimals |
-### initializeWithoutInitializer
+### _debit
```solidity
-function initializeWithoutInitializer(address _ac, struct MTokenInitParams _mTokenInitParams, struct ReceiversInitParams _receiversInitParams, struct InstantInitParams _instantInitParams, address _sanctionsList, uint256 _variationTolerance, uint256 _minAmount) external
+function _debit(address _from, uint256 _amountLD, uint256 _minAmountLD, uint32 _dstEid) internal returns (uint256 amountSentLD, uint256 amountReceivedLD)
```
-### vaultRole
+Burns tokens from the sender's balance to prepare for sending.
-```solidity
-function vaultRole() public view virtual returns (bytes32)
-```
+_WARNING: The default OFTAdapter implementation assumes LOSSLESS transfers, i.e., 1 token in, 1 token out.
+ If the 'innerToken' applies something like a transfer fee, the default will NOT work.
+ A pre/post balance check will need to be done to calculate the amountReceivedLD._
+
+#### Parameters
-AC role of vault administrator
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _from | address | The address to debit the tokens from. |
+| _amountLD | uint256 | The amount of tokens to send in local decimals. |
+| _minAmountLD | uint256 | The minimum amount to send in local decimals. |
+| _dstEid | uint32 | The destination chain ID. |
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | bytes32 | role bytes32 role |
+| amountSentLD | uint256 | The amount sent in local decimals. |
+| amountReceivedLD | uint256 | The amount received in local decimals on the remote. |
-## SanctionsListMock
+## MidasLzVaultComposerSync
-### isSanctioned
+This contract is a composer that allows deposits and redemptions operations against a
+ synchronous vault across different chains using LayerZero's OFT protocol.
-```solidity
-mapping(address => bool) isSanctioned
-```
+_The contract is designed to handle deposits and redemptions of vault mTokens and paymentTokens,
+ ensuring that the mToken and paymentToken are correctly managed and transferred across chains.
+ It also includes slippage protection and refund mechanisms for failed transactions.
+Default refunds are enabled to EOA addresses only on the source._
-### setSanctioned
+### TokenAddressMismatch
```solidity
-function setSanctioned(address addr, bool sanctioned) external
+error TokenAddressMismatch(address oftTokenValue, address dvValue, address rvValue)
```
-## eUSD
+error for token address mismatch
-### E_USD_MINT_OPERATOR_ROLE
-
-```solidity
-bytes32 E_USD_MINT_OPERATOR_ROLE
-```
+#### Parameters
-actor that can mint eUSD
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| oftTokenValue | address | address of OFT token |
+| dvValue | address | address of mToken of deposit vault |
+| rvValue | address | address of mToken of redemption vault |
-### E_USD_BURN_OPERATOR_ROLE
+### InvalidTokenRate
```solidity
-bytes32 E_USD_BURN_OPERATOR_ROLE
+error InvalidTokenRate(address feed)
```
-actor that can burn eUSD
+error for invalid token rate
-### E_USD_PAUSE_OPERATOR_ROLE
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| feed | address | address of failed data feed contract |
+
+### depositVault
```solidity
-bytes32 E_USD_PAUSE_OPERATOR_ROLE
+contract IDepositVault depositVault
```
-actor that can pause eUSD
+getter for the deposit vault
-### initialize
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+
+### redemptionVault
```solidity
-function initialize(address _accessControl) external
+contract IRedemptionVault redemptionVault
```
-upgradeable pattern contract`s initializer
+getter for the redemption vault
-#### Parameters
+#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| _accessControl | address | address of MidasAccessControll contract |
-### _minterRole
+### paymentTokenOft
```solidity
-function _minterRole() internal pure returns (bytes32)
+address paymentTokenOft
```
-_AC role, owner of which can mint eUSD token_
+getter for the paymentToken OFT
-### _burnerRole
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+
+### paymentTokenErc20
```solidity
-function _burnerRole() internal pure returns (bytes32)
+address paymentTokenErc20
```
-_AC role, owner of which can burn eUSD token_
+getter for the paymentToken ERC20
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
-### _pauserRole
+### mTokenOft
```solidity
-function _pauserRole() internal pure returns (bytes32)
+address mTokenOft
```
-_AC role, owner of which can pause eUSD token_
+getter for the mToken OFT
-## CustomAggregatorV3CompatibleFeed
+#### Return Values
-AggregatorV3 compatible feed, where price is submitted manually by feed admins
+| Name | Type | Description |
+| ---- | ---- | ----------- |
-### RoundData
+### mTokenErc20
```solidity
-struct RoundData {
- uint80 roundId;
- int256 answer;
- uint256 startedAt;
- uint256 updatedAt;
- uint80 answeredInRound;
-}
+address mTokenErc20
```
-### description
+getter for the mToken ERC20
-```solidity
-string description
-```
+#### Return Values
-feed description
+| Name | Type | Description |
+| ---- | ---- | ----------- |
-### latestRound
+### paymentTokenDecimals
```solidity
-uint80 latestRound
+uint8 paymentTokenDecimals
```
-last round id
+decimals of `paymentTokenErc20`
-### maxAnswerDeviation
+### lzEndpoint
```solidity
-uint256 maxAnswerDeviation
+address lzEndpoint
```
-max deviation from lattest price in %
+getter for the LayerZero endpoint
-_10 ** decimals() is a percentage precision_
+#### Return Values
-### minAnswer
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+
+### thisChaindEid
```solidity
-int192 minAnswer
+uint32 thisChaindEid
```
-minimal possible answer that feed can return
-
-### maxAnswer
+getter for the current chain EID
-```solidity
-int192 maxAnswer
-```
+#### Return Values
-maximal possible answer that feed can return
+| Name | Type | Description |
+| ---- | ---- | ----------- |
-### AnswerUpdated
+### constructor
```solidity
-event AnswerUpdated(int256 data, uint256 roundId, uint256 timestamp)
+constructor(address _depositVault, address _redemptionVault, address _paymentTokenOft, address _mTokenOft) public
```
-### onlyAggregatorAdmin
+### initialize
```solidity
-modifier onlyAggregatorAdmin()
+function initialize() external
```
-_checks that msg.sender do have a feedAdminRole() role_
+Initializes the contract
-### initialize
+### lzCompose
```solidity
-function initialize(address _accessControl, int192 _minAnswer, int192 _maxAnswer, uint256 _maxAnswerDeviation, string _description) external
+function lzCompose(address _composeSender, bytes32 _guid, bytes _message, address, bytes) external payable
```
-upgradeable pattern contract`s initializer
+Handles LayerZero compose operations for vault transactions with automatic refund functionality
+
+_This composer is designed to handle refunds to an EOA address and not a contract
+Any revert in handleCompose() causes a refund back to the src EXCEPT for InsufficientMsgValue_
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| _accessControl | address | address of MidasAccessControll contract |
-| _minAnswer | int192 | init value for `minAnswer`. Should be < `_maxAnswer` |
-| _maxAnswer | int192 | init value for `maxAnswer`. Should be > `_minAnswer` |
-| _maxAnswerDeviation | uint256 | init value for `maxAnswerDeviation` |
-| _description | string | init value for `description` |
+| _composeSender | address | The OFT contract address used for refunds, must be either paymentTokenOft or mTokenOft |
+| _guid | bytes32 | LayerZero's unique tx id (created on the source tx) |
+| _message | bytes | Decomposable bytes object into [composeHeader][composeMessage] |
+| | address | |
+| | bytes | |
-### setRoundDataSafe
+### handleCompose
```solidity
-function setRoundDataSafe(int256 _data) external
+function handleCompose(address _oftIn, bytes32 _composeFrom, bytes _composeMsg, uint256 _amount) public payable virtual
```
-works as `setRoundData()`, but also checks the
-deviation with the lattest submitted data
+Handles the compose operation for OFT transactions
-_deviation with previous data needs to be <= `maxAnswerDeviation`_
+_This function can only be called by the contract itself (self-call restriction)
+ Decodes the compose message to extract SendParam and minimum message value
+ Routes to either deposit or redeem flow based on the input OFT token type_
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| _data | int256 | data value |
+| _oftIn | address | The OFT token whose funds have been received in the lzReceive associated with this lzTx |
+| _composeFrom | bytes32 | The bytes32 identifier of the compose sender |
+| _composeMsg | bytes | The encoded message containing SendParam, minMsgValue and extraOptions |
+| _amount | uint256 | The amount of tokens received in the lzReceive associated with this lzTx |
-### setRoundData
+### depositAndSend
```solidity
-function setRoundData(int256 _data) public
+function depositAndSend(uint256 _paymentTokenAmount, bytes _extraOptions, struct SendParam _sendParam, address _refundAddress) external payable
```
-sets the data for `latestRound` + 1 round id
-
-_`_data` should be >= `minAnswer` and <= `maxAnswer`.
-Function should be called only from address with `feedAdminRole()`_
+Deposits payment token from the caller into the vault and sends them to the recipient
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| _data | int256 | data value |
+| _paymentTokenAmount | uint256 | |
+| _extraOptions | bytes | |
+| _sendParam | struct SendParam | |
+| _refundAddress | address | |
-### latestRoundData
+### redeemAndSend
```solidity
-function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
+function redeemAndSend(uint256 _mTokenAmount, bytes _extraOptions, struct SendParam _sendParam, address _refundAddress) external payable
```
-### version
+Redeems vault mTokens and sends the resulting payment tokens to the user
-```solidity
-function version() external pure returns (uint256)
-```
+#### Parameters
-### lastAnswer
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _mTokenAmount | uint256 | |
+| _extraOptions | bytes | |
+| _sendParam | struct SendParam | |
+| _refundAddress | address | |
+
+### _depositAndSend
```solidity
-function lastAnswer() public view returns (int256)
+function _depositAndSend(bytes32 _depositor, uint256 _paymentTokenAmount, bytes _extraOptions, struct SendParam _sendParam, address _refundAddress) internal
```
-#### Return Values
+This function first deposits the paymentTokens to mint mTokens, validates the mTokens meet minimum slippage requirements,
+ then sends the minted mTokens cross-chain using the OFT protocol
+The _sendParam.amountLD is updated to the actual mToken amount minted, and minAmountLD is reset to 0 for the send operation
+
+_Internal function that deposits paymentTokens and sends mTokens to another chain_
+
+#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | int256 | answer of lattest price submission |
+| _depositor | bytes32 | The depositor (bytes32 format to account for non-evm addresses) |
+| _paymentTokenAmount | uint256 | The number of paymentTokens to deposit |
+| _extraOptions | bytes | Extra options for the deposit operation |
+| _sendParam | struct SendParam | Parameter that defines how to send the mTokens |
+| _refundAddress | address | Address to receive excess payment of the LZ fees |
-### lastTimestamp
+### _redeemAndSend
```solidity
-function lastTimestamp() public view returns (uint256)
+function _redeemAndSend(bytes32 _redeemer, uint256 _mTokenAmount, bytes, struct SendParam _sendParam, address _refundAddress) internal
```
-#### Return Values
+This function first redeems the specified mToken amount for the underlying paymentToken,
+ validates the received amount against slippage protection, then initiates a cross-chain
+ transfer of the redeemed paymentTokens using the OFT protocol
+The minAmountLD in _sendParam is reset to 0 after slippage validation since the
+ actual amount has already been verified
+
+_Internal function that redeems mTokens for paymentTokens and sends them cross-chain_
+
+#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | uint256 | timestamp of lattest price submission |
+| _redeemer | bytes32 | The address of the redeemer in bytes32 format |
+| _mTokenAmount | uint256 | The number of mTokens to redeem |
+| | bytes | |
+| _sendParam | struct SendParam | Parameter that defines how to send the paymentTokens |
+| _refundAddress | address | Address to receive excess payment of the LZ fees |
-### getRoundData
+### _deposit
```solidity
-function getRoundData(uint80 _roundId) public view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
+function _deposit(address _receiver, uint256 _paymentTokenAmount, uint256 _minReceiveAmount, bytes32 _referrerId) internal returns (uint256 mTokenAmount)
```
-### feedAdminRole
+_Internal function to deposit paymentTokens into the vault_
-```solidity
-function feedAdminRole() public view virtual returns (bytes32)
-```
+#### Parameters
-_describes a role, owner of which can update prices in this feed_
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _receiver | address | The address to receive the mTokens |
+| _paymentTokenAmount | uint256 | The number of paymentTokens to deposit into the vault |
+| _minReceiveAmount | uint256 | The minimum amount of mTokens to receive |
+| _referrerId | bytes32 | The referrer id |
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+| mTokenAmount | uint256 | The number of mTokens received from the vault deposit |
-### decimals
+### _redeem
```solidity
-function decimals() public pure returns (uint8)
+function _redeem(address _receiver, uint256 _mTokenAmount, uint256 _minReceiveAmount) internal returns (uint256 paymentTokenAmount)
```
-### _getDeviation
+_Internal function to redeem mTokens from the vault_
-```solidity
-function _getDeviation(int256 _lastPrice, int256 _newPrice) internal pure returns (uint256)
-```
+#### Parameters
-_calculates a deviation in % between `_lastPrice` and `_newPrice`_
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _receiver | address | The address to receive the paymentTokens |
+| _mTokenAmount | uint256 | The number of mTokens to redeem from the vault |
+| _minReceiveAmount | uint256 | The minimum amount of paymentTokens to receive |
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | uint256 | deviation in `10 ** decimals()` precision |
-
-## CustomAggregatorV3CompatibleFeedDiscounted
-
-AggregatorV3 compatible proxy-feed that discounts the price
-of an underlying chainlink compatible feed by a given percentage
+| paymentTokenAmount | uint256 | The number of paymentTokens received from the vault redemption |
-### underlyingFeed
+### _sendOft
```solidity
-contract AggregatorV3Interface underlyingFeed
+function _sendOft(address _oft, struct SendParam _sendParam, address _refundAddress) internal
```
-the underlying chainlink compatible feed
-
-### discountPercentage
+_Internal function that handles token transfer to the recipient
+If the destination eid is the same as the current eid, it transfers the tokens directly to the recipient
+If the destination eid is different, it sends a LayerZero cross-chain transaction_
-```solidity
-uint256 discountPercentage
-```
+#### Parameters
-the discount percentage. Expressed in 10 ** decimals() precision
-Example: 10 ** decimals() = 1%
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _oft | address | The OFT contract address to use for sending |
+| _sendParam | struct SendParam | The parameters for the send operation |
+| _refundAddress | address | Address to receive excess payment of the LZ fees |
-### constructor
+### _refund
```solidity
-constructor(address _underlyingFeed, uint256 _discountPercentage) public
+function _refund(address _oft, bytes _message, uint256 _amount, address _refundAddress) internal
```
-constructor
+_Internal function to refund input tokens to sender on source during a failed transaction_
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| _underlyingFeed | address | the underlying chainlink compatible feed |
-| _discountPercentage | uint256 | the discount percentage. Expressed in 10 ** decimals() precision |
+| _oft | address | The OFT contract address used for refunding |
+| _message | bytes | The original message that was sent |
+| _amount | uint256 | The amount of tokens to refund |
+| _refundAddress | address | Address to receive the refund |
-### latestRoundData
+### _requireNoValue
```solidity
-function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
+function _requireNoValue() internal view
```
-### version
-
-```solidity
-function version() external view returns (uint256)
-```
+_Internal function to revert if msg.value is not 0_
-### getRoundData
+### _parseDepositExtraOptions
```solidity
-function getRoundData(uint80 _roundId) public view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
+function _parseDepositExtraOptions(bytes _extraOptions) internal pure returns (bytes32 referrerId)
```
-### decimals
+_Internal function to parse the extra options_
-```solidity
-function decimals() public view returns (uint8)
-```
+#### Parameters
-### description
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _extraOptions | bytes | The extra options for the deposit operation |
-```solidity
-function description() public view returns (string)
-```
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| referrerId | bytes32 | The referrer id |
-### _calculateDiscountedAnswer
+### _balanceOf
```solidity
-function _calculateDiscountedAnswer(int256 _answer) internal view returns (int256)
+function _balanceOf(address _token, address _of) internal view returns (uint256)
```
-_calculates the discounted answer_
+_Internal function to get the balance of the token of the contract_
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| _answer | int256 | the answer to discount |
+| _token | address | the address of the token |
+| _of | address | the address of the account |
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | int256 | the discounted answer |
+| [0] | uint256 | balance The balance of the token of the contract |
-## DataFeed
-
-Wrapper of ChainLink`s AggregatorV3 data feeds
-
-### aggregator
+### _tokenAmountToBase18
```solidity
-contract AggregatorV3Interface aggregator
+function _tokenAmountToBase18(uint256 amount) internal view returns (uint256)
```
-AggregatorV3Interface contract address
+_Internal function to convert a token amount to base18_
-### healthyDiff
+#### Parameters
-```solidity
-uint256 healthyDiff
-```
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| amount | uint256 | The amount of the token |
-_healty difference between `block.timestamp` and `updatedAt` timestamps_
+#### Return Values
-### minExpectedAnswer
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint256 | The amount in base18 |
+
+### receive
```solidity
-int256 minExpectedAnswer
+receive() external payable
```
-_minimal answer expected to receive from the `aggregator`_
+========================== Receive =====================================
-### maxExpectedAnswer
+## IMidasLzVaultComposerSync
+
+Interface for the MidasLzVaultComposerSync contract
+
+### Sent
```solidity
-int256 maxExpectedAnswer
+event Sent(bytes32 guid)
```
-_maximal answer expected to receive from the `aggregator`_
+event emitted when a send operation is successful
-### initialize
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| guid | bytes32 | the guid of the send operation |
+
+### Refunded
```solidity
-function initialize(address _ac, address _aggregator, uint256 _healthyDiff, int256 _minExpectedAnswer, int256 _maxExpectedAnswer) external
+event Refunded(bytes32 guid)
```
-upgradeable pattern contract`s initializer
+event emitted when a refund operation is successful
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| _ac | address | MidasAccessControl contract address |
-| _aggregator | address | AggregatorV3Interface contract address |
-| _healthyDiff | uint256 | max. staleness time for data feed answers |
-| _minExpectedAnswer | int256 | min.expected answer value from data feed |
-| _maxExpectedAnswer | int256 | max.expected answer value from data feed |
+| guid | bytes32 | the guid of the refund operation |
-### changeAggregator
+### Deposited
```solidity
-function changeAggregator(address _aggregator) external
+event Deposited(bytes32 sender, bytes32 recipient, uint32 dstEid, uint256 paymentTokenAmount, uint256 mTokenAmount)
```
-updates `aggregator` address
+event emitted when a deposit operation is successful
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| _aggregator | address | new AggregatorV3Interface contract address |
+| sender | bytes32 | the sender of the deposit operation |
+| recipient | bytes32 | the recipient of the deposit operation |
+| dstEid | uint32 | the destination eid of the deposit operation |
+| paymentTokenAmount | uint256 | the amount of payment tokens deposited |
+| mTokenAmount | uint256 | the amount of m tokens deposited |
-### setHealthyDiff
+### Redeemed
```solidity
-function setHealthyDiff(uint256 _healthyDiff) external
+event Redeemed(bytes32 sender, bytes32 recipient, uint32 dstEid, uint256 mTokenAmount, uint256 paymentTokenAmount)
```
-_updates `healthyDiff` value_
+event emitted when a redemption operation is successful
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| _healthyDiff | uint256 | new value |
+| sender | bytes32 | the sender of the redemption operation |
+| recipient | bytes32 | the recipient of the redemption operation |
+| dstEid | uint32 | the destination eid of the redemption operation |
+| mTokenAmount | uint256 | the amount of m tokens redeemed |
+| paymentTokenAmount | uint256 | the amount of payment tokens redeemed |
-### setMinExpectedAnswer
+### OnlyEndpoint
```solidity
-function setMinExpectedAnswer(int256 _minExpectedAnswer) external
+error OnlyEndpoint(address caller)
```
-_updates `minExpectedAnswer` value_
+error emitted when the caller is not the endpoint
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| _minExpectedAnswer | int256 | min value |
+| caller | address | the caller of the function |
-### setMaxExpectedAnswer
+### OnlySelf
```solidity
-function setMaxExpectedAnswer(int256 _maxExpectedAnswer) external
+error OnlySelf(address caller)
```
-_updates `maxExpectedAnswer` value_
+error emitted when the caller is not the self
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| _maxExpectedAnswer | int256 | max value |
+| caller | address | the caller of the function |
-### getDataInBase18
+### OnlyValidComposeCaller
```solidity
-function getDataInBase18() external view returns (uint256 answer)
+error OnlyValidComposeCaller(address caller)
```
-fetches answer from aggregator
-and converts it to the base18 precision
+error emitted when the caller is not a valid compose caller
-#### Return Values
+#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| answer | uint256 | fetched aggregator answer |
+| caller | address | the caller of the function |
-### feedAdminRole
+### InsufficientMsgValue
```solidity
-function feedAdminRole() public pure virtual returns (bytes32)
+error InsufficientMsgValue(uint256 expectedMsgValue, uint256 actualMsgValue)
```
-_describes a role, owner of which can manage this feed_
+error emitted when the msg.value is insufficient
-#### Return Values
+#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+| expectedMsgValue | uint256 | the expected msg.value |
+| actualMsgValue | uint256 | the actual msg.value |
+
+### NoMsgValueExpected
-## HBUsdtCustomAggregatorFeed
+```solidity
+error NoMsgValueExpected()
+```
-AggregatorV3 compatible feed for hbUSDT,
-where price is submitted manually by feed admins
+error emitted when msg.value expected to be 0 but is not
-### feedAdminRole
+### depositVault
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function depositVault() external view returns (contract IDepositVault)
```
-_describes a role, owner of which can update prices in this feed_
+getter for the deposit vault
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+| [0] | contract IDepositVault | the deposit vault |
-## HBUsdtDataFeed
-
-DataFeed for hbUSDT product
-
-### feedAdminRole
+### redemptionVault
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function redemptionVault() external view returns (contract IRedemptionVault)
```
-_describes a role, owner of which can manage this feed_
+getter for the redemption vault
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+| [0] | contract IRedemptionVault | the redemption vault |
-## hbUSDT
-
-### HB_USDT_MINT_OPERATOR_ROLE
+### paymentTokenOft
```solidity
-bytes32 HB_USDT_MINT_OPERATOR_ROLE
+function paymentTokenOft() external view returns (address)
```
-actor that can mint hbUSDT
-
-### HB_USDT_BURN_OPERATOR_ROLE
+getter for the paymentToken OFT
-```solidity
-bytes32 HB_USDT_BURN_OPERATOR_ROLE
-```
+#### Return Values
-actor that can burn hbUSDT
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | address | the paymentToken OFT |
-### HB_USDT_PAUSE_OPERATOR_ROLE
+### paymentTokenErc20
```solidity
-bytes32 HB_USDT_PAUSE_OPERATOR_ROLE
+function paymentTokenErc20() external view returns (address)
```
-actor that can pause hbUSDT
+getter for the paymentToken ERC20
-### initialize
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | address | the paymentToken ERC20 |
+
+### mTokenOft
```solidity
-function initialize(address _accessControl) external
+function mTokenOft() external view returns (address)
```
-upgradeable pattern contract`s initializer
+getter for the mToken OFT
-#### Parameters
+#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| _accessControl | address | address of MidasAccessControll contract |
+| [0] | address | the mToken OFT |
-### _minterRole
+### mTokenErc20
```solidity
-function _minterRole() internal pure returns (bytes32)
+function mTokenErc20() external view returns (address)
```
-_AC role, owner of which can mint hbUSDT token_
+getter for the mToken ERC20
-### _burnerRole
-
-```solidity
-function _burnerRole() internal pure returns (bytes32)
-```
+#### Return Values
-_AC role, owner of which can burn hbUSDT token_
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | address | the mToken ERC20 |
-### _pauserRole
+### lzEndpoint
```solidity
-function _pauserRole() internal pure returns (bytes32)
+function lzEndpoint() external view returns (address)
```
-_AC role, owner of which can pause hbUSDT token_
+getter for the LayerZero endpoint
-## HBXautCustomAggregatorFeed
+#### Return Values
-AggregatorV3 compatible feed for hbXAUt,
-where price is submitted manually by feed admins
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | address | the LayerZero endpoint |
-### feedAdminRole
+### thisChaindEid
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function thisChaindEid() external view returns (uint32)
```
-_describes a role, owner of which can update prices in this feed_
+getter for the current chain EID
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
-
-## HBXautDataFeed
+| [0] | uint32 | the current chain EID |
-DataFeed for hbXAUt product
-
-### feedAdminRole
+### depositAndSend
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function depositAndSend(uint256 paymentTokenAmount, bytes extraOptions, struct SendParam sendParam, address refundAddress) external payable
```
-_describes a role, owner of which can manage this feed_
+Deposits payment token from the caller into the vault and sends them to the recipient
-#### Return Values
+#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
-
-## hbXAUt
+| paymentTokenAmount | uint256 | The number of ERC20 tokens to deposit and send |
+| extraOptions | bytes | Extra options for the deposit operation. Expected extraOptions: abi.encode(bytes32 referrerId) or 0x |
+| sendParam | struct SendParam | Parameters on how to send the mTokens to the recipient |
+| refundAddress | address | Address to receive excess `msg.value` |
-### HB_XAUT_MINT_OPERATOR_ROLE
+### redeemAndSend
```solidity
-bytes32 HB_XAUT_MINT_OPERATOR_ROLE
+function redeemAndSend(uint256 mTokenAmount, bytes extraOptions, struct SendParam sendParam, address refundAddress) external payable
```
-actor that can mint hbXAUt
+Redeems vault mTokens and sends the resulting payment tokens to the user
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| mTokenAmount | uint256 | The number of vault mTokens to redeem |
+| extraOptions | bytes | Extra options for the redeem operation. Expected extraOptions: 0x |
+| sendParam | struct SendParam | Parameter that defines how to send the payment tokens to the recipient |
+| refundAddress | address | Address to receive excess payment of the LZ fees |
-### HB_XAUT_BURN_OPERATOR_ROLE
+### receive
```solidity
-bytes32 HB_XAUT_BURN_OPERATOR_ROLE
+receive() external payable
```
-actor that can burn hbXAUt
+========================== Receive =====================================
+
+## AggregatorV3DeprecatedMock
-### HB_XAUT_PAUSE_OPERATOR_ROLE
+### decimals
```solidity
-bytes32 HB_XAUT_PAUSE_OPERATOR_ROLE
+function decimals() external view returns (uint8)
```
-actor that can pause hbXAUt
-
-### initialize
+### description
```solidity
-function initialize(address _accessControl) external
+function description() external view returns (string)
```
-upgradeable pattern contract`s initializer
-
-#### Parameters
+### version
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _accessControl | address | address of MidasAccessControll contract |
+```solidity
+function version() external view returns (uint256)
+```
-### _minterRole
+### setRoundData
```solidity
-function _minterRole() internal pure returns (bytes32)
+function setRoundData(int256 _data) external
```
-_AC role, owner of which can mint hbXAUt token_
+### getRoundData
+
+```solidity
+function getRoundData(uint80 _roundId) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
+```
-### _burnerRole
+### latestRoundData
```solidity
-function _burnerRole() internal pure returns (bytes32)
+function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
```
-_AC role, owner of which can burn hbXAUt token_
+## AggregatorV3Mock
-### _pauserRole
+### decimals
```solidity
-function _pauserRole() internal pure returns (bytes32)
+function decimals() external view returns (uint8)
```
-_AC role, owner of which can pause hbXAUt token_
+### description
+
+```solidity
+function description() external view returns (string)
+```
-## HypeBtcCustomAggregatorFeed
+### version
-AggregatorV3 compatible feed for hypeBTC,
-where price is submitted manually by feed admins
+```solidity
+function version() external view returns (uint256)
+```
-### feedAdminRole
+### setRoundData
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function setRoundData(int256 _data) external
```
-_describes a role, owner of which can update prices in this feed_
+### getRoundData
-#### Return Values
+```solidity
+function getRoundData(uint80 _roundId) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
+```
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+### latestRoundData
-## HypeBtcDataFeed
+```solidity
+function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
+```
-DataFeed for hypeBTC product
+## AggregatorV3UnhealthyMock
-### feedAdminRole
+### decimals
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function decimals() external view returns (uint8)
```
-_describes a role, owner of which can manage this feed_
+### description
-#### Return Values
+```solidity
+function description() external view returns (string)
+```
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+### version
-## hypeBTC
+```solidity
+function version() external view returns (uint256)
+```
-### HYPE_BTC_MINT_OPERATOR_ROLE
+### setRoundData
```solidity
-bytes32 HYPE_BTC_MINT_OPERATOR_ROLE
+function setRoundData(int256 _data) external
```
-actor that can mint hypeBTC
-
-### HYPE_BTC_BURN_OPERATOR_ROLE
+### getRoundData
```solidity
-bytes32 HYPE_BTC_BURN_OPERATOR_ROLE
+function getRoundData(uint80 _roundId) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
```
-actor that can burn hypeBTC
-
-### HYPE_BTC_PAUSE_OPERATOR_ROLE
+### latestRoundData
```solidity
-bytes32 HYPE_BTC_PAUSE_OPERATOR_ROLE
+function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
```
-actor that can pause hypeBTC
+## BlacklistableTester
### initialize
@@ -6064,3271 +13859,3272 @@ actor that can pause hypeBTC
function initialize(address _accessControl) external
```
-upgradeable pattern contract`s initializer
+### onlyNotBlacklistedTester
-#### Parameters
+```solidity
+function onlyNotBlacklistedTester(address account) external
+```
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _accessControl | address | address of MidasAccessControl contract |
+### contractAdminRole
-### _minterRole
+```solidity
+function contractAdminRole() public pure returns (bytes32)
+```
+
+_main admin role for the contract_
+
+### _disableInitializers
```solidity
-function _minterRole() internal pure returns (bytes32)
+function _disableInitializers() internal
```
-_AC role, owner of which can mint hypeBTC token_
+_Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
+Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
+to any version. It is recommended to use this to lock implementation contracts that are designed to be called
+through proxies.
+
+Emits an {Initialized} event the first time it is successfully executed._
-### _burnerRole
+### _onlyProxyAdmin
```solidity
-function _burnerRole() internal pure returns (bytes32)
+function _onlyProxyAdmin() internal view
```
-_AC role, owner of which can burn hypeBTC token_
+function to check if the sender is the proxy admin
-### _pauserRole
+## CustomAggregatorV3CompatibleFeedTester
+
+### constructor
```solidity
-function _pauserRole() internal pure returns (bytes32)
+constructor() public
```
-_AC role, owner of which can pause hypeBTC token_
+### _disableInitializers
+
+```solidity
+function _disableInitializers() internal
+```
-## HypeEthCustomAggregatorFeed
+_Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
+Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
+to any version. It is recommended to use this to lock implementation contracts that are designed to be called
+through proxies.
-AggregatorV3 compatible feed for hypeETH,
-where price is submitted manually by feed admins
+Emits an {Initialized} event the first time it is successfully executed._
-### feedAdminRole
+### _onlyProxyAdmin
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function _onlyProxyAdmin() internal view
```
-_describes a role, owner of which can update prices in this feed_
+function to check if the sender is the proxy admin
-#### Return Values
+### getDeviation
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+```solidity
+function getDeviation(int256 _lastPrice, int256 _newPrice) public pure returns (uint256)
+```
-## HypeEthDataFeed
+## DepositVaultTestBase
-DataFeed for hypeETH product
+### _disableInitializers
+
+```solidity
+function _disableInitializers() internal virtual
+```
-### feedAdminRole
+### convertTokenToUsdTest
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function convertTokenToUsdTest(address tokenIn, uint256 amount) external view returns (uint256 amountInUsd, uint256 rate)
```
-_describes a role, owner of which can manage this feed_
+### convertUsdToMTokenTest
-#### Return Values
+```solidity
+function convertUsdToMTokenTest(uint256 amountUsd) external view returns (uint256 amountMToken, uint256 mTokenRate)
+```
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+### calcAndValidateDeposit
-## hypeETH
+```solidity
+function calcAndValidateDeposit(address user, address tokenIn, uint256 amountToken, bool isInstant) external returns (struct DepositVault.CalcAndValidateDepositResult)
+```
-### HYPE_ETH_MINT_OPERATOR_ROLE
+### calculateHoldbackPartRateFromAvgTest
```solidity
-bytes32 HYPE_ETH_MINT_OPERATOR_ROLE
+function calculateHoldbackPartRateFromAvgTest(uint256 depositedUsdAmount, uint256 depositedInstantUsdAmount, uint256 mTokenRate, uint256 avgMTokenRate) external pure returns (uint256)
```
-actor that can mint hypeETH
+### _getTokenRate
+
+```solidity
+function _getTokenRate(address dataFeed, bool stable) internal view virtual returns (uint256)
+```
-### HYPE_ETH_BURN_OPERATOR_ROLE
+### contractAdminRole
```solidity
-bytes32 HYPE_ETH_BURN_OPERATOR_ROLE
+function contractAdminRole() public view virtual returns (bytes32)
```
-actor that can burn hypeETH
+## DepositVaultTest
-### HYPE_ETH_PAUSE_OPERATOR_ROLE
+### constructor
```solidity
-bytes32 HYPE_ETH_PAUSE_OPERATOR_ROLE
+constructor() public
```
-actor that can pause hypeETH
+## DepositVaultWithAaveTest
-### initialize
+### constructor
```solidity
-function initialize(address _accessControl) external
+constructor() public
```
-upgradeable pattern contract`s initializer
+### _disableInitializers
-#### Parameters
+```solidity
+function _disableInitializers() internal
+```
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _accessControl | address | address of MidasAccessControl contract |
+### _instantTransferTokensToTokensReceiver
+
+```solidity
+function _instantTransferTokensToTokensReceiver(address tokenIn, uint256 amountToken, uint256 tokensDecimals) internal virtual
+```
+
+### _requestTransferTokensToTokensReceiver
+
+```solidity
+function _requestTransferTokensToTokensReceiver(address tokenIn, uint256 amountToken, uint256 tokensDecimals) internal
+```
-### _minterRole
+### _getTokenRate
```solidity
-function _minterRole() internal pure returns (bytes32)
+function _getTokenRate(address dataFeed, bool stable) internal view returns (uint256)
```
-_AC role, owner of which can mint hypeETH token_
-
-### _burnerRole
+### contractAdminRole
```solidity
-function _burnerRole() internal pure returns (bytes32)
+function contractAdminRole() public view returns (bytes32)
```
-_AC role, owner of which can burn hypeETH token_
+## DepositVaultWithMTokenTest
-### _pauserRole
+### constructor
```solidity
-function _pauserRole() internal pure returns (bytes32)
+constructor() public
```
-_AC role, owner of which can pause hypeETH token_
-
-## HypeUsdCustomAggregatorFeed
+### _disableInitializers
-AggregatorV3 compatible feed for hypeUSD,
-where price is submitted manually by feed admins
+```solidity
+function _disableInitializers() internal
+```
-### feedAdminRole
+### _instantTransferTokensToTokensReceiver
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function _instantTransferTokensToTokensReceiver(address tokenIn, uint256 amountToken, uint256 tokensDecimals) internal virtual
```
-_describes a role, owner of which can update prices in this feed_
-
-#### Return Values
+### _requestTransferTokensToTokensReceiver
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+```solidity
+function _requestTransferTokensToTokensReceiver(address tokenIn, uint256 amountToken, uint256 tokensDecimals) internal
+```
-## HypeUsdDataFeed
+### _getTokenRate
-DataFeed for hypeUSD product
+```solidity
+function _getTokenRate(address dataFeed, bool stable) internal view returns (uint256)
+```
-### feedAdminRole
+### contractAdminRole
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function contractAdminRole() public view returns (bytes32)
```
-_describes a role, owner of which can manage this feed_
+## DepositVaultWithMorphoTest
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+### constructor
-## hypeUSD
+```solidity
+constructor() public
+```
-### HYPE_USD_MINT_OPERATOR_ROLE
+### _disableInitializers
```solidity
-bytes32 HYPE_USD_MINT_OPERATOR_ROLE
+function _disableInitializers() internal
```
-actor that can mint hypeUSD
-
-### HYPE_USD_BURN_OPERATOR_ROLE
+### _instantTransferTokensToTokensReceiver
```solidity
-bytes32 HYPE_USD_BURN_OPERATOR_ROLE
+function _instantTransferTokensToTokensReceiver(address tokenIn, uint256 amountToken, uint256 tokensDecimals) internal virtual
```
-actor that can burn hypeUSD
-
-### HYPE_USD_PAUSE_OPERATOR_ROLE
+### _requestTransferTokensToTokensReceiver
```solidity
-bytes32 HYPE_USD_PAUSE_OPERATOR_ROLE
+function _requestTransferTokensToTokensReceiver(address tokenIn, uint256 amountToken, uint256 tokensDecimals) internal
```
-actor that can pause hypeUSD
-
-### initialize
+### _getTokenRate
```solidity
-function initialize(address _accessControl) external
+function _getTokenRate(address dataFeed, bool stable) internal view returns (uint256)
```
-upgradeable pattern contract`s initializer
+### contractAdminRole
-#### Parameters
+```solidity
+function contractAdminRole() public view returns (bytes32)
+```
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _accessControl | address | address of MidasAccessControl contract |
+## DepositVaultWithUSTBTest
-### _minterRole
+### constructor
```solidity
-function _minterRole() internal pure returns (bytes32)
+constructor() public
```
-_AC role, owner of which can mint hypeUSD token_
-
-### _burnerRole
+### _disableInitializers
```solidity
-function _burnerRole() internal pure returns (bytes32)
+function _disableInitializers() internal
```
-_AC role, owner of which can burn hypeUSD token_
-
-### _pauserRole
+### _instantTransferTokensToTokensReceiver
```solidity
-function _pauserRole() internal pure returns (bytes32)
+function _instantTransferTokensToTokensReceiver(address tokenIn, uint256 amountToken, uint256 tokensDecimals) internal virtual
```
-_AC role, owner of which can pause hypeUSD token_
-
-## IAllowListV2
+### _getTokenRate
-### EntityId
+```solidity
+function _getTokenRate(address dataFeed, bool stable) internal view returns (uint256)
+```
-### FundPermissionSet
+### contractAdminRole
```solidity
-event FundPermissionSet(IAllowListV2.EntityId entityId, string fundSymbol, bool permission)
+function contractAdminRole() public view returns (bytes32)
```
-An event emitted when an address's permission is changed for a fund.
+## GreenlistableTester
-### ProtocolAddressPermissionSet
+### initialize
```solidity
-event ProtocolAddressPermissionSet(address addr, string fundSymbol, bool isAllowed)
+function initialize(address _accessControl) external
```
-An event emitted when a protocol's permission is changed for a fund.
-
-### EntityIdSet
+### onlyGreenlistedTester
```solidity
-event EntityIdSet(address addr, uint256 entityId)
+function onlyGreenlistedTester(address account) external
```
-An event emitted when an address is associated with an entityId
-
-### BadData
+### _disableInitializers
```solidity
-error BadData()
+function _disableInitializers() internal
```
-_Thrown when the input for a function is invalid_
+_Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
+Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
+to any version. It is recommended to use this to lock implementation contracts that are designed to be called
+through proxies.
-### AlreadySet
+Emits an {Initialized} event the first time it is successfully executed._
+
+### _onlyProxyAdmin
```solidity
-error AlreadySet()
+function _onlyProxyAdmin() internal view
```
-_Thrown when the input is already equivalent to the storage being set_
+function to check if the sender is the proxy admin
-### NonZeroEntityIdMustBeChangedToZero
+### greenlistAdminRole
```solidity
-error NonZeroEntityIdMustBeChangedToZero()
+function greenlistAdminRole() public view virtual returns (bytes32)
```
-_An address's entityId can not be changed once set, it can only be unset and then set to a new value_
-
-### AddressHasProtocolPermissions
+### contractAdminRole
```solidity
-error AddressHasProtocolPermissions()
+function contractAdminRole() public pure returns (bytes32)
```
-_Thrown when trying to set entityId for an address that has protocol permissions_
+_main admin role for the contract_
-### AddressHasEntityId
+### greenlistedRole
```solidity
-error AddressHasEntityId()
+function greenlistedRole() public view virtual returns (bytes32)
```
-_Thrown when trying to set protocol permissions for an address that has an entityId_
+AC role of a greenlist
-### CodeSizeZero
+#### Return Values
-```solidity
-error CodeSizeZero()
-```
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bytes32 | role bytes32 role |
-_Thrown when trying to set protocol permissions but the code size is 0_
+## ManageableVaultTesterBase
-### Deprecated
+### _disableInitializers
```solidity
-error Deprecated()
+function _disableInitializers() internal virtual
```
-_Thrown when a method is no longer supported_
+_Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
+Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
+to any version. It is recommended to use this to lock implementation contracts that are designed to be called
+through proxies.
-### RenounceOwnershipDisabled
+Emits an {Initialized} event the first time it is successfully executed._
+
+### setVaultRole
```solidity
-error RenounceOwnershipDisabled()
+function setVaultRole(bytes32 role) external
```
-_Thrown if an attempt to call `renounceOwnership` is made_
-
-### owner
+### setOverrideGetTokenRate
```solidity
-function owner() external view returns (address)
+function setOverrideGetTokenRate(bool _override) external
```
-### addressEntityIds
+### tokenTransferFromToTester
```solidity
-function addressEntityIds(address addr) external view returns (IAllowListV2.EntityId)
+function tokenTransferFromToTester(address token, address from, address to, uint256 amount, uint256 tokenDecimals) external
```
-Gets the entityId for the provided address
-
-#### Parameters
+### setGetTokenRateValue
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| addr | address | The address to get the entityId for |
+```solidity
+function setGetTokenRateValue(uint256 val) external
+```
-### isAddressAllowedForFund
+### _getTokenRate
```solidity
-function isAddressAllowedForFund(address addr, string fundSymbol) external view returns (bool)
+function _getTokenRate(address dataFeed, bool stable) internal view virtual returns (uint256)
```
-Checks whether an address is allowed to use a fund
+_get token rate depends on data feed and stablecoin flag_
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| addr | address | The address to check permissions for |
-| fundSymbol | string | The fund symbol to check permissions for |
+| dataFeed | address | address of dataFeed from token config |
+| stable | bool | is stablecoin |
-### isEntityAllowedForFund
+### initializeExternal
```solidity
-function isEntityAllowedForFund(IAllowListV2.EntityId entityId, string fundSymbol) external view returns (bool)
+function initializeExternal(struct CommonVaultInitParams _commonVaultInitParams) external
```
-Checks whether an Entity is allowed to use a fund
-
-#### Parameters
+### initializeWithoutInitializer
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| entityId | IAllowListV2.EntityId | |
-| fundSymbol | string | The fund symbol to check permissions for |
+```solidity
+function initializeWithoutInitializer(struct CommonVaultInitParams _commonVaultInitParams) external
+```
-### setEntityAllowedForFund
+### contractAdminRole
```solidity
-function setEntityAllowedForFund(IAllowListV2.EntityId entityId, string fundSymbol, bool isAllowed) external
+function contractAdminRole() public view virtual returns (bytes32)
```
-Sets whether an Entity is allowed to use a fund
-
-#### Parameters
+_main admin role for the contract_
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| entityId | IAllowListV2.EntityId | |
-| fundSymbol | string | The fund symbol to set permissions for |
-| isAllowed | bool | The permission value to set |
+## ManageableVaultTester
-### setEntityIdForAddress
+### constructor
```solidity
-function setEntityIdForAddress(IAllowListV2.EntityId entityId, address addr) external
+constructor() public
```
-Sets the entityId for a given address. Setting to 0 removes the address from the allowList
-
-#### Parameters
+constructor
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| entityId | IAllowListV2.EntityId | The entityId to associate with an address |
-| addr | address | The address to associate with an entityId |
+## MidasAccessControlTest
-### setEntityIdForMultipleAddresses
+### _disableInitializers
```solidity
-function setEntityIdForMultipleAddresses(IAllowListV2.EntityId entityId, address[] addresses) external
+function _disableInitializers() internal
```
-Sets the entity Id for a list of addresses. Setting to 0 removes the address from the allowList
-
-#### Parameters
+_Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
+Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
+to any version. It is recommended to use this to lock implementation contracts that are designed to be called
+through proxies.
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| entityId | IAllowListV2.EntityId | The entityId to associate with an address |
-| addresses | address[] | The addresses to associate with an entityId |
+Emits an {Initialized} event the first time it is successfully executed._
-### setProtocolAddressPermission
+### _onlyProxyAdmin
```solidity
-function setProtocolAddressPermission(address addr, string fundSymbol, bool isAllowed) external
+function _onlyProxyAdmin() internal view
```
-Sets protocol permissions for an address
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| addr | address | The address to set permissions for |
-| fundSymbol | string | The fund symbol to set permissions for |
-| isAllowed | bool | The permission value to set |
+function to check if the sender is the proxy admin
-### setProtocolAddressPermissions
+### setDefaultDelayTest
```solidity
-function setProtocolAddressPermissions(address[] addresses, string fundSymbol, bool isAllowed) external
+function setDefaultDelayTest(uint32 delay) external
```
-Sets protocol permissions for multiple addresses
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| addresses | address[] | The addresses to set permissions for |
-| fundSymbol | string | The fund symbol to set permissions for |
-| isAllowed | bool | The permission value to set |
+## MidasAxelarVaultExecutableTester
-### setEntityPermissionsAndAddresses
+### constructor
```solidity
-function setEntityPermissionsAndAddresses(IAllowListV2.EntityId entityId, address[] addresses, string[] fundPermissionsToUpdate, bool[] fundPermissions) external
+constructor(address _depositVault, address _redemptionVault, bytes32 _paymentTokenId, bytes32 _mTokenId, address _interchainTokenService) public
```
-Sets entity for an array of addresses and sets permissions for an entity
-
-#### Parameters
+### depositAndSendPublic
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| entityId | IAllowListV2.EntityId | The entityId to be updated |
-| addresses | address[] | The addresses to associate with an entityId |
-| fundPermissionsToUpdate | string[] | The funds to update permissions for |
-| fundPermissions | bool[] | The permissions for each fund |
+```solidity
+function depositAndSendPublic(bytes _depositor, uint256 _paymentTokenAmount, bytes _data) external
+```
-### hasAnyProtocolPermissions
+### depositPublic
```solidity
-function hasAnyProtocolPermissions(address addr) external view returns (bool hasPermissions)
+function depositPublic(address _receiver, uint256 _paymentTokenAmount, uint256 _minReceiveAmount, bytes32 _referrerId) external returns (uint256 mTokenAmount)
```
-### protocolPermissionsForFunds
+### redeemAndSendPublic
```solidity
-function protocolPermissionsForFunds(address protocol) external view returns (uint256)
+function redeemAndSendPublic(bytes _redeemer, uint256 _mTokenAmount, bytes _data) external
```
-### protocolPermissions
+### redeemPublic
```solidity
-function protocolPermissions(address, string) external view returns (bool)
+function redeemPublic(address _receiver, uint256 _mTokenAmount, uint256 _minReceiveAmount) external virtual returns (uint256 paymentTokenAmount)
```
-### initialize
+### balanceOfPublic
```solidity
-function initialize() external
+function balanceOfPublic(address token, address _of) external view returns (uint256)
```
-## ISuperstateToken
-
-### symbol
+### itsTransferPublic
```solidity
-function symbol() external view returns (string)
+function itsTransferPublic(string _destinationChain, bytes _destinationAddress, bytes32 _tokenId, uint256 _amount, uint256 _gasValue) external payable
```
-### allowListV2
+### bytesToAddressPublic
```solidity
-function allowListV2() external view returns (address)
+function bytesToAddressPublic(bytes _b) external pure returns (address)
```
-## MBtcCustomAggregatorFeed
-
-AggregatorV3 compatible feed for mBTC,
-where price is submitted manually by feed admins
+## MidasLzVaultComposerSyncTester
-### feedAdminRole
+### HandleComposeType
```solidity
-function feedAdminRole() public pure returns (bytes32)
+enum HandleComposeType {
+ NoOverride,
+ ThrowsInsufficientBalanceError,
+ ThrowsError
+}
```
-_describes a role, owner of which can update prices in this feed_
+### handleComposeType
-#### Return Values
+```solidity
+enum MidasLzVaultComposerSyncTester.HandleComposeType handleComposeType
+```
+
+### constructor
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+```solidity
+constructor(address _depositVault, address _redemptionVault, address _paymentTokenOft, address _mTokenOft) public
+```
-## MBtcDataFeed
+### setHandleComposeType
-DataFeed for mBTC product
+```solidity
+function setHandleComposeType(enum MidasLzVaultComposerSyncTester.HandleComposeType _handleComposeType) external
+```
-### feedAdminRole
+### handleCompose
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function handleCompose(address _oftIn, bytes32 _composeFrom, bytes _composeMsg, uint256 _amount) public payable
```
-_describes a role, owner of which can manage this feed_
+Handles the compose operation for OFT transactions
-#### Return Values
+_This function can only be called by the contract itself (self-call restriction)
+ Decodes the compose message to extract SendParam and minimum message value
+ Routes to either deposit or redeem flow based on the input OFT token type_
+
+#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
-
-## mBTC
+| _oftIn | address | The OFT token whose funds have been received in the lzReceive associated with this lzTx |
+| _composeFrom | bytes32 | The bytes32 identifier of the compose sender |
+| _composeMsg | bytes | The encoded message containing SendParam, minMsgValue and extraOptions |
+| _amount | uint256 | The amount of tokens received in the lzReceive associated with this lzTx |
-### M_BTC_MINT_OPERATOR_ROLE
+### depositAndSendPublic
```solidity
-bytes32 M_BTC_MINT_OPERATOR_ROLE
+function depositAndSendPublic(bytes32 _depositor, uint256 _paymentTokenAmount, bytes _extraOptions, struct SendParam _sendParam, address _refundAddress) external
```
-actor that can mint mBTC
-
-### M_BTC_BURN_OPERATOR_ROLE
+### depositPublic
```solidity
-bytes32 M_BTC_BURN_OPERATOR_ROLE
+function depositPublic(address _receiver, uint256 _paymentTokenAmount, uint256 _minReceiveAmount, bytes32 _referrerId) external returns (uint256 mTokenAmount)
```
-actor that can burn mBTC
-
-### M_BTC_PAUSE_OPERATOR_ROLE
+### redeemAndSendPublic
```solidity
-bytes32 M_BTC_PAUSE_OPERATOR_ROLE
+function redeemAndSendPublic(bytes32 _redeemer, uint256 _mTokenAmount, bytes _extraOptions, struct SendParam _sendParam, address _refundAddress) external
```
-actor that can pause mBTC
-
-### initialize
+### redeemPublic
```solidity
-function initialize(address _accessControl) external
+function redeemPublic(address _receiver, uint256 _mTokenAmount, uint256 _minReceiveAmount) external virtual returns (uint256 paymentTokenAmount)
```
-upgradeable pattern contract`s initializer
+### parseExtraOptionsPublic
-#### Parameters
+```solidity
+function parseExtraOptionsPublic(bytes _extraOptions) external pure returns (bytes32 referrerId)
+```
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _accessControl | address | address of MidasAccessControll contract |
+### balanceOfPublic
+
+```solidity
+function balanceOfPublic(address _token, address _of) external view returns (uint256)
+```
-### _minterRole
+### sendOftPublic
```solidity
-function _minterRole() internal pure returns (bytes32)
+function sendOftPublic(address _oft, struct SendParam _sendParam, address _refundAddress) external payable
```
-_AC role, owner of which can mint mBTC token_
+## MidasPauseManagerTest
-### _burnerRole
+### _disableInitializers
```solidity
-function _burnerRole() internal pure returns (bytes32)
+function _disableInitializers() internal
```
-_AC role, owner of which can burn mBTC token_
+_Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
+Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
+to any version. It is recommended to use this to lock implementation contracts that are designed to be called
+through proxies.
+
+Emits an {Initialized} event the first time it is successfully executed._
-### _pauserRole
+### _onlyProxyAdmin
```solidity
-function _pauserRole() internal pure returns (bytes32)
+function _onlyProxyAdmin() internal view
```
-_AC role, owner of which can pause mBTC token_
+function to check if the sender is the proxy admin
-## TACmBTC
+## MidasTimelockManagerTest
-### TAC_M_BTC_MINT_OPERATOR_ROLE
+### _disableInitializers
```solidity
-bytes32 TAC_M_BTC_MINT_OPERATOR_ROLE
+function _disableInitializers() internal
```
-actor that can mint TACmBTC
+_Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
+Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
+to any version. It is recommended to use this to lock implementation contracts that are designed to be called
+through proxies.
+
+Emits an {Initialized} event the first time it is successfully executed._
-### TAC_M_BTC_BURN_OPERATOR_ROLE
+### _onlyProxyAdmin
```solidity
-bytes32 TAC_M_BTC_BURN_OPERATOR_ROLE
+function _onlyProxyAdmin() internal view
```
-actor that can burn TACmBTC
+function to check if the sender is the proxy admin
+
+## PausableTester
-### TAC_M_BTC_PAUSE_OPERATOR_ROLE
+### setContractAdminRole
```solidity
-bytes32 TAC_M_BTC_PAUSE_OPERATOR_ROLE
+function setContractAdminRole(bytes32 role) external
```
-actor that can pause TACmBTC
-
### initialize
```solidity
function initialize(address _accessControl) external
```
-upgradeable pattern contract`s initializer
-
-#### Parameters
+### requireFnNotPaused
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _accessControl | address | address of MidasAccessControll contract |
+```solidity
+function requireFnNotPaused(bytes4 fn) external
+```
-### _minterRole
+### requireNotPaused
```solidity
-function _minterRole() internal pure returns (bytes32)
+function requireNotPaused(bytes4 fn) external
```
-_AC role, owner of which can mint TACmBTC token_
-
-### _burnerRole
+### contractAdminRole
```solidity
-function _burnerRole() internal pure returns (bytes32)
+function contractAdminRole() public view returns (bytes32)
```
-_AC role, owner of which can burn TACmBTC token_
+_main admin role for the contract_
-### _pauserRole
+### _disableInitializers
```solidity
-function _pauserRole() internal pure returns (bytes32)
+function _disableInitializers() internal
```
-_AC role, owner of which can pause TACmBTC token_
-
-## MBasisCustomAggregatorFeed
+_Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
+Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
+to any version. It is recommended to use this to lock implementation contracts that are designed to be called
+through proxies.
-AggregatorV3 compatible feed for mBASIS,
-where price is submitted manually by feed admins
+Emits an {Initialized} event the first time it is successfully executed._
-### feedAdminRole
+### _onlyProxyAdmin
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function _onlyProxyAdmin() internal view
```
-_describes a role, owner of which can update prices in this feed_
-
-#### Return Values
+function to check if the sender is the proxy admin
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+## RedemptionVaultTestBase
-## MBasisDataFeed
+### _disableInitializers
-DataFeed for mBASIS product
+```solidity
+function _disableInitializers() internal virtual
+```
-### feedAdminRole
+### calcAndValidateRedeemTest
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function calcAndValidateRedeemTest(address user, address tokenOut, uint256 amountMTokenIn, uint256 overrideMTokenRate, uint256 overrideTokenOutRate, bool shouldOverrideFeePercent, uint256 overrideFeePercent, bool isInstant) external returns (struct RedemptionVault.CalcAndValidateRedeemResult calcResult)
```
-_describes a role, owner of which can manage this feed_
+### calculateHoldbackPartRateFromAvgTest
-#### Return Values
+```solidity
+function calculateHoldbackPartRateFromAvgTest(uint256 amountMToken, uint256 amountMTokenInstant, uint256 mTokenRate, uint256 avgMTokenRate) external pure returns (uint256)
+```
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+### convertUsdToTokenTest
-## mBASIS
+```solidity
+function convertUsdToTokenTest(uint256 amountUsd, address tokenOut, uint256 overrideTokenOutRate) external view returns (uint256 amountToken, uint256 tokenRate)
+```
-### M_BASIS_MINT_OPERATOR_ROLE
+### convertMTokenToUsdTest
```solidity
-bytes32 M_BASIS_MINT_OPERATOR_ROLE
+function convertMTokenToUsdTest(uint256 amountMToken, uint256 overrideMTokenRate) external view returns (uint256 amountUsd, uint256 mTokenRate)
```
-actor that can mint mBASIS
-
-### M_BASIS_BURN_OPERATOR_ROLE
+### _getTokenRate
```solidity
-bytes32 M_BASIS_BURN_OPERATOR_ROLE
+function _getTokenRate(address dataFeed, bool stable) internal view virtual returns (uint256)
```
-actor that can burn mBASIS
-
-### M_BASIS_PAUSE_OPERATOR_ROLE
+### contractAdminRole
```solidity
-bytes32 M_BASIS_PAUSE_OPERATOR_ROLE
+function contractAdminRole() public view virtual returns (bytes32)
```
-actor that can pause mBASIS
+## RedemptionVaultTest
-### initialize
+### constructor
```solidity
-function initialize(address _accessControl) external
+constructor() public
```
-upgradeable pattern contract`s initializer
+## RedemptionVaultWithAaveTest
-#### Parameters
+### constructor
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _accessControl | address | address of MidasAccessControll contract |
+```solidity
+constructor() public
+```
-### _minterRole
+### _disableInitializers
```solidity
-function _minterRole() internal pure returns (bytes32)
+function _disableInitializers() internal virtual
```
-_AC role, owner of which can mint mBASIS token_
-
-### _burnerRole
+### checkAndRedeemAave
```solidity
-function _burnerRole() internal pure returns (bytes32)
+function checkAndRedeemAave(address token, uint256 amount) external returns (uint256)
```
-_AC role, owner of which can burn mBASIS token_
-
-### _pauserRole
+### _obtainVaultLiquidity
```solidity
-function _pauserRole() internal pure returns (bytes32)
+function _obtainVaultLiquidity(address tokenOut, uint256 amountTokenOutBase18, uint256 tokenOutRate, uint256 currentTokenOutBalanceBase18, uint256 tokenOutDecimals) internal returns (uint256)
```
-_AC role, owner of which can pause mBASIS token_
-
-## MEdgeCustomAggregatorFeed
+### _getTokenRate
-AggregatorV3 compatible feed for mEDGE,
-where price is submitted manually by feed admins
+```solidity
+function _getTokenRate(address dataFeed, bool stable) internal view returns (uint256)
+```
-### feedAdminRole
+### contractAdminRole
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function contractAdminRole() public view returns (bytes32)
```
-_describes a role, owner of which can update prices in this feed_
+## RedemptionVaultWithMTokenTest
-#### Return Values
+### constructor
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+```solidity
+constructor() public
+```
-## MEdgeDataFeed
+### _disableInitializers
-DataFeed for mEDGE product
+```solidity
+function _disableInitializers() internal virtual
+```
-### feedAdminRole
+### checkAndRedeemMToken
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function checkAndRedeemMToken(address token, uint256 amount, uint256 rate) external returns (uint256)
```
-_describes a role, owner of which can manage this feed_
+### _obtainVaultLiquidity
-#### Return Values
+```solidity
+function _obtainVaultLiquidity(address token, uint256 amountTokenOutBase18, uint256 tokenOutRate, uint256 currentTokenOutBalanceBase18, uint256 tokenOutDecimals) internal returns (uint256)
+```
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+### _getTokenRate
-## mEDGE
+```solidity
+function _getTokenRate(address dataFeed, bool stable) internal view returns (uint256)
+```
-### M_EDGE_MINT_OPERATOR_ROLE
+### contractAdminRole
```solidity
-bytes32 M_EDGE_MINT_OPERATOR_ROLE
+function contractAdminRole() public view returns (bytes32)
```
-actor that can mint mEDGE
+## RedemptionVaultWithMorphoTest
-### M_EDGE_BURN_OPERATOR_ROLE
+### constructor
```solidity
-bytes32 M_EDGE_BURN_OPERATOR_ROLE
+constructor() public
```
-actor that can burn mEDGE
+### _disableInitializers
+
+```solidity
+function _disableInitializers() internal virtual
+```
-### M_EDGE_PAUSE_OPERATOR_ROLE
+### checkAndRedeemMorpho
```solidity
-bytes32 M_EDGE_PAUSE_OPERATOR_ROLE
+function checkAndRedeemMorpho(address token, uint256 amount) external returns (uint256)
```
-actor that can pause mEDGE
+### _obtainVaultLiquidity
-### initialize
+```solidity
+function _obtainVaultLiquidity(address token, uint256 amountTokenOutBase18, uint256 tokenOutRate, uint256 currentTokenOutBalanceBase18, uint256 tokenOutDecimals) internal returns (uint256)
+```
+
+### _getTokenRate
```solidity
-function initialize(address _accessControl) external
+function _getTokenRate(address dataFeed, bool stable) internal view returns (uint256)
```
-upgradeable pattern contract`s initializer
+### contractAdminRole
-#### Parameters
+```solidity
+function contractAdminRole() public view returns (bytes32)
+```
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _accessControl | address | address of MidasAccessControll contract |
+## RedemptionVaultWithUSTBTest
-### _minterRole
+### constructor
```solidity
-function _minterRole() internal pure returns (bytes32)
+constructor() public
```
-_AC role, owner of which can mint mEDGE token_
-
-### _burnerRole
+### _disableInitializers
```solidity
-function _burnerRole() internal pure returns (bytes32)
+function _disableInitializers() internal virtual
```
-_AC role, owner of which can burn mEDGE token_
+### checkAndRedeemUSTB
+
+```solidity
+function checkAndRedeemUSTB(address token, uint256 amount) external returns (uint256)
+```
-### _pauserRole
+### _obtainVaultLiquidity
```solidity
-function _pauserRole() internal pure returns (bytes32)
+function _obtainVaultLiquidity(address tokenOut, uint256 amountTokenOutBase18, uint256 tokenOutRate, uint256 currentTokenOutBalanceBase18, uint256 tokenOutDecimals) internal returns (uint256)
```
-_AC role, owner of which can pause mEDGE token_
+### _getTokenRate
-## TACmEDGE
+```solidity
+function _getTokenRate(address dataFeed, bool stable) internal view returns (uint256)
+```
-### TAC_M_EDGE_MINT_OPERATOR_ROLE
+### contractAdminRole
```solidity
-bytes32 TAC_M_EDGE_MINT_OPERATOR_ROLE
+function contractAdminRole() public view returns (bytes32)
```
-actor that can mint TACmEDGE
+## WithMidasAccessControlTester
-### TAC_M_EDGE_BURN_OPERATOR_ROLE
+### WrongRolePreflightSucceeded
```solidity
-bytes32 TAC_M_EDGE_BURN_OPERATOR_ROLE
+error WrongRolePreflightSucceeded(bytes32 role, uint32 overrideDelay, bool roleIsFunctionOperator, bool validateFunctionRole)
```
-actor that can burn TACmEDGE
+copy of `RolePreflightSucceeded` with a different name for testing
-### TAC_M_EDGE_PAUSE_OPERATOR_ROLE
+### setContractAdminRole
```solidity
-bytes32 TAC_M_EDGE_PAUSE_OPERATOR_ROLE
+function setContractAdminRole(bytes32 role) external
```
-actor that can pause TACmEDGE
-
### initialize
```solidity
function initialize(address _accessControl) external
```
-upgradeable pattern contract`s initializer
-
-#### Parameters
+### initializeWithoutInitializer
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _accessControl | address | address of MidasAccessControll contract |
+```solidity
+function initializeWithoutInitializer(address _accessControl) external
+```
-### _minterRole
+### withOnlyRole
```solidity
-function _minterRole() internal pure returns (bytes32)
+function withOnlyRole(bytes32 role, bool validateFunctionRole) external
```
-_AC role, owner of which can mint TACmEDGE token_
-
-### _burnerRole
+### withOnlyRoleNoTimelock
```solidity
-function _burnerRole() internal pure returns (bytes32)
+function withOnlyRoleNoTimelock(bytes32 role, bool validateFunctionRole) external
```
-_AC role, owner of which can burn TACmEDGE token_
-
-### _pauserRole
+### withOnlyContractAdmin
```solidity
-function _pauserRole() internal pure returns (bytes32)
+function withOnlyContractAdmin() external
```
-_AC role, owner of which can pause TACmEDGE token_
+### withUnprotected
+
+```solidity
+function withUnprotected() external
+```
-## MFOneCustomAggregatorFeed
+### withWrongRolePreflight
-AggregatorV3 compatible feed for mF-ONE,
-where price is submitted manually by feed admins
+```solidity
+function withWrongRolePreflight(bytes32 role, uint32 overrideDelay, bool roleIsFunctionOperator, bool validateFunctionRole) external pure
+```
-### feedAdminRole
+### contractAdminRole
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function contractAdminRole() public view returns (bytes32)
```
-_describes a role, owner of which can update prices in this feed_
+_main admin role for the contract_
-#### Return Values
+### _disableInitializers
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+```solidity
+function _disableInitializers() internal
+```
-## MFOneDataFeed
+_Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
+Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
+to any version. It is recommended to use this to lock implementation contracts that are designed to be called
+through proxies.
-DataFeed for mF-ONE product
+Emits an {Initialized} event the first time it is successfully executed._
-### feedAdminRole
+### _onlyProxyAdmin
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function _onlyProxyAdmin() internal view
```
-_describes a role, owner of which can manage this feed_
+function to check if the sender is the proxy admin
-#### Return Values
+## WithSanctionsListTester
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+### initialize
-## mFONE
+```solidity
+function initialize(address _accessControl, address _sanctionsList) external
+```
-### M_FONE_MINT_OPERATOR_ROLE
+### initializeUnchainedWithoutInitializer
```solidity
-bytes32 M_FONE_MINT_OPERATOR_ROLE
+function initializeUnchainedWithoutInitializer(address _sanctionsList) external
```
-actor that can mint mF-ONE
-
-### M_FONE_BURN_OPERATOR_ROLE
+### onlyNotSanctionedTester
```solidity
-bytes32 M_FONE_BURN_OPERATOR_ROLE
+function onlyNotSanctionedTester(address user) public
```
-actor that can burn mF-ONE
+### sanctionsListAdminRole
+
+```solidity
+function sanctionsListAdminRole() public pure returns (bytes32)
+```
-### M_FONE_PAUSE_OPERATOR_ROLE
+### contractAdminRole
```solidity
-bytes32 M_FONE_PAUSE_OPERATOR_ROLE
+function contractAdminRole() public pure returns (bytes32)
```
-actor that can pause mF-ONE
+_main admin role for the contract_
-### initialize
+### _disableInitializers
```solidity
-function initialize(address _accessControl) external
+function _disableInitializers() internal
```
-upgradeable pattern contract`s initializer
-
-#### Parameters
+_Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
+Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
+to any version. It is recommended to use this to lock implementation contracts that are designed to be called
+through proxies.
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _accessControl | address | address of MidasAccessControl contract |
+Emits an {Initialized} event the first time it is successfully executed._
-### _minterRole
+### _onlyProxyAdmin
```solidity
-function _minterRole() internal pure returns (bytes32)
+function _onlyProxyAdmin() internal view
```
-_AC role, owner of which can mint mF-ONE token_
+function to check if the sender is the proxy admin
-### _burnerRole
+## mTokenTest
+
+### constructor
```solidity
-function _burnerRole() internal pure returns (bytes32)
+constructor(bytes32 _managerRole, bytes32 _mintOperatorRole, bytes32 _burnOperatorRole, bytes32 _greenlistedRole, bytes32 _minBalanceExemptRole) public
```
-_AC role, owner of which can burn mF-ONE token_
-
-### _pauserRole
+### _disableInitializers
```solidity
-function _pauserRole() internal pure returns (bytes32)
+function _disableInitializers() internal
```
-_AC role, owner of which can pause mF-ONE token_
-
-## MLiquidityCustomAggregatorFeed
+_Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
+Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
+to any version. It is recommended to use this to lock implementation contracts that are designed to be called
+through proxies.
-AggregatorV3 compatible feed for mLIQUIDITY,
-where price is submitted manually by feed admins
+Emits an {Initialized} event the first time it is successfully executed._
-### feedAdminRole
+### _onlyProxyAdmin
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function _onlyProxyAdmin() internal view
```
-_describes a role, owner of which can update prices in this feed_
+function to check if the sender is the proxy admin
-#### Return Values
+## MidasAccessControlTimelockController
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+TimelockController for Midas Protocol that is controlled by MidasTimelockManager
-## MLiquidityDataFeed
+### timelockManager
+
+```solidity
+address timelockManager
+```
-DataFeed for mLIQUIDITY product
+address of MidasTimelockManager contract
-### feedAdminRole
+### initialize
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function initialize(address _timelockManager) external
```
-_describes a role, owner of which can manage this feed_
+upgradeable pattern contract`s initializer
-#### Return Values
+#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+| _timelockManager | address | address of MidasTimelockManager contract |
+
+## MidasTimelockController
-## mLIQUIDITY
+Default TimelockController but with getters for proposers and executors
-### M_LIQUIDITY_MINT_OPERATOR_ROLE
+### constructor
```solidity
-bytes32 M_LIQUIDITY_MINT_OPERATOR_ROLE
+constructor(uint256 minDelay, address[] proposers, address[] executors) public
```
-actor that can mint mLIQUIDITY
-
-### M_LIQUIDITY_BURN_OPERATOR_ROLE
+### getInitialProposers
```solidity
-bytes32 M_LIQUIDITY_BURN_OPERATOR_ROLE
+function getInitialProposers() external view returns (address[])
```
-actor that can burn mLIQUIDITY
+Get all the initial proposers
-### M_LIQUIDITY_PAUSE_OPERATOR_ROLE
-
-```solidity
-bytes32 M_LIQUIDITY_PAUSE_OPERATOR_ROLE
-```
+#### Return Values
-actor that can pause mLIQUIDITY
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | address[] | initial proposers addresses |
-### initialize
+### getInitialExecutors
```solidity
-function initialize(address _accessControl) external
+function getInitialExecutors() external view returns (address[])
```
-upgradeable pattern contract`s initializer
+Get all the initial executors
-#### Parameters
+#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| _accessControl | address | address of MidasAccessControll contract |
-
-### _minterRole
-
-```solidity
-function _minterRole() internal pure returns (bytes32)
-```
+| [0] | address[] | initial executors addresses |
-_AC role, owner of which can mint mLIQUIDITY token_
+## CustomAggregatorV3CompatibleFeedAdjusted
-### _burnerRole
+AggregatorV3 compatible proxy-feed that adjusts the price
+of an underlying chainlink compatible feed by a given signed percentage.
+Positive adjustmentPercentage raises the reported price.
+Negative adjustmentPercentage lowers the reported price.
+
+### underlyingFeed
```solidity
-function _burnerRole() internal pure returns (bytes32)
+contract AggregatorV3Interface underlyingFeed
```
-_AC role, owner of which can burn mLIQUIDITY token_
+the underlying chainlink compatible feed
-### _pauserRole
+### adjustmentPercentage
```solidity
-function _pauserRole() internal pure returns (bytes32)
+int256 adjustmentPercentage
```
-_AC role, owner of which can pause mLIQUIDITY token_
+the adjustment percentage (signed).
+Expressed in 10 ** decimals() precision.
+Example: 10 ** decimals() = 1%, -(10 ** decimals()) = -1%
+Positive values raise the reported price.
+Negative values lower the reported price.
-## MMevCustomAggregatorFeed
-
-AggregatorV3 compatible feed for mMEV,
-where price is submitted manually by feed admins
-
-### feedAdminRole
+### constructor
```solidity
-function feedAdminRole() public pure returns (bytes32)
+constructor(address _underlyingFeed, int256 _adjustmentPercentage) public
```
-_describes a role, owner of which can update prices in this feed_
+constructor
-#### Return Values
+#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
-
-## MMevDataFeed
-
-DataFeed for mMEV product
+| _underlyingFeed | address | the underlying chainlink compatible feed |
+| _adjustmentPercentage | int256 | signed adjustment percentage in 10 ** decimals() precision |
-### feedAdminRole
+### latestRoundData
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
```
-_describes a role, owner of which can manage this feed_
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+### version
-## mMEV
+```solidity
+function version() external view returns (uint256)
+```
-### M_MEV_MINT_OPERATOR_ROLE
+### getRoundData
```solidity
-bytes32 M_MEV_MINT_OPERATOR_ROLE
+function getRoundData(uint80 _roundId) public view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
```
-actor that can mint mMEV
-
-### M_MEV_BURN_OPERATOR_ROLE
+### decimals
```solidity
-bytes32 M_MEV_BURN_OPERATOR_ROLE
+function decimals() public view returns (uint8)
```
-actor that can burn mMEV
-
-### M_MEV_PAUSE_OPERATOR_ROLE
+### description
```solidity
-bytes32 M_MEV_PAUSE_OPERATOR_ROLE
+function description() public view returns (string)
```
-actor that can pause mMEV
-
-### initialize
+### _calculateAdjustedAnswer
```solidity
-function initialize(address _accessControl) external
+function _calculateAdjustedAnswer(int256 _answer) internal view returns (int256)
```
-upgradeable pattern contract`s initializer
+_calculates the adjusted answer_
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| _accessControl | address | address of MidasAccessControll contract |
+| _answer | int256 | the answer to adjust |
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | int256 | the adjusted answer |
+
+## IAllowListV2
-### _minterRole
+### EntityId
+
+### FundPermissionSet
```solidity
-function _minterRole() internal pure returns (bytes32)
+event FundPermissionSet(IAllowListV2.EntityId entityId, string fundSymbol, bool permission)
```
-_AC role, owner of which can mint mMEV token_
+An event emitted when an address's permission is changed for a fund.
-### _burnerRole
+### ProtocolAddressPermissionSet
```solidity
-function _burnerRole() internal pure returns (bytes32)
+event ProtocolAddressPermissionSet(address addr, string fundSymbol, bool isAllowed)
```
-_AC role, owner of which can burn mMEV token_
+An event emitted when a protocol's permission is changed for a fund.
-### _pauserRole
+### EntityIdSet
```solidity
-function _pauserRole() internal pure returns (bytes32)
+event EntityIdSet(address addr, uint256 entityId)
```
-_AC role, owner of which can pause mMEV token_
-
-## TACmMEV
+An event emitted when an address is associated with an entityId
-### TAC_M_MEV_MINT_OPERATOR_ROLE
+### BadData
```solidity
-bytes32 TAC_M_MEV_MINT_OPERATOR_ROLE
+error BadData()
```
-actor that can mint TACmMEV
+_Thrown when the input for a function is invalid_
-### TAC_M_MEV_BURN_OPERATOR_ROLE
+### AlreadySet
```solidity
-bytes32 TAC_M_MEV_BURN_OPERATOR_ROLE
+error AlreadySet()
```
-actor that can burn TACmMEV
+_Thrown when the input is already equivalent to the storage being set_
-### TAC_M_MEV_PAUSE_OPERATOR_ROLE
+### NonZeroEntityIdMustBeChangedToZero
```solidity
-bytes32 TAC_M_MEV_PAUSE_OPERATOR_ROLE
+error NonZeroEntityIdMustBeChangedToZero()
```
-actor that can pause TACmMEV
+_An address's entityId can not be changed once set, it can only be unset and then set to a new value_
-### initialize
+### AddressHasProtocolPermissions
```solidity
-function initialize(address _accessControl) external
+error AddressHasProtocolPermissions()
```
-upgradeable pattern contract`s initializer
+_Thrown when trying to set entityId for an address that has protocol permissions_
-#### Parameters
+### AddressHasEntityId
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _accessControl | address | address of MidasAccessControll contract |
+```solidity
+error AddressHasEntityId()
+```
+
+_Thrown when trying to set protocol permissions for an address that has an entityId_
-### _minterRole
+### CodeSizeZero
```solidity
-function _minterRole() internal pure returns (bytes32)
+error CodeSizeZero()
```
-_AC role, owner of which can mint TACmMEV token_
+_Thrown when trying to set protocol permissions but the code size is 0_
-### _burnerRole
+### Deprecated
```solidity
-function _burnerRole() internal pure returns (bytes32)
+error Deprecated()
```
-_AC role, owner of which can burn TACmMEV token_
+_Thrown when a method is no longer supported_
-### _pauserRole
+### RenounceOwnershipDisabled
```solidity
-function _pauserRole() internal pure returns (bytes32)
+error RenounceOwnershipDisabled()
```
-_AC role, owner of which can pause TACmMEV token_
+_Thrown if an attempt to call `renounceOwnership` is made_
-## MRe7CustomAggregatorFeed
+### owner
-AggregatorV3 compatible feed for mRE7,
-where price is submitted manually by feed admins
+```solidity
+function owner() external view returns (address)
+```
-### feedAdminRole
+### addressEntityIds
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function addressEntityIds(address addr) external view returns (IAllowListV2.EntityId)
```
-_describes a role, owner of which can update prices in this feed_
+Gets the entityId for the provided address
-#### Return Values
+#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
-
-## MRe7DataFeed
-
-DataFeed for mRE7 product
+| addr | address | The address to get the entityId for |
-### feedAdminRole
+### isAddressAllowedForFund
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function isAddressAllowedForFund(address addr, string fundSymbol) external view returns (bool)
```
-_describes a role, owner of which can manage this feed_
+Checks whether an address is allowed to use a fund
-#### Return Values
+#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
-
-## mRE7
+| addr | address | The address to check permissions for |
+| fundSymbol | string | The fund symbol to check permissions for |
-### M_RE7_MINT_OPERATOR_ROLE
+### isEntityAllowedForFund
```solidity
-bytes32 M_RE7_MINT_OPERATOR_ROLE
+function isEntityAllowedForFund(IAllowListV2.EntityId entityId, string fundSymbol) external view returns (bool)
```
-actor that can mint mRE7
-
-### M_RE7_BURN_OPERATOR_ROLE
+Checks whether an Entity is allowed to use a fund
-```solidity
-bytes32 M_RE7_BURN_OPERATOR_ROLE
-```
+#### Parameters
-actor that can burn mRE7
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| entityId | IAllowListV2.EntityId | |
+| fundSymbol | string | The fund symbol to check permissions for |
-### M_RE7_PAUSE_OPERATOR_ROLE
+### setEntityAllowedForFund
```solidity
-bytes32 M_RE7_PAUSE_OPERATOR_ROLE
+function setEntityAllowedForFund(IAllowListV2.EntityId entityId, string fundSymbol, bool isAllowed) external
```
-actor that can pause mRE7
+Sets whether an Entity is allowed to use a fund
+
+#### Parameters
-### initialize
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| entityId | IAllowListV2.EntityId | |
+| fundSymbol | string | The fund symbol to set permissions for |
+| isAllowed | bool | The permission value to set |
+
+### setEntityIdForAddress
```solidity
-function initialize(address _accessControl) external
+function setEntityIdForAddress(IAllowListV2.EntityId entityId, address addr) external
```
-upgradeable pattern contract`s initializer
+Sets the entityId for a given address. Setting to 0 removes the address from the allowList
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| _accessControl | address | address of MidasAccessControll contract |
+| entityId | IAllowListV2.EntityId | The entityId to associate with an address |
+| addr | address | The address to associate with an entityId |
-### _minterRole
+### setEntityIdForMultipleAddresses
```solidity
-function _minterRole() internal pure returns (bytes32)
+function setEntityIdForMultipleAddresses(IAllowListV2.EntityId entityId, address[] addresses) external
```
-_AC role, owner of which can mint mRE7 token_
-
-### _burnerRole
+Sets the entity Id for a list of addresses. Setting to 0 removes the address from the allowList
-```solidity
-function _burnerRole() internal pure returns (bytes32)
-```
+#### Parameters
-_AC role, owner of which can burn mRE7 token_
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| entityId | IAllowListV2.EntityId | The entityId to associate with an address |
+| addresses | address[] | The addresses to associate with an entityId |
-### _pauserRole
+### setProtocolAddressPermission
```solidity
-function _pauserRole() internal pure returns (bytes32)
+function setProtocolAddressPermission(address addr, string fundSymbol, bool isAllowed) external
```
-_AC role, owner of which can pause mRE7 token_
+Sets protocol permissions for an address
-## MRe7SolCustomAggregatorFeed
+#### Parameters
-AggregatorV3 compatible feed for mRE7SOL,
-where price is submitted manually by feed admins
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| addr | address | The address to set permissions for |
+| fundSymbol | string | The fund symbol to set permissions for |
+| isAllowed | bool | The permission value to set |
-### feedAdminRole
+### setProtocolAddressPermissions
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function setProtocolAddressPermissions(address[] addresses, string fundSymbol, bool isAllowed) external
```
-_describes a role, owner of which can update prices in this feed_
+Sets protocol permissions for multiple addresses
-#### Return Values
+#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
-
-## MRe7SolDataFeed
-
-DataFeed for mRE7SOL product
+| addresses | address[] | The addresses to set permissions for |
+| fundSymbol | string | The fund symbol to set permissions for |
+| isAllowed | bool | The permission value to set |
-### feedAdminRole
+### setEntityPermissionsAndAddresses
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function setEntityPermissionsAndAddresses(IAllowListV2.EntityId entityId, address[] addresses, string[] fundPermissionsToUpdate, bool[] fundPermissions) external
```
-_describes a role, owner of which can manage this feed_
+Sets entity for an array of addresses and sets permissions for an entity
-#### Return Values
+#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
-
-## mRE7SOL
+| entityId | IAllowListV2.EntityId | The entityId to be updated |
+| addresses | address[] | The addresses to associate with an entityId |
+| fundPermissionsToUpdate | string[] | The funds to update permissions for |
+| fundPermissions | bool[] | The permissions for each fund |
-### M_RE7SOL_MINT_OPERATOR_ROLE
+### hasAnyProtocolPermissions
```solidity
-bytes32 M_RE7SOL_MINT_OPERATOR_ROLE
+function hasAnyProtocolPermissions(address addr) external view returns (bool hasPermissions)
```
-actor that can mint mRE7SOL
-
-### M_RE7SOL_BURN_OPERATOR_ROLE
+### protocolPermissionsForFunds
```solidity
-bytes32 M_RE7SOL_BURN_OPERATOR_ROLE
+function protocolPermissionsForFunds(address protocol) external view returns (uint256)
```
-actor that can burn mRE7SOL
-
-### M_RE7SOL_PAUSE_OPERATOR_ROLE
+### protocolPermissions
```solidity
-bytes32 M_RE7SOL_PAUSE_OPERATOR_ROLE
+function protocolPermissions(address, string) external view returns (bool)
```
-actor that can pause mRE7SOL
-
### initialize
```solidity
-function initialize(address _accessControl) external
+function initialize() external
```
-upgradeable pattern contract`s initializer
-
-#### Parameters
+## IStdReference
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _accessControl | address | address of MidasAccessControll contract |
+### ReferenceData
-### _minterRole
+A structure returned whenever someone requests for standard reference data.
```solidity
-function _minterRole() internal pure returns (bytes32)
+struct ReferenceData {
+ uint256 rate;
+ uint256 lastUpdatedBase;
+ uint256 lastUpdatedQuote;
+}
```
-_AC role, owner of which can mint mRE7SOL token_
-
-### _burnerRole
+### getReferenceData
```solidity
-function _burnerRole() internal pure returns (bytes32)
+function getReferenceData(string _base, string _quote) external view returns (struct IStdReference.ReferenceData)
```
-_AC role, owner of which can burn mRE7SOL token_
+Returns the price data for the given base/quote pair. Revert if not available.
-### _pauserRole
+### getReferenceDataBulk
```solidity
-function _pauserRole() internal pure returns (bytes32)
+function getReferenceDataBulk(string[] _bases, string[] _quotes) external view returns (struct IStdReference.ReferenceData[])
```
-_AC role, owner of which can pause mRE7SOL token_
-
-## MSlCustomAggregatorFeed
+Similar to getReferenceData, but with multiple base/quote pairs at once.
-AggregatorV3 compatible feed for mSL,
-where price is submitted manually by feed admins
+## BandStdChailinkAdapter
-### feedAdminRole
+### ref
```solidity
-function feedAdminRole() public pure returns (bytes32)
+contract IStdReference ref
```
-_describes a role, owner of which can update prices in this feed_
-
-#### Return Values
+### base
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+```solidity
+string base
+```
-## MSlDataFeed
+### quote
-DataFeed for mSL product
+```solidity
+string quote
+```
-### feedAdminRole
+### constructor
```solidity
-function feedAdminRole() public pure returns (bytes32)
+constructor(address _ref, string _base, string _quote) public
```
-_describes a role, owner of which can manage this feed_
+### description
-#### Return Values
+```solidity
+function description() external pure returns (string)
+```
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+### latestAnswer
-## mSL
+```solidity
+function latestAnswer() public view returns (int256)
+```
-### M_SL_MINT_OPERATOR_ROLE
+### latestTimestamp
```solidity
-bytes32 M_SL_MINT_OPERATOR_ROLE
+function latestTimestamp() public view returns (uint256)
```
-actor that can mint mSL
-
-### M_SL_BURN_OPERATOR_ROLE
+### latestRoundData
```solidity
-bytes32 M_SL_BURN_OPERATOR_ROLE
+function latestRoundData() external view returns (uint80, int256, uint256, uint256, uint80)
```
-actor that can burn mSL
+## IBeHype
-### M_SL_PAUSE_OPERATOR_ROLE
+### BeHYPEToHYPE
```solidity
-bytes32 M_SL_PAUSE_OPERATOR_ROLE
+function BeHYPEToHYPE(uint256 beHYPEAmount) external view returns (uint256)
```
-actor that can pause mSL
+## BeHypeChainlinkAdapter
-### initialize
+Adapter for beHYPE LST from hyperbeat for liquidHYPE redemptions
+
+### beHype
```solidity
-function initialize(address _accessControl) external
+contract IBeHype beHype
```
-upgradeable pattern contract`s initializer
-
-#### Parameters
+### constructor
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _accessControl | address | address of MidasAccessControll contract |
+```solidity
+constructor(address _beHype) public
+```
-### _minterRole
+### description
```solidity
-function _minterRole() internal pure returns (bytes32)
+function description() external pure returns (string)
```
-_AC role, owner of which can mint mSL token_
-
-### _burnerRole
+### latestAnswer
```solidity
-function _burnerRole() internal pure returns (bytes32)
+function latestAnswer() public view returns (int256)
```
-_AC role, owner of which can burn mSL token_
+## ChainlinkAdapterBase
-### _pauserRole
+### decimals
```solidity
-function _pauserRole() internal pure returns (bytes32)
+function decimals() public view virtual returns (uint8)
```
-_AC role, owner of which can pause mSL token_
-
-## MTBillCustomAggregatorFeed
+### description
-AggregatorV3 compatible feed for mTBILL,
-where price is submitted manually by feed admins
+```solidity
+function description() external pure virtual returns (string)
+```
-### feedAdminRole
+### version
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function version() external view virtual returns (uint256)
```
-_describes a role, owner of which can update prices in this feed_
-
-#### Return Values
+### latestTimestamp
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+```solidity
+function latestTimestamp() public view virtual returns (uint256)
+```
-## MTBillDataFeed
+### latestRound
-DataFeed for mBASIS product
+```solidity
+function latestRound() public view virtual returns (uint256)
+```
-### feedAdminRole
+### latestAnswer
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function latestAnswer() public view virtual returns (int256)
```
-_describes a role, owner of which can manage this feed_
+### getAnswer
-#### Return Values
+```solidity
+function getAnswer(uint256) public pure virtual returns (int256)
+```
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+### getTimestamp
+
+```solidity
+function getTimestamp(uint256) external pure virtual returns (uint256)
+```
-## MTBillMidasAccessControlRoles
+### getRoundData
-Base contract that stores all roles descriptors for mBASIS contracts
+```solidity
+function getRoundData(uint80) external view virtual returns (uint80, int256, uint256, uint256, uint80)
+```
-### M_TBILL_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+### latestRoundData
```solidity
-bytes32 M_TBILL_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+function latestRoundData() external view virtual returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
```
-actor that can manage MTBillCustomAggregatorFeed and MTBillDataFeed
+## ERC4626ChainlinkAdapter
-## mTBILL
+_uses convertToAssets for the answer_
-### metadata
+### vault
```solidity
-mapping(bytes32 => bytes) metadata
+address vault
```
-metadata key => metadata value
+erc4626 vault
-### initialize
+### constructor
```solidity
-function initialize(address _accessControl) external virtual
+constructor(address _vault) public
```
-upgradeable pattern contract`s initializer
+_constructor_
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
-| _accessControl | address | address of MidasAccessControll contract |
+| _vault | address | erc4626 vault address |
-### mint
+### description
```solidity
-function mint(address to, uint256 amount) external
+function description() external pure virtual returns (string)
```
-mints mTBILL token `amount` to a given `to` address.
-should be called only from permissioned actor
-
-#### Parameters
+### decimals
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| to | address | addres to mint tokens to |
-| amount | uint256 | amount to mint |
+```solidity
+function decimals() public view returns (uint8)
+```
-### burn
+### vaultDecimals
```solidity
-function burn(address from, uint256 amount) external
+function vaultDecimals() public view returns (uint8)
```
-burns mTBILL token `amount` to a given `to` address.
-should be called only from permissioned actor
+### latestAnswer
-#### Parameters
+```solidity
+function latestAnswer() public view virtual returns (int256)
+```
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| from | address | addres to burn tokens from |
-| amount | uint256 | amount to burn |
+## IMantleLspStaking
-### pause
+### mETHToETH
```solidity
-function pause() external
+function mETHToETH(uint256 mETHAmount) external view returns (uint256)
```
-puts mTBILL token on pause.
-should be called only from permissioned actor
+## MantleLspStakingChainlinkAdapter
+
+example https://etherscan.io/address/0xe3cBd06D7dadB3F4e6557bAb7EdD924CD1489E8f
-### unpause
+### lspStaking
```solidity
-function unpause() external
+contract IMantleLspStaking lspStaking
```
-puts mTBILL token on pause.
-should be called only from permissioned actor
-
-### setMetadata
+### constructor
```solidity
-function setMetadata(bytes32 key, bytes data) external
+constructor(address _lspStaking) public
```
-updates contract`s metadata.
-should be called only from permissioned actor
-
-#### Parameters
+### description
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| key | bytes32 | metadata map. key |
-| data | bytes | metadata map. value |
+```solidity
+function description() external pure returns (string)
+```
-### _beforeTokenTransfer
+### latestAnswer
```solidity
-function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual
+function latestAnswer() public view returns (int256)
```
-_overrides _beforeTokenTransfer function to ban
-blaclisted users from using the token functions_
+## PythStructs
-### _minterRole
+### Price
```solidity
-function _minterRole() internal pure virtual returns (bytes32)
+struct Price {
+ int64 price;
+ uint64 conf;
+ int32 expo;
+ uint256 publishTime;
+}
```
-_AC role, owner of which can mint mTBILL token_
+## IPyth
-### _burnerRole
+### getPriceUnsafe
```solidity
-function _burnerRole() internal pure virtual returns (bytes32)
+function getPriceUnsafe(bytes32 id) external view returns (struct PythStructs.Price price)
```
-_AC role, owner of which can burn mTBILL token_
-
-### _pauserRole
+### getUpdateFee
```solidity
-function _pauserRole() internal pure virtual returns (bytes32)
+function getUpdateFee(bytes[] updateData) external view returns (uint256 feeAmount)
```
-_AC role, owner of which can pause mTBILL token_
+### updatePriceFeeds
-## MevBtcCustomAggregatorFeed
+```solidity
+function updatePriceFeeds(bytes[] updateData) external payable
+```
-AggregatorV3 compatible feed for mevBTC,
-where price is submitted manually by feed admins
+## PythChainlinkAdapter
-### feedAdminRole
+### priceId
```solidity
-function feedAdminRole() public pure returns (bytes32)
+bytes32 priceId
```
-_describes a role, owner of which can update prices in this feed_
-
-#### Return Values
+### pyth
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+```solidity
+contract IPyth pyth
+```
-## MevBtcDataFeed
+### constructor
-DataFeed for mevBTC product
+```solidity
+constructor(address _pyth, bytes32 _priceId) public
+```
-### feedAdminRole
+### updateFeeds
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function updateFeeds(bytes[] priceUpdateData) public payable
```
-_describes a role, owner of which can manage this feed_
+### decimals
-#### Return Values
+```solidity
+function decimals() public view virtual returns (uint8)
+```
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+### description
-## mevBTC
+```solidity
+function description() external pure returns (string)
+```
-### MEV_BTC_MINT_OPERATOR_ROLE
+### latestAnswer
```solidity
-bytes32 MEV_BTC_MINT_OPERATOR_ROLE
+function latestAnswer() public view virtual returns (int256)
```
-actor that can mint mevBTC
-
-### MEV_BTC_BURN_OPERATOR_ROLE
+### latestTimestamp
```solidity
-bytes32 MEV_BTC_BURN_OPERATOR_ROLE
+function latestTimestamp() public view returns (uint256)
```
-actor that can burn mevBTC
-
-### MEV_BTC_PAUSE_OPERATOR_ROLE
+### latestRoundData
```solidity
-bytes32 MEV_BTC_PAUSE_OPERATOR_ROLE
+function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
```
-actor that can pause mevBTC
+## IRsEth
-### initialize
+### rsETHPrice
```solidity
-function initialize(address _accessControl) external
+function rsETHPrice() external view returns (uint256)
```
-upgradeable pattern contract`s initializer
-
-#### Parameters
+## RsEthChainlinkAdapter
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _accessControl | address | address of MidasAccessControll contract |
+example https://etherscan.io/address/0x349A73444b1a310BAe67ef67973022020d70020d
-### _minterRole
+### rsEth
```solidity
-function _minterRole() internal pure returns (bytes32)
+contract IRsEth rsEth
```
-_AC role, owner of which can mint mevBTC token_
-
-### _burnerRole
+### constructor
```solidity
-function _burnerRole() internal pure returns (bytes32)
+constructor(address _rsEth) public
```
-_AC role, owner of which can burn mevBTC token_
-
-### _pauserRole
+### description
```solidity
-function _pauserRole() internal pure returns (bytes32)
+function description() external pure returns (string)
```
-_AC role, owner of which can pause mevBTC token_
-
-## IStdReference
-
-### ReferenceData
+### latestAnswer
```solidity
-struct ReferenceData {
- uint256 rate;
- uint256 lastUpdatedBase;
- uint256 lastUpdatedQuote;
-}
+function latestAnswer() public view returns (int256)
```
-### getReferenceData
+## IStorkTemporalNumericValueUnsafeGetter
+
+### getTemporalNumericValueUnsafeV1
```solidity
-function getReferenceData(string _base, string _quote) external view returns (struct IStdReference.ReferenceData)
+function getTemporalNumericValueUnsafeV1(bytes32 id) external view returns (struct StorkStructs.TemporalNumericValue value)
```
-Returns the price data for the given base/quote pair. Revert if not available.
+## StorkStructs
-### getReferenceDataBulk
+### TemporalNumericValue
```solidity
-function getReferenceDataBulk(string[] _bases, string[] _quotes) external view returns (struct IStdReference.ReferenceData[])
+struct TemporalNumericValue {
+ uint64 timestampNs;
+ int192 quantizedValue;
+}
```
-Similar to getReferenceData, but with multiple base/quote pairs at once.
-
-## BandStdChailinkAdapter
+## StorkChainlinkAdapter
-### ref
+### TIMESTAMP_DIVIDER
```solidity
-contract IStdReference ref
+uint256 TIMESTAMP_DIVIDER
```
-### base
+### priceId
```solidity
-string base
+bytes32 priceId
```
-### quote
+### stork
```solidity
-string quote
+contract IStorkTemporalNumericValueUnsafeGetter stork
```
### constructor
```solidity
-constructor(address _ref, string _base, string _quote) public
+constructor(address _stork, bytes32 _priceId) public
```
-### decimals
+### description
```solidity
-function decimals() external pure returns (uint8)
+function description() external pure returns (string)
```
-### description
+### latestAnswer
```solidity
-function description() public pure returns (string)
+function latestAnswer() public view returns (int256)
```
-### version
+### latestTimestamp
```solidity
-function version() public pure returns (uint256)
+function latestTimestamp() public view returns (uint256)
```
-### latestAnswer
+### latestRoundData
```solidity
-function latestAnswer() public view virtual returns (int256)
+function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
```
-### latestTimestamp
+## ISyrupToken
+
+### convertToExitAssets
```solidity
-function latestTimestamp() public view returns (uint256)
+function convertToExitAssets(uint256 shares) external view returns (uint256)
```
-### latestRound
+## SyrupChainlinkAdapter
+
+example https://etherscan.io/address/0x80ac24aa929eaf5013f6436cda2a7ba190f5cc0b
+
+### constructor
```solidity
-function latestRound() public view returns (uint256)
+constructor(address _syrupToken) public
```
-### getAnswer
+### description
```solidity
-function getAnswer(uint256) public view returns (int256)
+function description() external pure returns (string)
```
-### getTimestamp
+### latestAnswer
```solidity
-function getTimestamp(uint256) external view returns (uint256)
+function latestAnswer() public view returns (int256)
```
-### getRoundData
+## IWrappedEEth
+
+### getRate
```solidity
-function getRoundData(uint80 _roundId) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
+function getRate() external view returns (uint256)
```
-### latestRoundData
+## WrappedEEthChainlinkAdapter
+
+example https://etherscan.io/address/0xcd5fe23c85820f7b72d0926fc9b05b43e359b7ee
+
+### wrappedEEth
```solidity
-function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
+contract IWrappedEEth wrappedEEth
```
-## IMantleLspStaking
-
-### mETHToETH
+### constructor
```solidity
-function mETHToETH(uint256 mETHAmount) external view returns (uint256)
+constructor(address _wrappedEEth) public
```
-## MantleLspStakingAdapter
+### description
-example https://etherscan.io/address/0xe3cBd06D7dadB3F4e6557bAb7EdD924CD1489E8f
+```solidity
+function description() external pure returns (string)
+```
-### lspStaking
+### latestAnswer
```solidity
-contract IMantleLspStaking lspStaking
+function latestAnswer() public view returns (int256)
```
-### constructor
+## IWstEth
+
+### stEthPerToken
```solidity
-constructor(address _lspStaking) public
+function stEthPerToken() external view returns (uint256)
```
-### decimals
+## WstEthChainlinkAdapter
+
+example https://etherscan.io/address/0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0
+
+### wstEth
```solidity
-function decimals() external pure returns (uint8)
+contract IWstEth wstEth
```
-### description
+### constructor
```solidity
-function description() public pure returns (string)
+constructor(address _wstEth) public
```
-### version
+### description
```solidity
-function version() public pure returns (uint256)
+function description() external pure returns (string)
```
### latestAnswer
```solidity
-function latestAnswer() public view virtual returns (int256)
+function latestAnswer() public view returns (int256)
```
-### latestTimestamp
+## IYInjOracle
+
+### getExchangeRate
```solidity
-function latestTimestamp() public view returns (uint256)
+function getExchangeRate() external view returns (uint256)
```
-### latestRound
+## YInjChainlinkAdapter
-```solidity
-function latestRound() public view returns (uint256)
-```
+Adapter for yINJ from injective for sLINJ redemptions
-### getAnswer
+### yInj
```solidity
-function getAnswer(uint256) public view returns (int256)
+contract IYInjOracle yInj
```
-### getTimestamp
+### constructor
```solidity
-function getTimestamp(uint256) external view returns (uint256)
+constructor(address _yINJ) public
```
-### getRoundData
+### description
```solidity
-function getRoundData(uint80) external view returns (uint80, int256, uint256, uint256, uint80)
+function description() external pure returns (string)
```
-### latestRoundData
+### latestAnswer
```solidity
-function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
+function latestAnswer() public view returns (int256)
```
-## PythChainlinkAdapter
+## MidasLzOFT
+
+OFT adapter implementation
### constructor
```solidity
-constructor(address _pyth, bytes32 _priceId) public
+constructor(string _name, string _symbol, uint8 __sharedDecimals, address _lzEndpoint, address _delegate) public
```
-## IRsEth
+constructor
-### rsETHPrice
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _name | string | name of the token |
+| _symbol | string | symbol of the token |
+| __sharedDecimals | uint8 | shared decimals for the OFT |
+| _lzEndpoint | address | address of the LayerZero endpoint |
+| _delegate | address | address of the delegate |
+
+### sharedDecimals
```solidity
-function rsETHPrice() external view returns (uint256)
+function sharedDecimals() public view returns (uint8)
```
-## RsEthAdapter
+Returns the shared decimals for the OFT
-example https://etherscan.io/address/0x349A73444b1a310BAe67ef67973022020d70020d
+#### Return Values
-### rsEth
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint8 | The shared decimals |
-```solidity
-contract IRsEth rsEth
-```
+## MidasLzOFTAdapter
+
+OFT adapter implementation
### constructor
```solidity
-constructor(address _rsEth) public
+constructor(address _token, uint8 __sharedDecimals, address _lzEndpoint, address _delegate) public
```
-### decimals
+constructor
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| _token | address | address of the token |
+| __sharedDecimals | uint8 | shared decimals for the OFT adapter |
+| _lzEndpoint | address | address of the LayerZero endpoint |
+| _delegate | address | address of the delegate |
+
+### sharedDecimals
```solidity
-function decimals() external pure returns (uint8)
+function sharedDecimals() public view returns (uint8)
```
-### description
+Returns the shared decimals for the OFT
+
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | uint8 | The shared decimals |
+
+## AaveV3PoolMock
+
+### reserveATokens
```solidity
-function description() public pure returns (string)
+mapping(address => address) reserveATokens
```
-### version
+### withdrawReturnBps
```solidity
-function version() public pure returns (uint256)
+uint256 withdrawReturnBps
```
-### latestAnswer
+### shouldRevertSupply
```solidity
-function latestAnswer() public view virtual returns (int256)
+bool shouldRevertSupply
```
-### latestTimestamp
+### setReserveAToken
```solidity
-function latestTimestamp() public view returns (uint256)
+function setReserveAToken(address asset, address aToken) external
```
-### latestRound
+### setWithdrawReturnBps
```solidity
-function latestRound() public view returns (uint256)
+function setWithdrawReturnBps(uint256 bps) external
```
-### getAnswer
+### withdraw
```solidity
-function getAnswer(uint256) public view returns (int256)
+function withdraw(address asset, uint256 amount, address to) external returns (uint256)
```
-### getTimestamp
+### setShouldRevertSupply
```solidity
-function getTimestamp(uint256) external view returns (uint256)
+function setShouldRevertSupply(bool _shouldRevert) external
```
-### getRoundData
+### supply
```solidity
-function getRoundData(uint80) external view returns (uint80, int256, uint256, uint256, uint80)
+function supply(address asset, uint256 amount, address onBehalfOf, uint16) external
```
-### latestRoundData
+### withdrawAdmin
```solidity
-function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
+function withdrawAdmin(address token, address to, uint256 amount) external
```
-## IStakedUSDe
-
-### convertToAssets
+### getReserveAToken
```solidity
-function convertToAssets(uint256 shares) external view returns (uint256)
+function getReserveAToken(address asset) external view returns (address)
```
-## StakedUSDeAdapter
-
-example https://etherscan.io/address/0x9D39A5DE30e57443BfF2A8307A4256c8797A3497
+## IERC20MintBurn
-### stakedUSDe
+### mint
```solidity
-contract IStakedUSDe stakedUSDe
+function mint(address to, uint256 amount) external
```
-### constructor
+### burn
```solidity
-constructor(address _stakedUSDe) public
+function burn(address from, uint256 amount) external
```
-### decimals
+## AxelarInterchainTokenServiceMock
+
+### registeredTokenAddresses
```solidity
-function decimals() external pure returns (uint8)
+mapping(bytes32 => address) registeredTokenAddresses
```
-### description
+### mintBurn
```solidity
-function description() public pure returns (string)
+mapping(bytes32 => bool) mintBurn
```
-### version
+### shouldRevert
```solidity
-function version() public pure returns (uint256)
+bool shouldRevert
```
-### latestAnswer
+### chainNameHash
```solidity
-function latestAnswer() public view virtual returns (int256)
+bytes32 chainNameHash
```
-### latestTimestamp
+### setChainNameHash
```solidity
-function latestTimestamp() public view returns (uint256)
+function setChainNameHash(bytes32 _chainNameHash) external
```
-### latestRound
+### setShouldRevert
```solidity
-function latestRound() public view returns (uint256)
+function setShouldRevert(bool _shouldRevert) external
```
-### getAnswer
+### registerToken
```solidity
-function getAnswer(uint256) public view returns (int256)
+function registerToken(bytes32 tokenId, address tokenAddress, bool _mintBurn) external
```
-### getTimestamp
+### interchainTransfer
```solidity
-function getTimestamp(uint256) external view returns (uint256)
+function interchainTransfer(bytes32 tokenId, string, bytes destinationAddressBytes, uint256 amount, bytes, uint256) external payable
```
-### getRoundData
+### callContractWithInterchainToken
```solidity
-function getRoundData(uint80) external view returns (uint80, int256, uint256, uint256, uint80)
+function callContractWithInterchainToken(bytes32 tokenId, string destinationChain, bytes destinationAddress, uint256 amount, bytes data) external payable
```
-### latestRoundData
+### registeredTokenAddress
```solidity
-function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
+function registeredTokenAddress(bytes32 tokenId) external view returns (address tokenAddress)
```
-## StorkChainlinkAdapter
+## ERC20Mock
-### TIMESTAMP_DIVIDER
+### constructor
```solidity
-uint256 TIMESTAMP_DIVIDER
+constructor(uint8 decimals_) public
```
-### priceId
+### mint
```solidity
-bytes32 priceId
+function mint(address to, uint256 amount) external
```
-### stork
+### burn
```solidity
-contract IStorkTemporalNumericValueUnsafeGetter stork
+function burn(address from, uint256 amount) external
```
-### constructor
+### decimals
```solidity
-constructor(address _stork, bytes32 _priceId) public
+function decimals() public view returns (uint8)
```
-### decimals
+_Returns the number of decimals used to get its user representation.
+For example, if `decimals` equals `2`, a balance of `505` tokens should
+be displayed to a user as `5.05` (`505 / 10 ** 2`).
-```solidity
-function decimals() external pure returns (uint8)
-```
+Tokens usually opt for a value of 18, imitating the relationship between
+Ether and Wei. This is the value {ERC20} uses, unless this function is
+overridden;
-### description
+NOTE: This information is only used for _display_ purposes: it in
+no way affects any of the arithmetic of the contract, including
+{IERC20-balanceOf} and {IERC20-transfer}._
-```solidity
-function description() public pure returns (string)
-```
+## ERC20MockWithName
-### version
+### constructor
```solidity
-function version() public pure returns (uint256)
+constructor(uint8 decimals_, string name, string symb) public
```
-### latestAnswer
+### mint
```solidity
-function latestAnswer() public view virtual returns (int256)
+function mint(address to, uint256 amount) external
```
-### latestTimestamp
+### decimals
```solidity
-function latestTimestamp() public view returns (uint256)
+function decimals() public view returns (uint8)
```
-### latestRound
+_Returns the number of decimals used to get its user representation.
+For example, if `decimals` equals `2`, a balance of `505` tokens should
+be displayed to a user as `5.05` (`505 / 10 ** 2`).
+
+Tokens usually opt for a value of 18, imitating the relationship between
+Ether and Wei. This is the value {ERC20} uses, unless this function is
+overridden;
+
+NOTE: This information is only used for _display_ purposes: it in
+no way affects any of the arithmetic of the contract, including
+{IERC20-balanceOf} and {IERC20-transfer}._
+
+## LzEndpointV2Mock
+
+### EMPTY_PAYLOAD_HASH
```solidity
-function latestRound() public view returns (uint256)
+bytes32 EMPTY_PAYLOAD_HASH
```
-### getAnswer
+### eid
```solidity
-function getAnswer(uint256) public view returns (int256)
+uint32 eid
```
-### getTimestamp
+### lzEndpointLookup
```solidity
-function getTimestamp(uint256) external view returns (uint256)
+mapping(address => address) lzEndpointLookup
```
-### getRoundData
+### readResponseLookup
```solidity
-function getRoundData(uint80 _roundId) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
+mapping(address => bytes) readResponseLookup
```
-### latestRoundData
+### readChannelId
```solidity
-function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
+uint32 readChannelId
```
-## IStorkTemporalNumericValueUnsafeGetter
-
-### getTemporalNumericValueUnsafeV1
+### lazyInboundNonce
```solidity
-function getTemporalNumericValueUnsafeV1(bytes32 id) external view returns (struct StorkStructs.TemporalNumericValue value)
+mapping(address => mapping(uint32 => mapping(bytes32 => uint64))) lazyInboundNonce
```
-## StorkStructs
-
-### TemporalNumericValue
+### inboundPayloadHash
```solidity
-struct TemporalNumericValue {
- uint64 timestampNs;
- int192 quantizedValue;
-}
+mapping(address => mapping(uint32 => mapping(bytes32 => mapping(uint64 => bytes32)))) inboundPayloadHash
```
-## TransparentUpgradeableProxyCopy
-
-### constructor
+### outboundNonce
```solidity
-constructor(address _logic, address admin_, bytes _data) public payable
+mapping(address => mapping(uint32 => mapping(bytes32 => uint64))) outboundNonce
```
-## IWrappedEEth
-
-### getRate
+### nextComposerMsgValue
```solidity
-function getRate() external view returns (uint256)
+uint256 nextComposerMsgValue
```
-## WrappedEEthAdapter
+### relayerFeeConfig
-example https://etherscan.io/address/0xcd5fe23c85820f7b72d0926fc9b05b43e359b7ee
+```solidity
+struct LzEndpointV2Mock.RelayerFeeConfig relayerFeeConfig
+```
-### wrappedEEth
+### protocolFeeConfig
```solidity
-contract IWrappedEEth wrappedEEth
+struct LzEndpointV2Mock.ProtocolFeeConfig protocolFeeConfig
```
-### constructor
+### verifierFee
```solidity
-constructor(address _wrappedEEth) public
+uint256 verifierFee
```
-### decimals
+### ProtocolFeeConfig
```solidity
-function decimals() external pure returns (uint8)
+struct ProtocolFeeConfig {
+ uint256 zroFee;
+ uint256 nativeBP;
+}
```
-### description
+### RelayerFeeConfig
```solidity
-function description() public pure returns (string)
+struct RelayerFeeConfig {
+ uint128 dstPriceRatio;
+ uint128 dstGasPriceInWei;
+ uint128 dstNativeAmtCap;
+ uint64 baseGas;
+ uint64 gasPerByte;
+}
```
-### version
+### _NOT_ENTERED
```solidity
-function version() public pure returns (uint256)
+uint8 _NOT_ENTERED
```
-### latestAnswer
+### _ENTERED
```solidity
-function latestAnswer() public view virtual returns (int256)
+uint8 _ENTERED
```
-### latestTimestamp
+### _receive_entered_state
```solidity
-function latestTimestamp() public view returns (uint256)
+uint8 _receive_entered_state
```
-### latestRound
+### receiveNonReentrant
```solidity
-function latestRound() public view returns (uint256)
+modifier receiveNonReentrant()
```
-### getAnswer
+### ValueTransferFailed
```solidity
-function getAnswer(uint256) public view returns (int256)
+event ValueTransferFailed(address to, uint256 quantity)
```
-### getTimestamp
+### constructor
```solidity
-function getTimestamp(uint256) external view returns (uint256)
+constructor(uint32 _eid) public
```
-### getRoundData
+### send
```solidity
-function getRoundData(uint80) external view returns (uint80, int256, uint256, uint256, uint80)
+function send(struct MessagingParams _params, address _refundAddress) public payable returns (struct MessagingReceipt receipt)
```
-### latestRoundData
+### receivePayload
```solidity
-function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
+function receivePayload(struct Origin _origin, address _receiver, bytes32 _payloadHash, bytes _message, uint256 _gas, uint256 _msgValue, bytes32 _guid) external payable
```
-## IWstEth
-
-### stEthPerToken
+### getExecutorFee
```solidity
-function stEthPerToken() external view returns (uint256)
+function getExecutorFee(uint256 _payloadSize, bytes _options) public view returns (uint256)
```
-## WstEthAdapter
+### _quote
-example https://etherscan.io/address/0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0
+```solidity
+function _quote(struct MessagingParams _params, address) internal view returns (struct MessagingFee messagingFee)
+```
-### wstEth
+### _getTreasuryAndVerifierFees
```solidity
-contract IWstEth wstEth
+function _getTreasuryAndVerifierFees(uint256 _executorFee, uint256 _verifierFee) internal view returns (uint256)
```
-### constructor
+### _outbound
```solidity
-constructor(address _wstEth) public
+function _outbound(address _sender, uint32 _dstEid, bytes32 _receiver) internal returns (uint64 nonce)
```
-### decimals
+### setDestLzEndpoint
```solidity
-function decimals() external pure returns (uint8)
+function setDestLzEndpoint(address destAddr, address lzEndpointAddr) external
```
-### description
+### setReadResponse
```solidity
-function description() public pure returns (string)
+function setReadResponse(address destAddr, bytes resolvedPayload) external
```
-### version
+### setReadChannelId
```solidity
-function version() public pure returns (uint256)
+function setReadChannelId(uint32 _readChannelId) external
```
-### latestAnswer
+### _decodeExecutorOptions
```solidity
-function latestAnswer() public view virtual returns (int256)
+function _decodeExecutorOptions(bytes _options) internal view returns (uint256 dstAmount, uint256 totalGas)
```
-### latestTimestamp
+### splitOptions
```solidity
-function latestTimestamp() public view returns (uint256)
+function splitOptions(bytes _options) internal pure returns (bytes, struct WorkerOptions[])
```
-### latestRound
+### decode
```solidity
-function latestRound() public view returns (uint256)
+function decode(bytes _options) internal pure returns (bytes executorOptions, bytes dvnOptions)
```
-### getAnswer
+### decodeLegacyOptions
```solidity
-function getAnswer(uint256) public view returns (int256)
+function decodeLegacyOptions(uint16 _optionType, bytes _options) internal pure returns (bytes executorOptions)
```
-### getTimestamp
+### burn
```solidity
-function getTimestamp(uint256) external view returns (uint256)
+function burn(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce, bytes32 _payloadHash) external
```
-### getRoundData
+### clear
```solidity
-function getRoundData(uint80) external view returns (uint80, int256, uint256, uint256, uint80)
+function clear(address _oapp, struct Origin _origin, bytes32 _guid, bytes _message) external
```
-### latestRoundData
+### composeQueue
```solidity
-function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
+mapping(address => mapping(address => mapping(bytes32 => mapping(uint16 => bytes32)))) composeQueue
```
-## AggregatorV3DeprecatedMock
-
-### decimals
+### defaultReceiveLibrary
```solidity
-function decimals() external view returns (uint8)
+function defaultReceiveLibrary(uint32) external pure returns (address)
```
-### description
+### defaultReceiveLibraryTimeout
```solidity
-function description() external view returns (string)
+function defaultReceiveLibraryTimeout(uint32) external pure returns (address lib, uint256 expiry)
```
-### version
+### defaultSendLibrary
```solidity
-function version() external view returns (uint256)
+function defaultSendLibrary(uint32) external pure returns (address)
```
-### setRoundData
+### executable
```solidity
-function setRoundData(int256 _data) external
+function executable(struct Origin, address) external pure returns (enum ExecutionState)
```
-### getRoundData
+### getConfig
```solidity
-function getRoundData(uint80 _roundId) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
+function getConfig(address, address, uint32, uint32) external pure returns (bytes config)
```
-### latestRoundData
+### getReceiveLibrary
```solidity
-function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
+function getReceiveLibrary(address, uint32) external pure returns (address lib, bool isDefault)
```
-## AggregatorV3Mock
-
-### decimals
+### getRegisteredLibraries
```solidity
-function decimals() external view returns (uint8)
+function getRegisteredLibraries() external pure returns (address[])
```
-### description
+### getSendLibrary
```solidity
-function description() external view returns (string)
+function getSendLibrary(address, uint32) external pure returns (address lib)
```
-### version
+### inboundNonce
```solidity
-function version() external view returns (uint256)
+function inboundNonce(address _receiver, uint32 _srcEid, bytes32 _sender) external view returns (uint64)
```
-### setRoundData
+### isDefaultSendLibrary
```solidity
-function setRoundData(int256 _data) external
+function isDefaultSendLibrary(address, uint32) external pure returns (bool)
```
-### getRoundData
+### isRegisteredLibrary
```solidity
-function getRoundData(uint80 _roundId) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
+function isRegisteredLibrary(address) external pure returns (bool)
```
-### latestRoundData
+### isSupportedEid
```solidity
-function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
+function isSupportedEid(uint32) external pure returns (bool)
```
-## AggregatorV3UnhealthyMock
-
-### decimals
+### lzCompose
```solidity
-function decimals() external view returns (uint8)
+function lzCompose(address, address, bytes32, uint16, bytes, bytes) external payable
```
-### description
+### lzReceive
```solidity
-function description() external view returns (string)
+function lzReceive(struct Origin, address, bytes32, bytes, bytes) external payable
```
-### version
+### lzToken
```solidity
-function version() external view returns (uint256)
+function lzToken() external pure returns (address)
```
-### setRoundData
+### nativeToken
```solidity
-function setRoundData(int256 _data) external
+function nativeToken() external pure returns (address)
```
-### getRoundData
+### nextGuid
```solidity
-function getRoundData(uint80 _roundId) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
+function nextGuid(address, uint32, bytes32) external pure returns (bytes32)
```
-### latestRoundData
+### nilify
```solidity
-function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
+function nilify(address, uint32, bytes32, uint64, bytes32) external
```
-## ERC20Mock
-
-### constructor
+### quote
```solidity
-constructor(uint8 decimals_) public
+function quote(struct MessagingParams _params, address _sender) external view returns (struct MessagingFee)
```
-### mint
+### receiveLibraryTimeout
```solidity
-function mint(address to, uint256 amount) external
+mapping(address => mapping(uint32 => struct IMessageLibManager.Timeout)) receiveLibraryTimeout
```
-### decimals
+### registerLibrary
```solidity
-function decimals() public view returns (uint8)
+function registerLibrary(address) public
```
-_Returns the number of decimals used to get its user representation.
-For example, if `decimals` equals `2`, a balance of `505` tokens should
-be displayed to a user as `5.05` (`505 / 10 ** 2`).
+### setNextComposerMsgValue
-Tokens usually opt for a value of 18, imitating the relationship between
-Ether and Wei. This is the value {ERC20} uses, unless this function is
-overridden;
+```solidity
+function setNextComposerMsgValue() external payable
+```
-NOTE: This information is only used for _display_ purposes: it in
-no way affects any of the arithmetic of the contract, including
-{IERC20-balanceOf} and {IERC20-transfer}._
+### sendCompose
-## ERC20MockWithName
+```solidity
+function sendCompose(address to, bytes32 guid, uint16, bytes message) external
+```
-### constructor
+### setConfig
```solidity
-constructor(uint8 decimals_, string name, string symb) public
+function setConfig(address, address, struct SetConfigParam[]) external
```
-### mint
+### setDefaultReceiveLibrary
```solidity
-function mint(address to, uint256 amount) external
+function setDefaultReceiveLibrary(uint32, address, uint256) external
```
-### decimals
+### setDefaultReceiveLibraryTimeout
```solidity
-function decimals() public view returns (uint8)
+function setDefaultReceiveLibraryTimeout(uint32, address, uint256) external
```
-_Returns the number of decimals used to get its user representation.
-For example, if `decimals` equals `2`, a balance of `505` tokens should
-be displayed to a user as `5.05` (`505 / 10 ** 2`).
+### setDefaultSendLibrary
-Tokens usually opt for a value of 18, imitating the relationship between
-Ether and Wei. This is the value {ERC20} uses, unless this function is
-overridden;
+```solidity
+function setDefaultSendLibrary(uint32, address) external
+```
-NOTE: This information is only used for _display_ purposes: it in
-no way affects any of the arithmetic of the contract, including
-{IERC20-balanceOf} and {IERC20-transfer}._
+### setDelegate
-## RedemptionTest
+```solidity
+function setDelegate(address) external
+```
-### asset
+### setLzToken
```solidity
-address asset
+function setLzToken(address) external
```
-The asset being redeemed.
+### setReceiveLibrary
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
+```solidity
+function setReceiveLibrary(address, uint32, address, uint256) external
+```
-### liquidity
+### setReceiveLibraryTimeout
```solidity
-address liquidity
+function setReceiveLibraryTimeout(address, uint32, address, uint256) external
```
-The liquidity token that the asset is being redeemed for.
-
-#### Return Values
+### setSendLibrary
-| Name | Type | Description |
-| ---- | ---- | ----------- |
+```solidity
+function setSendLibrary(address, uint32, address) external
+```
-### constructor
+### skip
```solidity
-constructor(address _asset, address _liquidity) public
+function skip(address, uint32, bytes32, uint64) external
```
-### settlement
+### verifiable
```solidity
-function settlement() external view returns (address)
+function verifiable(struct Origin, address, address, bytes32) external pure returns (bool)
```
-The settlement contract address.
-
-#### Return Values
+### verify
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | address | The address of the settlement contract. |
+```solidity
+function verify(struct Origin, address, bytes32) external
+```
-### redeem
+### executeNativeAirDropAndReturnLzGas
```solidity
-function redeem(uint256 amount) external
+function executeNativeAirDropAndReturnLzGas(bytes _options) public returns (uint256 totalGas, uint256 dstAmount)
```
-Redeems an amount of asset for liquidity
+### _executeNativeAirDropAndReturnLzGas
-#### Parameters
+```solidity
+function _executeNativeAirDropAndReturnLzGas(bytes _options) public returns (uint256 totalGas, uint256 dstAmount)
+```
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| amount | uint256 | The amount of the asset token to redeem |
+### _initializable
-## USTBRedemptionMock
+```solidity
+function _initializable(struct Origin _origin, address _receiver, uint64 _lazyInboundNonce) internal view returns (bool)
+```
-### USDC_DECIMALS
+### _verifiable
```solidity
-uint256 USDC_DECIMALS
+function _verifiable(struct Origin _origin, address _receiver, uint64 _lazyInboundNonce) internal view returns (bool)
```
-### USDC_PRECISION
+_bytes(0) payloadHash can never be submitted_
+
+### initializable
```solidity
-uint256 USDC_PRECISION
+function initializable(struct Origin _origin, address _receiver) external view returns (bool)
```
-### SUPERSTATE_TOKEN_DECIMALS
+### verifiable
```solidity
-uint256 SUPERSTATE_TOKEN_DECIMALS
+function verifiable(struct Origin _origin, address _receiver) external view returns (bool)
```
-### SUPERSTATE_TOKEN_PRECISION
+### isValidReceiveLibrary
```solidity
-uint256 SUPERSTATE_TOKEN_PRECISION
+function isValidReceiveLibrary(address _receiver, uint32 _srcEid, address _actualReceiveLib) public view returns (bool)
```
-### FEE_DENOMINATOR
+_called when the endpoint checks if the msgLib attempting to verify the msg is the configured msgLib of the Oapp
+this check provides the ability for Oapp to lock in a trusted msgLib
+it will fist check if the msgLib is the currently configured one. then check if the msgLib is the one in grace period of msgLib versioning upgrade_
+
+### fallback
```solidity
-uint256 FEE_DENOMINATOR
+fallback() external payable
```
-### CHAINLINK_FEED_PRECISION
+### receive
```solidity
-uint256 CHAINLINK_FEED_PRECISION
+receive() external payable
```
-### SUPERSTATE_TOKEN
+## MorphoVaultMock
+
+### underlyingAsset
```solidity
-contract IERC20 SUPERSTATE_TOKEN
+address underlyingAsset
```
-### USDC
+### exchangeRateNumerator
```solidity
-contract IERC20 USDC
+uint256 exchangeRateNumerator
```
-### redemptionFee
+### RATE_PRECISION
```solidity
-uint256 redemptionFee
+uint256 RATE_PRECISION
```
-### _maxUstbRedemptionAmount
+### shouldRevertDeposit
```solidity
-uint256 _maxUstbRedemptionAmount
+bool shouldRevertDeposit
```
### constructor
```solidity
-constructor(address ustbToken, address usdcToken) public
+constructor(address _underlyingAsset) public
```
-### calculateFee
+### mint
```solidity
-function calculateFee(uint256 amount) public view returns (uint256)
+function mint(address to, uint256 amount) external
```
-### calculateUstbIn
+### setExchangeRate
```solidity
-function calculateUstbIn(uint256 usdcOutAmount) public view returns (uint256 ustbInAmount, uint256 usdPerUstbChainlinkRaw)
+function setExchangeRate(uint256 _numerator) external
```
-### calculateUsdcOut
+### setShouldRevertDeposit
```solidity
-function calculateUsdcOut(uint256 superstateTokenInAmount) external view returns (uint256 usdcOutAmountAfterFee, uint256 usdPerUstbChainlinkRaw)
+function setShouldRevertDeposit(bool _shouldRevert) external
```
-### _calculateUsdcOut
+### withdrawAdmin
```solidity
-function _calculateUsdcOut(uint256 superstateTokenInAmount) internal view returns (uint256 usdcOutAmountAfterFee, uint256 usdcOutAmountBeforeFee, uint256 usdPerUstbChainlinkRaw)
+function withdrawAdmin(address token, address to, uint256 amount) external
```
-### maxUstbRedemptionAmount
+### asset
```solidity
-function maxUstbRedemptionAmount() external view returns (uint256 superstateTokenAmount, uint256 usdPerUstbChainlinkRaw)
+function asset() external view returns (address)
```
-### redeem
+### deposit
```solidity
-function redeem(uint256 superstateTokenInAmount) external
+function deposit(uint256 assets, address receiver) external returns (uint256 shares)
```
-### redeem
+### previewDeposit
```solidity
-function redeem(address to, uint256 superstateTokenInAmount) external
+function previewDeposit(uint256 assets) public view returns (uint256 shares)
```
-### _redeem
+### redeem
```solidity
-function _redeem(address to, uint256 superstateTokenInAmount) internal
+function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets)
```
### withdraw
```solidity
-function withdraw(address _token, address to, uint256 amount) external
+function withdraw(uint256 assets, address receiver, address owner) public returns (uint256 shares)
```
-### _getChainlinkPrice
+### previewWithdraw
```solidity
-function _getChainlinkPrice() internal view returns (bool _isBadData, uint256 _updatedAt, uint256 _price)
+function previewWithdraw(uint256 assets) public view returns (uint256 shares)
```
-### _requireNotPaused
+### convertToAssets
```solidity
-function _requireNotPaused() internal view
+function convertToAssets(uint256 shares) public view returns (uint256 assets)
```
-### setRedemptionFee
+## SanctionsListMock
+
+### isSanctioned
```solidity
-function setRedemptionFee(uint256 fee) external
+mapping(address => bool) isSanctioned
```
-### setChainlinkData
+### setSanctioned
```solidity
-function setChainlinkData(uint256 price, bool isBadData) external
+function setSanctioned(address addr, bool sanctioned) external
```
-### setPaused
+## USTBMock
+
+### owner
```solidity
-function setPaused(bool paused) external
+address owner
```
-### setMaxUstbRedemptionAmount
+### constructor
```solidity
-function setMaxUstbRedemptionAmount(uint256 maxUstbRedemptionAmount_) external
+constructor() public
```
-## TBtcCustomAggregatorFeed
+### symbol
-AggregatorV3 compatible feed for tBTC,
-where price is submitted manually by feed admins
+```solidity
+function symbol() public view returns (string)
+```
-### feedAdminRole
+### decimals
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function decimals() public view returns (uint8)
```
-_describes a role, owner of which can update prices in this feed_
+_Returns the number of decimals used to get its user representation.
+For example, if `decimals` equals `2`, a balance of `505` tokens should
+be displayed to a user as `5.05` (`505 / 10 ** 2`).
-#### Return Values
+Tokens usually opt for a value of 18, imitating the relationship between
+Ether and Wei. This is the value {ERC20} uses, unless this function is
+overridden;
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+NOTE: This information is only used for _display_ purposes: it in
+no way affects any of the arithmetic of the contract, including
+{IERC20-balanceOf} and {IERC20-transfer}._
-## TBtcDataFeed
+### mint
-DataFeed for tBTC product
+```solidity
+function mint(address to, uint256 amount) external
+```
-### feedAdminRole
+### subscribe
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function subscribe(address to, uint256 inAmount, address stablecoin) public
```
-_describes a role, owner of which can manage this feed_
+### subscribe
-#### Return Values
+```solidity
+function subscribe(uint256 inAmount, address stablecoin) external
+```
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+### setStablecoinConfig
-## tBTC
+```solidity
+function setStablecoinConfig(address stablecoin, address newSweepDestination, uint96 newFee) external
+```
-### T_BTC_MINT_OPERATOR_ROLE
+### setAllowListV2
```solidity
-bytes32 T_BTC_MINT_OPERATOR_ROLE
+function setAllowListV2(address allowListV2_) external
```
-actor that can mint tBTC
-
-### T_BTC_BURN_OPERATOR_ROLE
+### setIsAllowed
```solidity
-bytes32 T_BTC_BURN_OPERATOR_ROLE
+function setIsAllowed(address addr, bool isAllowed_) external
```
-actor that can burn tBTC
-
-### T_BTC_PAUSE_OPERATOR_ROLE
+### _subscribe
```solidity
-bytes32 T_BTC_PAUSE_OPERATOR_ROLE
+function _subscribe(address to, uint256 inAmount, address stablecoin) internal
```
-actor that can pause tBTC
+_mints ustb 1:1 to inAmount_
-### initialize
+### supportedStablecoins
```solidity
-function initialize(address _accessControl) external
+function supportedStablecoins(address stablecoin) public view returns (struct ISuperstateToken.StablecoinConfig)
```
-upgradeable pattern contract`s initializer
-
-#### Parameters
+### allowListV2
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _accessControl | address | address of MidasAccessControl contract |
+```solidity
+function allowListV2() external view returns (address)
+```
-### _minterRole
+### isAllowed
```solidity
-function _minterRole() internal pure returns (bytes32)
+function isAllowed(address addr) external view returns (bool)
```
-_AC role, owner of which can mint tBTC token_
+## USTBRedemptionMock
-### _burnerRole
+### USDC_DECIMALS
```solidity
-function _burnerRole() internal pure returns (bytes32)
+uint256 USDC_DECIMALS
```
-_AC role, owner of which can burn tBTC token_
-
-### _pauserRole
+### USDC_PRECISION
```solidity
-function _pauserRole() internal pure returns (bytes32)
+uint256 USDC_PRECISION
```
-_AC role, owner of which can pause tBTC token_
-
-## TEthCustomAggregatorFeed
+### SUPERSTATE_TOKEN_DECIMALS
-AggregatorV3 compatible feed for tETH,
-where price is submitted manually by feed admins
+```solidity
+uint256 SUPERSTATE_TOKEN_DECIMALS
+```
-### feedAdminRole
+### SUPERSTATE_TOKEN_PRECISION
```solidity
-function feedAdminRole() public pure returns (bytes32)
+uint256 SUPERSTATE_TOKEN_PRECISION
```
-_describes a role, owner of which can update prices in this feed_
-
-#### Return Values
+### FEE_DENOMINATOR
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+```solidity
+uint256 FEE_DENOMINATOR
+```
-## TEthDataFeed
+### CHAINLINK_FEED_PRECISION
-DataFeed for tETH product
+```solidity
+uint256 CHAINLINK_FEED_PRECISION
+```
-### feedAdminRole
+### SUPERSTATE_TOKEN
```solidity
-function feedAdminRole() public pure returns (bytes32)
+contract IERC20 SUPERSTATE_TOKEN
```
-_describes a role, owner of which can manage this feed_
+### USDC
-#### Return Values
+```solidity
+contract IERC20 USDC
+```
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+### redemptionFee
-## tETH
+```solidity
+uint256 redemptionFee
+```
-### T_ETH_MINT_OPERATOR_ROLE
+### _maxUstbRedemptionAmount
```solidity
-bytes32 T_ETH_MINT_OPERATOR_ROLE
+uint256 _maxUstbRedemptionAmount
```
-actor that can mint tETH
-
-### T_ETH_BURN_OPERATOR_ROLE
+### constructor
```solidity
-bytes32 T_ETH_BURN_OPERATOR_ROLE
+constructor(address ustbToken, address usdcToken) public
```
-actor that can burn tETH
-
-### T_ETH_PAUSE_OPERATOR_ROLE
+### calculateFee
```solidity
-bytes32 T_ETH_PAUSE_OPERATOR_ROLE
+function calculateFee(uint256 amount) public view returns (uint256)
```
-actor that can pause tETH
+### calculateUstbIn
-### initialize
+```solidity
+function calculateUstbIn(uint256 usdcOutAmount) public view returns (uint256 ustbInAmount, uint256 usdPerUstbChainlinkRaw)
+```
+
+### calculateUsdcOut
```solidity
-function initialize(address _accessControl) external
+function calculateUsdcOut(uint256 superstateTokenInAmount) external view returns (uint256 usdcOutAmountAfterFee, uint256 usdPerUstbChainlinkRaw)
```
-upgradeable pattern contract`s initializer
+### _calculateUsdcOut
-#### Parameters
+```solidity
+function _calculateUsdcOut(uint256 superstateTokenInAmount) internal view returns (uint256 usdcOutAmountAfterFee, uint256 usdcOutAmountBeforeFee, uint256 usdPerUstbChainlinkRaw)
+```
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _accessControl | address | address of MidasAccessControl contract |
+### maxUstbRedemptionAmount
+
+```solidity
+function maxUstbRedemptionAmount() external view returns (uint256 superstateTokenAmount, uint256 usdPerUstbChainlinkRaw)
+```
-### _minterRole
+### redeem
```solidity
-function _minterRole() internal pure returns (bytes32)
+function redeem(uint256 superstateTokenInAmount) external
```
-_AC role, owner of which can mint tETH token_
-
-### _burnerRole
+### redeem
```solidity
-function _burnerRole() internal pure returns (bytes32)
+function redeem(address to, uint256 superstateTokenInAmount) external
```
-_AC role, owner of which can burn tETH token_
-
-### _pauserRole
+### _redeem
```solidity
-function _pauserRole() internal pure returns (bytes32)
+function _redeem(address to, uint256 superstateTokenInAmount) internal
```
-_AC role, owner of which can pause tETH token_
-
-## TUsdeCustomAggregatorFeed
-
-AggregatorV3 compatible feed for tUSDe,
-where price is submitted manually by feed admins
-
-### feedAdminRole
+### withdraw
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function withdraw(address _token, address to, uint256 amount) external
```
-_describes a role, owner of which can update prices in this feed_
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
-
-## TUsdeDataFeed
-
-DataFeed for tUSDe product
-
-### feedAdminRole
+### _getChainlinkPrice
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function _getChainlinkPrice() internal view returns (bool _isBadData, uint256 _updatedAt, uint256 _price)
```
-_describes a role, owner of which can manage this feed_
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
-
-## tUSDe
-
-### T_USDE_MINT_OPERATOR_ROLE
+### _requireNotPaused
```solidity
-bytes32 T_USDE_MINT_OPERATOR_ROLE
+function _requireNotPaused() internal view
```
-actor that can mint tUSDe
-
-### T_USDE_BURN_OPERATOR_ROLE
+### setRedemptionFee
```solidity
-bytes32 T_USDE_BURN_OPERATOR_ROLE
+function setRedemptionFee(uint256 fee) external
```
-actor that can burn tUSDe
-
-### T_USDE_PAUSE_OPERATOR_ROLE
+### setChainlinkData
```solidity
-bytes32 T_USDE_PAUSE_OPERATOR_ROLE
+function setChainlinkData(uint256 price, bool isBadData) external
```
-actor that can pause tUSDe
-
-### initialize
+### setPaused
```solidity
-function initialize(address _accessControl) external
+function setPaused(bool paused) external
```
-upgradeable pattern contract`s initializer
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _accessControl | address | address of MidasAccessControl contract |
-
-### _minterRole
+### setMaxUstbRedemptionAmount
```solidity
-function _minterRole() internal pure returns (bytes32)
+function setMaxUstbRedemptionAmount(uint256 maxUstbRedemptionAmount_) external
```
-_AC role, owner of which can mint tUSDe token_
+## YInjOracleMock
-### _burnerRole
+### constructor
```solidity
-function _burnerRole() internal pure returns (bytes32)
+constructor(uint256 _rate) public
```
-_AC role, owner of which can burn tUSDe token_
-
-### _pauserRole
+### getExchangeRate
```solidity
-function _pauserRole() internal pure returns (bytes32)
+function getExchangeRate() external view returns (uint256)
```
-_AC role, owner of which can pause tUSDe token_
-
-## BlacklistableTester
+## CustomAggregatorV3CompatibleFeedAdjustedTester
-### initialize
+### constructor
```solidity
-function initialize(address _accessControl) external
+constructor(address _underlyingFeed, int256 _adjustmentPercentage) public
```
-### initializeWithoutInitializer
+### getAdjustedAnswer
```solidity
-function initializeWithoutInitializer(address _accessControl) external
+function getAdjustedAnswer(int256 _answer) public view returns (int256)
```
-### initializeUnchainedWithoutInitializer
+## DecimalsCorrectionTester
+
+### convertAmountFromBase18Public
```solidity
-function initializeUnchainedWithoutInitializer() external
+function convertAmountFromBase18Public(uint256 amount, uint256 decimals) public pure returns (uint256)
```
-### onlyNotBlacklistedTester
+### convertAmountToBase18Public
```solidity
-function onlyNotBlacklistedTester(address account) external
+function convertAmountToBase18Public(uint256 amount, uint256 decimals) public pure returns (uint256)
```
+## MidasAccessControlTimelockControllerTest
+
### _disableInitializers
```solidity
@@ -9342,303 +17138,306 @@ through proxies.
Emits an {Initialized} event the first time it is successfully executed._
-## CustomAggregatorV3CompatibleFeedDiscountedTester
-
-### constructor
+### _onlyProxyAdmin
```solidity
-constructor(address _underlyingFeed, uint256 _discountPercentage) public
+function _onlyProxyAdmin() internal view
```
-### getDiscountedAnswer
+function to check if the sender is the proxy admin
+
+## MidasInitializableTester
+
+### initializeCallsCount
```solidity
-function getDiscountedAnswer(int256 _answer) public view returns (int256)
+uint256 initializeCallsCount
```
-## CustomAggregatorV3CompatibleFeedTester
+### reinitCallsCount
-### CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+```solidity
+uint256 reinitCallsCount
+```
+
+### initialize
```solidity
-bytes32 CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE
+function initialize() external
```
-### _disableInitializers
+### initializeV2
```solidity
-function _disableInitializers() internal
+function initializeV2() public
```
-_Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
-Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
-to any version. It is recommended to use this to lock implementation contracts that are designed to be called
-through proxies.
+## RateLimitLibraryTester
-Emits an {Initialized} event the first time it is successfully executed._
+Exposes {RateLimitLibrary} internals for unit tests.
-### feedAdminRole
+### setWindowLimitPublic
```solidity
-function feedAdminRole() public pure returns (bytes32)
+function setWindowLimitPublic(uint256 window, uint256 limit) external returns (uint256 previousLimit)
```
-_describes a role, owner of which can update prices in this feed_
+### removeWindowLimitPublic
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | role descriptor |
+```solidity
+function removeWindowLimitPublic(uint256 window) external
+```
-### getDeviation
+### consumeLimitPublic
```solidity
-function getDeviation(int256 _lastPrice, int256 _newPrice) public pure returns (uint256)
+function consumeLimitPublic(uint256 amount) external
```
-## DataFeedTest
-
-### _disableInitializers
+### getWindowStatusesPublic
```solidity
-function _disableInitializers() internal
+function getWindowStatusesPublic() external view returns (struct RateLimitLibrary.WindowRateLimitStatus[])
```
-_Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
-Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
-to any version. It is recommended to use this to lock implementation contracts that are designed to be called
-through proxies.
-
-Emits an {Initialized} event the first time it is successfully executed._
+### getWindowConfigPublic
-## DecimalsCorrectionTester
+```solidity
+function getWindowConfigPublic(uint256 window) external view returns (uint256 limit, uint256 amountInFlight, uint256 lastUpdated, uint256 windowDuration)
+```
-### convertAmountFromBase18Public
+### windowCountPublic
```solidity
-function convertAmountFromBase18Public(uint256 amount, uint256 decimals) public pure returns (uint256)
+function windowCountPublic() external view returns (uint256)
```
-### convertAmountToBase18Public
+### hasWindowPublic
```solidity
-function convertAmountToBase18Public(uint256 amount, uint256 decimals) public pure returns (uint256)
+function hasWindowPublic(uint256 window) external view returns (bool)
```
-## GreenlistableTester
+## MidasAccessControlRoles
-### initialize
+Base contract that stores all roles descriptors
+
+### GREENLIST_OPERATOR_ROLE
```solidity
-function initialize(address _accessControl) external
+bytes32 GREENLIST_OPERATOR_ROLE
```
-### initializeWithoutInitializer
+actor that can change green list statuses of addresses
+
+### BLACKLIST_OPERATOR_ROLE
```solidity
-function initializeWithoutInitializer(address _accessControl) external
+bytes32 BLACKLIST_OPERATOR_ROLE
```
-### initializeUnchainedWithoutInitializer
+actor that can change black list statuses of addresses
+
+### GREENLISTED_ROLE
```solidity
-function initializeUnchainedWithoutInitializer() external
+bytes32 GREENLISTED_ROLE
```
-### onlyGreenlistedTester
+actor that is greenlisted
+
+### BLACKLISTED_ROLE
```solidity
-function onlyGreenlistedTester(address account) external
+bytes32 BLACKLISTED_ROLE
```
-### onlyGreenlistTogglerTester
+actor that is blacklisted
+
+## MidasCCTBurnMintTokenPool
+
+BurnMintTokenPool implementation for Midas mTokens
+
+### fallbackReceiver
```solidity
-function onlyGreenlistTogglerTester(address account) external view
+address fallbackReceiver
```
-### _disableInitializers
+The receiver of the tokens if user mint fails
+
+### FallbackReceiverSet
```solidity
-function _disableInitializers() internal
+event FallbackReceiverSet(address newFallbackReceiver)
```
-_Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
-Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
-to any version. It is recommended to use this to lock implementation contracts that are designed to be called
-through proxies.
-
-Emits an {Initialized} event the first time it is successfully executed._
+#### Parameters
-## MidasAccessControlTest
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newFallbackReceiver | address | The new fallback receiver |
-### _disableInitializers
+### FallbackHit
```solidity
-function _disableInitializers() internal
+event FallbackHit(address originalReceiver, address fallbackReceiver, uint256 amount, bytes error)
```
-_Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
-Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
-to any version. It is recommended to use this to lock implementation contracts that are designed to be called
-through proxies.
-
-Emits an {Initialized} event the first time it is successfully executed._
+#### Parameters
-## PausableTester
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| originalReceiver | address | The original receiver of the tokens |
+| fallbackReceiver | address | The fallback receiver of the tokens |
+| amount | uint256 | The amount of tokens |
+| error | bytes | The error that occurred |
-### initialize
+### InvalidFallbackReceiver
```solidity
-function initialize(address _accessControl) external
+error InvalidFallbackReceiver(address newFallbackReceiver)
```
-### initializeWithoutInitializer
+Error thrown when the fallback receiver is set to address zero
+
+### NotSelf
```solidity
-function initializeWithoutInitializer(address _accessControl) external
+error NotSelf()
```
-### pauseAdminRole
+Error thrown when the function is called by an address other than the contract itself
+
+### constructor
```solidity
-function pauseAdminRole() public view returns (bytes32)
+constructor(contract IMToken token, address rmnProxy, address router, address initFallbackReceiver) public
```
-_virtual function to determine pauseAdmin role_
-
-### _disableInitializers
+### setFallbackReceiver
```solidity
-function _disableInitializers() internal
+function setFallbackReceiver(address newFallbackReceiver) external
```
-_Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
-Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
-to any version. It is recommended to use this to lock implementation contracts that are designed to be called
-through proxies.
+Set the fallback receiver of the pool
-Emits an {Initialized} event the first time it is successfully executed._
+#### Parameters
-## WithMidasAccessControlTester
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newFallbackReceiver | address | The new fallback receiver |
-### initialize
+### _lockOrBurn
```solidity
-function initialize(address _accessControl) external
+function _lockOrBurn(uint64, uint256 amount) internal virtual
```
-### initializeWithoutInitializer
+### _releaseOrMint
```solidity
-function initializeWithoutInitializer(address _accessControl) external
+function _releaseOrMint(address receiver, uint256 amount, uint64) internal virtual
```
-### grantRoleTester
+_Mints the tokens to the receiver, in case if
+user mint fails it mints to the fallback receiver_
-```solidity
-function grantRoleTester(bytes32 role, address account) external
-```
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| receiver | address | The original receiver of the tokens |
+| amount | uint256 | The amount of tokens |
+| | uint64 | |
-### revokeRoleTester
+### releaseOrMintInternal
```solidity
-function revokeRoleTester(bytes32 role, address account) external
+function releaseOrMintInternal(address receiver, uint256 amount) external
```
-### withOnlyRole
+Function that mints the tokens to the receiver and
+can be wrapped with a try/catch to handle errors
-```solidity
-function withOnlyRole(bytes32 role, address account) external
-```
+_Only callable by the contract itself_
-### withOnlyNotRole
+#### Parameters
-```solidity
-function withOnlyNotRole(bytes32 role, address account) external
-```
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| receiver | address | The receiver of the tokens |
+| amount | uint256 | The amount of tokens |
-### _disableInitializers
+### _mint
```solidity
-function _disableInitializers() internal
+function _mint(address receiver, uint256 amount) internal
```
-_Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
-Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
-to any version. It is recommended to use this to lock implementation contracts that are designed to be called
-through proxies.
+_Mint the tokens to the receiver_
-Emits an {Initialized} event the first time it is successfully executed._
+#### Parameters
-## WithSanctionsListTester
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| receiver | address | The receiver of the tokens |
+| amount | uint256 | The amount of tokens |
-### initialize
+### _setFallbackReceiver
```solidity
-function initialize(address _accessControl, address _sanctionsList) external
+function _setFallbackReceiver(address newFallbackReceiver) internal
```
-### initializeWithoutInitializer
+_Set the fallback receiver of the pool_
-```solidity
-function initializeWithoutInitializer(address _accessControl, address _sanctionsList) external
-```
+#### Parameters
-### initializeUnchainedWithoutInitializer
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| newFallbackReceiver | address | The new fallback receiver |
-```solidity
-function initializeUnchainedWithoutInitializer(address _sanctionsList) external
-```
+## CCIPRmnMock
-### onlyNotSanctionedTester
+### setCursed
```solidity
-function onlyNotSanctionedTester(address user) public
+function setCursed(bool cursed) external
```
-### sanctionsListAdminRole
+### isCursed
```solidity
-function sanctionsListAdminRole() public pure returns (bytes32)
+function isCursed() external view returns (bool)
```
-AC role of sanctions list admin
-
-_address that have this role can use `setSanctionsList`_
+Iff there is an active global or legacy curse, this function returns true.
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
-| [0] | bytes32 | role bytes32 role |
+| [0] | bool | bool true if there is an active global curse. |
-### _disableInitializers
+### isCursed
```solidity
-function _disableInitializers() internal
+function isCursed(bytes16) external view returns (bool)
```
-_Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
-Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
-to any version. It is recommended to use this to lock implementation contracts that are designed to be called
-through proxies.
-
-Emits an {Initialized} event the first time it is successfully executed._
-
-## mTBILLTest
-
-### _disableInitializers
+### getCursedSubjects
```solidity
-function _disableInitializers() internal
+function getCursedSubjects() external pure returns (bytes16[])
```
-_Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
-Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
-to any version. It is recommended to use this to lock implementation contracts that are designed to be called
-through proxies.
+gets the current set of cursed subjects.
-Emits an {Initialized} event the first time it is successfully executed._
+#### Return Values
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| [0] | bytes16[] | |
diff --git a/hardhat.config.ts b/hardhat.config.ts
index b8098507..0464c5e2 100644
--- a/hardhat.config.ts
+++ b/hardhat.config.ts
@@ -14,8 +14,8 @@ import 'hardhat-deploy';
import 'hardhat-gas-reporter';
import 'solidity-coverage';
import 'hardhat-tracer';
-
import './tasks';
+
import {
chainIds,
ENV,
@@ -35,20 +35,11 @@ const config: HardhatUserConfig = {
solidity: {
compilers: [
{
- version: '0.8.9',
- settings: {
- optimizer: {
- enabled: true,
- runs: 200,
- },
- },
- },
- {
- version: '0.8.22',
+ version: '0.8.34',
settings: {
optimizer: {
enabled: true,
- runs: 200,
+ runs: 1,
},
},
},
diff --git a/helpers/contracts.ts b/helpers/contracts.ts
index c0bbfac0..92e8d787 100644
--- a/helpers/contracts.ts
+++ b/helpers/contracts.ts
@@ -10,21 +10,24 @@ export type TokenContractNames = {
rv: string;
rvSwapper: string;
rvMToken: string;
- rvBuidl: string;
rvUstb: string;
rvAave: string;
rvMorpho: string;
- dataFeed?: string;
- dataFeedComposite?: string;
- dataFeedMultiply?: string;
+ dataFeed: string;
+ dataFeedComposite: string;
+ dataFeedMultiply: string;
customAggregator?: string;
customAggregatorGrowth?: string;
token: string;
+ tokenPermissioned: string;
roles: string;
};
-type CommonContractNames = Omit & {
+type CommonContractNames = TokenContractNames & {
ac: string;
+ pauseManager: string;
+ timelockManager: string;
+ timelockController: string;
customAggregator: string;
customAggregatorAdjusted: string;
layerZero: {
@@ -43,9 +46,9 @@ const vaultTypeToContractNameMap: Record = {
depositVaultAave: 'dvAave',
depositVaultMorpho: 'dvMorpho',
depositVaultMToken: 'dvMToken',
- redemptionVaultBuidl: 'rvBuidl',
redemptionVaultAave: 'rvAave',
redemptionVaultMorpho: 'rvMorpho',
+ redemptionVaultBuidl: 'rvBuidl',
};
export const vaultTypeToContractName = (
@@ -149,7 +152,12 @@ export const contractNamesPrefixes: Record = {
export const getCommonContractNames = (): CommonContractNames => {
return {
ac: 'MidasAccessControl',
+ pauseManager: 'MidasPauseManager',
+ timelockManager: 'MidasTimelockManager',
+ timelockController: 'MidasAccessControlTimelockController',
dv: 'DepositVault',
+ token: 'mToken',
+ tokenPermissioned: 'mToken',
dvUstb: 'DepositVaultWithUSTB',
dvAave: 'DepositVaultWithAave',
dvMorpho: 'DepositVaultWithMorpho',
@@ -157,7 +165,6 @@ export const getCommonContractNames = (): CommonContractNames => {
rv: 'RedemptionVault',
rvSwapper: 'RedemptionVaultWithSwapper',
rvMToken: 'RedemptionVaultWithMToken',
- rvBuidl: 'RedemptionVaultWIthBUIDL',
rvUstb: 'RedemptionVaultWithUSTB',
rvAave: 'RedemptionVaultWithAave',
rvMorpho: 'RedemptionVaultWithMorpho',
@@ -175,35 +182,11 @@ export const getCommonContractNames = (): CommonContractNames => {
};
};
+// TODO: remove this function
export const getTokenContractNames = (
- token: MTokenName,
+ _token: MTokenName,
): TokenContractNames => {
const commonContractNames = getCommonContractNames();
- const prefix = contractNamesPrefixes[token];
-
- const isMtbill = token === 'mTBILL';
- const isTac = token.startsWith('TAC');
- const tokenPrefix = isMtbill ? '' : prefix;
- return {
- dv: `${tokenPrefix}${commonContractNames.dv}`,
- dvUstb: `${tokenPrefix}${commonContractNames.dvUstb}`,
- dvAave: `${tokenPrefix}${commonContractNames.dvAave}`,
- dvMorpho: `${tokenPrefix}${commonContractNames.dvMorpho}`,
- dvMToken: `${tokenPrefix}${commonContractNames.dvMToken}`,
- rv: `${tokenPrefix}${commonContractNames.rv}`,
- rvSwapper: `${tokenPrefix}${commonContractNames.rvSwapper}`,
- rvMToken: `${tokenPrefix}${commonContractNames.rvMToken}`,
- rvBuidl: `${tokenPrefix}${commonContractNames.rvBuidl}`,
- rvUstb: `${tokenPrefix}${commonContractNames.rvUstb}`,
- rvAave: `${tokenPrefix}${commonContractNames.rvAave}`,
- rvMorpho: `${tokenPrefix}${commonContractNames.rvMorpho}`,
- dataFeed: isTac ? undefined : `${prefix}${commonContractNames.dataFeed}`,
- customAggregator: isTac ? undefined : `${prefix}CustomAggregatorFeed`,
- customAggregatorGrowth: isTac
- ? undefined
- : `${prefix}CustomAggregatorFeedGrowth`,
- token: `${token}`,
- roles: `${prefix}${commonContractNames.roles}`,
- };
+ return commonContractNames;
};
diff --git a/helpers/roles.ts b/helpers/roles.ts
index 2767f339..dfb3c637 100644
--- a/helpers/roles.ts
+++ b/helpers/roles.ts
@@ -130,11 +130,12 @@ const getGreenlistRoleName = (token: MTokenName): string => {
type TokenRoles = {
minter: string;
burner: string;
- pauser: string;
+ tokenManager: string;
depositVaultAdmin: string;
redemptionVaultAdmin: string;
- customFeedAdmin: string | null;
+ customFeedAdmin: string;
greenlisted: string;
+ minBalanceExempt: string;
};
type CommonRoles = {
@@ -143,6 +144,9 @@ type CommonRoles = {
greenlistedOperator: string;
blacklistedOperator: string;
defaultAdmin: string;
+ pauseAdmin: string;
+ securityCouncilManager: string;
+ timelockOperationPauser: string;
};
type IntegrationRoles = {
@@ -169,13 +173,12 @@ export const getRolesNamesForToken = (token: MTokenName): TokenRoles => {
return {
minter: `${tokenPrefix}_MINT_OPERATOR_ROLE`,
burner: `${tokenPrefix}_BURN_OPERATOR_ROLE`,
- pauser: `${tokenPrefix}_PAUSE_OPERATOR_ROLE`,
- customFeedAdmin: isTAC
- ? null
- : `${tokenPrefix}_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE`,
+ tokenManager: `${tokenPrefix}_TOKEN_MANAGER_ROLE`,
+ customFeedAdmin: `${tokenPrefix}_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE`,
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 => {
@@ -185,6 +188,9 @@ export const getRolesNamesCommon = (): CommonRoles => {
greenlistedOperator: 'GREENLIST_OPERATOR_ROLE',
blacklisted: 'BLACKLISTED_ROLE',
blacklistedOperator: 'BLACKLIST_OPERATOR_ROLE',
+ pauseAdmin: 'PAUSE_ADMIN_ROLE',
+ securityCouncilManager: 'SECURITY_COUNCIL_MANAGER_ROLE',
+ timelockOperationPauser: 'TIMELOCK_OPERATION_PAUSER_ROLE',
};
};
@@ -228,6 +234,13 @@ export const getAllRoles = (): AllRoles => {
greenlistedOperator: keccak256(rolesNamesCommon.greenlistedOperator),
blacklisted: keccak256(rolesNamesCommon.blacklisted),
blacklistedOperator: keccak256(rolesNamesCommon.blacklistedOperator),
+ pauseAdmin: keccak256(rolesNamesCommon.pauseAdmin),
+ securityCouncilManager: keccak256(
+ rolesNamesCommon.securityCouncilManager,
+ ),
+ timelockOperationPauser: keccak256(
+ rolesNamesCommon.timelockOperationPauser,
+ ),
},
tokenRoles: Object.fromEntries(
Object.keys(prefixes).map((token) => [
diff --git a/helpers/utils.ts b/helpers/utils.ts
index 8ac334c1..a9f80a34 100644
--- a/helpers/utils.ts
+++ b/helpers/utils.ts
@@ -15,6 +15,37 @@ import {
export const DAY = 86400;
+const contractNameToPath = {
+ access: [
+ 'MidasAccessControl',
+ 'MidasPauseManager',
+ 'MidasTimelockManager',
+ 'MidasAccessControlTimelockController',
+ ],
+ feeds: [
+ 'CompositeDataFeed',
+ 'CompositeDataFeedMultiply',
+ 'CustomAggregatorV3CompatibleFeed',
+ 'CustomAggregatorV3CompatibleFeedAdjusted',
+ 'CustomAggregatorV3CompatibleFeedGrowth',
+ 'DataFeed',
+ ],
+ root: ['mToken'],
+};
+
+const getContractPath = (contractName: string) => {
+ for (const [key, value] of Object.entries(contractNameToPath)) {
+ if (value.includes(contractName)) {
+ const path = `contracts/${
+ key === 'root' ? '' : key + '/'
+ }${contractName}.sol:${contractName}`;
+ return path;
+ }
+ }
+
+ return undefined;
+};
+
export function delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
@@ -87,11 +118,19 @@ export const getPaymentTokenOrThrow = (hre: HardhatRuntimeEnvironment) => {
return paymentToken;
};
-export const getActionOrThrow = (hre: HardhatRuntimeEnvironment) => {
+export const upgradeActions = ['propose', 'execute'] as const;
+
+export const getActionOrThrow = (
+ hre: HardhatRuntimeEnvironment,
+ validActions?: string[] | readonly string[],
+) => {
const action = hre.action;
if (!action) {
throw new Error('Action parameter not found');
}
+ if (validActions?.length && !validActions.includes(action)) {
+ throw new Error(`Invalid action: ${action}`);
+ }
return action;
};
@@ -138,20 +177,27 @@ export const logDeploy = (
export const etherscanVerify = async (
hre: HardhatRuntimeEnvironment,
contractAddress: string,
+ contractName: string,
...constructorArguments: unknown[]
) => {
const network = hre.network.name;
if (network === 'localhost' || network === 'hardhat') return;
- await verify(hre, contractAddress, ...constructorArguments);
+ await verify(hre, contractAddress, contractName, ...constructorArguments);
};
export const etherscanVerifyImplementation = async (
hre: HardhatRuntimeEnvironment,
proxyAddress: string,
+ contractName: string,
...constructorArguments: unknown[]
) => {
const contractAddress = await getImplAddressFromProxy(hre, proxyAddress);
- return etherscanVerify(hre, contractAddress, ...constructorArguments);
+ return etherscanVerify(
+ hre,
+ contractAddress,
+ contractName,
+ ...constructorArguments,
+ );
};
export const logDeployProxy = async (
@@ -175,11 +221,13 @@ export const logDeployProxy = async (
export const tryEtherscanVerifyImplementation = async (
hre: HardhatRuntimeEnvironment,
proxyAddress: string,
+ contractName: string,
...constructorArguments: unknown[]
) => {
return await etherscanVerifyImplementation(
hre,
proxyAddress,
+ contractName,
...constructorArguments,
)
.catch((err) => {
@@ -194,12 +242,14 @@ export const tryEtherscanVerifyImplementation = async (
export const verify = async (
hre: HardhatRuntimeEnvironment,
contractAddress: string,
+ contractName: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...constructorArguments: any[]
) => {
await hre.run('verify:verify', {
address: contractAddress,
constructorArguments,
+ contract: getContractPath(contractName),
});
};
diff --git a/package.json b/package.json
index 555342a9..6f088527 100644
--- a/package.json
+++ b/package.json
@@ -2,10 +2,10 @@
"name": "midas-contracts",
"version": "2.0.0",
"description": "",
- "license": "AGPL-3.0",
+ "license": "BUSL-1.1",
"author": "RedDuck Software",
"engines": {
- "node": ">=22"
+ "node": ">=22 <23"
},
"scripts": {
"postinstall": "sh -c '[ \"$NODE_ENV\" = \"production\" ] || husky install'",
@@ -15,10 +15,10 @@
"docgen": "hardhat docgen",
"compile": "hardhat compile",
"build": "run-s clean compile",
- "test:unit": "hardhat test $(find test/unit -name \"*.test.ts\")",
- "test:unit:parallel": "yarn test:unit --parallel",
- "test:integration": "hardhat test $(find test/integration -name \"*.test.ts\")",
- "test": "run-p test:unit test:integration",
+ "test:unit": "cross-env TS_NODE_TRANSPILE_ONLY=1 hardhat test $(find test/unit -name \"*.test.ts\")",
+ "test:unit:parallel": "cross-env TS_NODE_TRANSPILE_ONLY=1 hardhat test --parallel $(find test/unit -name \"*.test.ts\")",
+ "test:integration": "cross-env TS_NODE_TRANSPILE_ONLY=1 hardhat test $(find test/integration -name \"*.test.ts\")",
+ "test": "run-s test:unit test:integration",
"test:parallel": "run-s test:unit:parallel test:integration",
"coverage": "cross-env COVERAGE=true hardhat coverage --solcoverjs ./.solcover.json --testfiles 'test/unit/*.test.ts'",
"slither": "slither . --config-file ./slither.config.json",
@@ -100,6 +100,7 @@
"verify:proxy": "yarn hardhat verify-transparent-proxy",
"verify:sourcify": "VERIFY_SOURCIFY=true VERIFY_ETHERSCAN=false yarn hardhat verify",
"dump:roles": "yarn hh:run scripts/dump_roles.ts",
+ "dump:errors": "ts-node scripts/generate-errors-readme.ts",
"verify:all:impl": "yarn hh:run:script scripts/verify_contracts.ts",
"oz:merge-manifest": "ts-node scripts/merge-oz-manifests.ts"
},
diff --git a/scripts/deploy/codegen/common/templates/aggregator.template.ts b/scripts/deploy/codegen/common/templates/aggregator.template.ts
index e7ba534f..580c503d 100644
--- a/scripts/deploy/codegen/common/templates/aggregator.template.ts
+++ b/scripts/deploy/codegen/common/templates/aggregator.template.ts
@@ -21,8 +21,8 @@ export const getCustomAggregatorContractFromTemplate = async (
return {
name: contractNames.customAggregator,
content: `
- // SPDX-License-Identifier: MIT
- pragma solidity 0.8.9;
+ // SPDX-License-Identifier: BUSL-1.1
+ pragma solidity 0.8.34;
import "../../feeds/CustomAggregatorV3CompatibleFeed.sol";
import "./${contractNames.roles}.sol";
@@ -72,8 +72,8 @@ export const getCustomAggregatorGrowthContractFromTemplate = async (
return {
name: contractNames.customAggregatorGrowth,
content: `
- // SPDX-License-Identifier: MIT
- pragma solidity 0.8.9;
+ // SPDX-License-Identifier: BUSL-1.1
+ pragma solidity 0.8.34;
import "../../feeds/CustomAggregatorV3CompatibleFeedGrowth.sol";
import "./${contractNames.roles}.sol";
diff --git a/scripts/deploy/codegen/common/templates/data-feed.template.ts b/scripts/deploy/codegen/common/templates/data-feed.template.ts
index a85d051e..0b8ff1e2 100644
--- a/scripts/deploy/codegen/common/templates/data-feed.template.ts
+++ b/scripts/deploy/codegen/common/templates/data-feed.template.ts
@@ -18,8 +18,8 @@ export const getDataFeedContractFromTemplate = async (mToken: MTokenName) => {
return {
name: contractNames.dataFeed,
content: `
- // SPDX-License-Identifier: MIT
- pragma solidity 0.8.9;
+ // SPDX-License-Identifier: BUSL-1.1
+ pragma solidity 0.8.34;
import "../../feeds/DataFeed.sol";
import "./${contractNames.roles}.sol";
diff --git a/scripts/deploy/codegen/common/templates/dv-aave.template.ts b/scripts/deploy/codegen/common/templates/dv-aave.template.ts
index 25001389..9f06c08b 100644
--- a/scripts/deploy/codegen/common/templates/dv-aave.template.ts
+++ b/scripts/deploy/codegen/common/templates/dv-aave.template.ts
@@ -20,8 +20,8 @@ export const getDvAaveContractFromTemplate = async (
return {
name: contractNames.dvAave,
content: `
- // SPDX-License-Identifier: MIT
- pragma solidity 0.8.9;
+ // SPDX-License-Identifier: BUSL-1.1
+ pragma solidity 0.8.34;
import "../../DepositVaultWithAave.sol";
import "./${contractNames.roles}.sol";
diff --git a/scripts/deploy/codegen/common/templates/dv-morpho.template.ts b/scripts/deploy/codegen/common/templates/dv-morpho.template.ts
index edb69743..af826528 100644
--- a/scripts/deploy/codegen/common/templates/dv-morpho.template.ts
+++ b/scripts/deploy/codegen/common/templates/dv-morpho.template.ts
@@ -20,8 +20,8 @@ export const getDvMorphoContractFromTemplate = async (
return {
name: contractNames.dvMorpho,
content: `
- // SPDX-License-Identifier: MIT
- pragma solidity 0.8.9;
+ // SPDX-License-Identifier: BUSL-1.1
+ pragma solidity 0.8.34;
import "../../DepositVaultWithMorpho.sol";
import "./${contractNames.roles}.sol";
diff --git a/scripts/deploy/codegen/common/templates/dv-mtoken.template.ts b/scripts/deploy/codegen/common/templates/dv-mtoken.template.ts
index fabd91ad..3e0ae115 100644
--- a/scripts/deploy/codegen/common/templates/dv-mtoken.template.ts
+++ b/scripts/deploy/codegen/common/templates/dv-mtoken.template.ts
@@ -20,8 +20,8 @@ export const getDvMTokenContractFromTemplate = async (
return {
name: contractNames.dvMToken,
content: `
- // SPDX-License-Identifier: MIT
- pragma solidity 0.8.9;
+ // SPDX-License-Identifier: BUSL-1.1
+ pragma solidity 0.8.34;
import "../../DepositVaultWithMToken.sol";
import "./${contractNames.roles}.sol";
diff --git a/scripts/deploy/codegen/common/templates/dv.template.ts b/scripts/deploy/codegen/common/templates/dv.template.ts
index c8de0470..c2cc681e 100644
--- a/scripts/deploy/codegen/common/templates/dv.template.ts
+++ b/scripts/deploy/codegen/common/templates/dv.template.ts
@@ -20,8 +20,8 @@ export const getDvContractFromTemplate = async (
return {
name: contractNames.dv,
content: `
- // SPDX-License-Identifier: MIT
- pragma solidity 0.8.9;
+ // SPDX-License-Identifier: BUSL-1.1
+ pragma solidity 0.8.34;
import "../../DepositVault.sol";
import "./${contractNames.roles}.sol";
diff --git a/scripts/deploy/codegen/common/templates/mtoken.template.ts b/scripts/deploy/codegen/common/templates/mtoken.template.ts
index c27ce66b..9772f7d8 100644
--- a/scripts/deploy/codegen/common/templates/mtoken.template.ts
+++ b/scripts/deploy/codegen/common/templates/mtoken.template.ts
@@ -26,8 +26,8 @@ export const getTokenContractFromTemplate = async (
return {
name: contractNames.token,
content: `
- // SPDX-License-Identifier: MIT
- pragma solidity 0.8.9;
+ // SPDX-License-Identifier: BUSL-1.1
+ pragma solidity 0.8.34;
import "../../mToken${isPermissionedMToken ? 'Permissioned' : ''}.sol";
${isPermissionedMToken ? `import "./${contractNames.roles}.sol";` : ''}
diff --git a/scripts/deploy/codegen/common/templates/rv-aave.template.ts b/scripts/deploy/codegen/common/templates/rv-aave.template.ts
index 89e520ae..5cc69366 100644
--- a/scripts/deploy/codegen/common/templates/rv-aave.template.ts
+++ b/scripts/deploy/codegen/common/templates/rv-aave.template.ts
@@ -20,8 +20,8 @@ export const getRvAaveContractFromTemplate = async (
return {
name: contractNames.rvAave,
content: `
- // SPDX-License-Identifier: MIT
- pragma solidity 0.8.9;
+ // SPDX-License-Identifier: BUSL-1.1
+ pragma solidity 0.8.34;
import "../../RedemptionVaultWithAave.sol";
import "./${contractNames.roles}.sol";
diff --git a/scripts/deploy/codegen/common/templates/rv-morpho.template.ts b/scripts/deploy/codegen/common/templates/rv-morpho.template.ts
index 36fc4caa..1fe18ef3 100644
--- a/scripts/deploy/codegen/common/templates/rv-morpho.template.ts
+++ b/scripts/deploy/codegen/common/templates/rv-morpho.template.ts
@@ -20,8 +20,8 @@ export const getRvMorphoContractFromTemplate = async (
return {
name: contractNames.rvMorpho,
content: `
- // SPDX-License-Identifier: MIT
- pragma solidity 0.8.9;
+ // SPDX-License-Identifier: BUSL-1.1
+ pragma solidity 0.8.34;
import "../../RedemptionVaultWithMorpho.sol";
import "./${contractNames.roles}.sol";
diff --git a/scripts/deploy/codegen/common/templates/rv-mtoken.template.ts b/scripts/deploy/codegen/common/templates/rv-mtoken.template.ts
index 6baa4c12..ebd1820c 100644
--- a/scripts/deploy/codegen/common/templates/rv-mtoken.template.ts
+++ b/scripts/deploy/codegen/common/templates/rv-mtoken.template.ts
@@ -20,8 +20,8 @@ export const getRvMTokenContractFromTemplate = async (
return {
name: contractNames.rvMToken,
content: `
- // SPDX-License-Identifier: MIT
- pragma solidity 0.8.9;
+ // SPDX-License-Identifier: BUSL-1.1
+ pragma solidity 0.8.34;
import "../../RedemptionVaultWithMToken.sol";
import "./${contractNames.roles}.sol";
diff --git a/scripts/deploy/codegen/common/templates/rv-swapper.template.ts b/scripts/deploy/codegen/common/templates/rv-swapper.template.ts
index 07422069..9fb078fb 100644
--- a/scripts/deploy/codegen/common/templates/rv-swapper.template.ts
+++ b/scripts/deploy/codegen/common/templates/rv-swapper.template.ts
@@ -20,8 +20,8 @@ export const getRvSwapperContractFromTemplate = async (
return {
name: contractNames.rvSwapper,
content: `
- // SPDX-License-Identifier: MIT
- pragma solidity 0.8.9;
+ // SPDX-License-Identifier: BUSL-1.1
+ pragma solidity 0.8.34;
import "../../RedemptionVaultWithSwapper.sol";
import "./${contractNames.roles}.sol";
diff --git a/scripts/deploy/codegen/common/templates/rv-ustb.template.ts b/scripts/deploy/codegen/common/templates/rv-ustb.template.ts
index fb7cf6af..c4c54ded 100644
--- a/scripts/deploy/codegen/common/templates/rv-ustb.template.ts
+++ b/scripts/deploy/codegen/common/templates/rv-ustb.template.ts
@@ -20,8 +20,8 @@ export const getRvUstbContractFromTemplate = async (
return {
name: contractNames.rvUstb,
content: `
- // SPDX-License-Identifier: MIT
- pragma solidity 0.8.9;
+ // SPDX-License-Identifier: BUSL-1.1
+ pragma solidity 0.8.34;
import "../../RedemptionVaultWithUSTB.sol";
import "./${contractNames.roles}.sol";
diff --git a/scripts/deploy/codegen/common/templates/rv.template.ts b/scripts/deploy/codegen/common/templates/rv.template.ts
index b309a89b..d1b7a33c 100644
--- a/scripts/deploy/codegen/common/templates/rv.template.ts
+++ b/scripts/deploy/codegen/common/templates/rv.template.ts
@@ -21,8 +21,8 @@ export const getRvContractFromTemplate = async (
return {
name: contractNames.rv,
content: `
- // SPDX-License-Identifier: MIT
- pragma solidity 0.8.9;
+ // SPDX-License-Identifier: BUSL-1.1
+ pragma solidity 0.8.34;
import "../../RedemptionVault.sol";
import "./${contractNames.roles}.sol";
diff --git a/scripts/deploy/codegen/common/templates/token-roles.template.ts b/scripts/deploy/codegen/common/templates/token-roles.template.ts
index 1120bb96..1e831682 100644
--- a/scripts/deploy/codegen/common/templates/token-roles.template.ts
+++ b/scripts/deploy/codegen/common/templates/token-roles.template.ts
@@ -21,8 +21,8 @@ export const getTokenRolesContractFromTemplate = async (
return {
name: contractNames.roles,
content: `
- // SPDX-License-Identifier: MIT
- pragma solidity 0.8.9;
+ // SPDX-License-Identifier: BUSL-1.1
+ pragma solidity 0.8.34;
/**
* @title ${contractNames.roles}
diff --git a/scripts/deploy/common/common-vault.ts b/scripts/deploy/common/common-vault.ts
index b0eba545..52c231a6 100644
--- a/scripts/deploy/common/common-vault.ts
+++ b/scripts/deploy/common/common-vault.ts
@@ -42,6 +42,7 @@ export type AddFeeWaivedConfig = {
mToken: MTokenName;
type: VaultType;
};
+ value?: boolean;
toWaive: (
| {
// default: hre.mtoken
@@ -52,7 +53,7 @@ export type AddFeeWaivedConfig = {
)[];
}[];
-export const addFeeWaived = async (
+export const setFeeWaivedAccounts = async (
hre: HardhatRuntimeEnvironment,
token: MTokenName,
) => {
@@ -93,16 +94,21 @@ export const addFeeWaived = async (
vaultContract,
feeWaiveAddress,
feeWaiveLabel,
+ value,
) => {
const waived = await vaultContract.waivedFeeRestriction(feeWaiveAddress!);
- if (waived) {
- console.log('Fee is already waived, skipping...', feeWaiveAddress);
+ if (waived === value) {
+ console.log(
+ `Fee is already ${value ? 'waived' : 'not waived'}, skipping...`,
+ feeWaiveAddress,
+ );
return;
}
- const tx = await vaultContract.populateTransaction.addWaivedFeeAccount(
+ const tx = await vaultContract.populateTransaction.setWaivedFeeAccount(
feeWaiveAddress!,
+ value,
);
const txRes = await sendAndWaitForCustomTxSign(hre, tx, {
@@ -192,6 +198,7 @@ const foreachFeeWaiveAddress = async (
vaultContract: ManageableVault,
feeWaiveAddress: string,
feeWaiveLabel: string,
+ value: boolean,
) => Promise,
) => {
const addresses = getCurrentAddresses(hre);
@@ -222,6 +229,7 @@ const foreachFeeWaiveAddress = async (
typeof toWaive === 'string'
? toWaive
: `${toWaive.mToken ?? token} ${toWaive.type}`,
+ vault.value ?? true,
);
}
}
diff --git a/scripts/deploy/common/data-feed.ts b/scripts/deploy/common/data-feed.ts
index 170ec490..40cd599e 100644
--- a/scripts/deploy/common/data-feed.ts
+++ b/scripts/deploy/common/data-feed.ts
@@ -23,6 +23,7 @@ import {
getCommonContractNames,
getTokenContractNames,
} from '../../../helpers/contracts';
+import { getAllRoles } from '../../../helpers/roles';
import {
CustomAggregatorV3CompatibleFeed,
CustomAggregatorV3CompatibleFeedGrowth,
@@ -214,7 +215,7 @@ const setRoundData = async (
tx = await aggregator.populateTransaction.setRoundData(
networkConfig.data,
- networkConfig.dataTimestamp ?? currentTimestamp,
+ networkConfig.dataTimestamp ?? currentTimestamp - 1,
networkConfig.apr,
);
log = `${token} set price to ${formatUnits(
@@ -402,52 +403,25 @@ const updateExpectedAnswers = async (
)}`,
);
- const action = isMToken ? 'update-feed-mtoken' : 'update-feed-ptoken';
-
- const setMax = async () => {
- if (newMax.eq(currentMax)) {
- console.log('maxExpectedAnswer is already up to date, skipping');
- return;
- }
- const tx = await dataFeed.populateTransaction.setMaxExpectedAnswer(newMax);
- const log = `${token} set maxExpectedAnswer to ${formatUnits(
- newMax,
- aggregatorDecimals,
- )}`;
- const txRes = await sendAndWaitForCustomTxSign(hre, tx, {
- action,
- comment: log,
- });
- console.log(log, txRes);
- };
-
- const setMin = async () => {
- if (newMin.eq(currentMin)) {
- console.log('minExpectedAnswer is already up to date, skipping');
- return;
- }
- const tx = await dataFeed.populateTransaction.setMinExpectedAnswer(newMin);
- const log = `${token} set minExpectedAnswer to ${formatUnits(
- newMin,
- aggregatorDecimals,
- )}`;
- const txRes = await sendAndWaitForCustomTxSign(hre, tx, {
- action,
- comment: log,
- });
- console.log(log, txRes);
- };
-
- // ordering matters: the contract enforces max > min on every update.
- // when raising the range (e.g. 8 -> 18 decimals migration), max must
- // be raised first; when lowering the range, min must be lowered first.
- if (newMax.gt(currentMin)) {
- await setMax();
- await setMin();
- } else {
- await setMin();
- await setMax();
+ if (newMin.eq(currentMin) && newMax.eq(currentMax)) {
+ console.log('min/max expected answers are already up to date, skipping');
+ return;
}
+
+ const action = isMToken ? 'update-feed-mtoken' : 'update-feed-ptoken';
+ const tx = await dataFeed.populateTransaction.setMinMaxExpectedAnswer(
+ newMax,
+ newMin,
+ );
+ const log = `${token} set expected answers to [${formatUnits(
+ newMin,
+ aggregatorDecimals,
+ )}, ${formatUnits(newMax, aggregatorDecimals)}]`;
+ const txRes = await sendAndWaitForCustomTxSign(hre, tx, {
+ action,
+ comment: log,
+ });
+ console.log(log, txRes);
};
const getAggregatorContract = async (
@@ -523,15 +497,6 @@ export const deployPaymentTokenDataFeed = async (
const compositeConfig = networkConfig as DeployDataFeedConfigComposite;
const feedType = compositeConfig.feedType;
- const contractName =
- feedType === 'multiply'
- ? getCommonContractNames().dataFeedMultiply
- : getCommonContractNames().dataFeedComposite;
-
- if (!contractName) {
- throw new Error(`${feedType} data feed contract name is not set`);
- }
-
if (
!tokenAddresses?.denominator?.dataFeed ||
!tokenAddresses?.numerator?.dataFeed
@@ -544,7 +509,6 @@ export const deployPaymentTokenDataFeed = async (
hre,
tokenAddresses.numerator.dataFeed,
tokenAddresses.denominator.dataFeed,
- contractName,
compositeConfig,
);
} else {
@@ -552,13 +516,10 @@ export const deployPaymentTokenDataFeed = async (
hre,
tokenAddresses.numerator.dataFeed,
tokenAddresses.denominator.dataFeed,
- contractName,
compositeConfig,
);
}
} else {
- const contractName = getCommonContractNames().dataFeed;
-
let aggregator: string | undefined;
let config: DeployDataFeedConfigRegular;
@@ -574,15 +535,17 @@ export const deployPaymentTokenDataFeed = async (
throw new Error('Incorrect params');
}
- if (!contractName) {
- throw new Error('Data feed contract name is not set');
- }
-
if (!aggregator) {
throw new Error('Token config is not found or aggregator is not set');
}
- await deployTokenDataFeed(hre, aggregator, contractName, config);
+ const roles = getAllRoles();
+ await deployTokenDataFeed(
+ hre,
+ aggregator,
+ roles.common.defaultAdmin,
+ config,
+ );
}
};
@@ -602,9 +565,11 @@ export const deployPaymentTokenCustomAggregator = async (
paymentToken
]?.customAggregator;
+ const roles = getAllRoles();
await deployCustomAggregator(
hre,
customAggregatorContractName,
+ roles.common.defaultAdmin,
networkConfig,
);
};
@@ -625,20 +590,15 @@ export const deployMTokenDataFeed = async (
throw new Error('Token config is not found or customFeed is not set');
}
- const dataFeedContractName = getTokenContractNames(token).dataFeed;
-
- if (!dataFeedContractName) {
- throw new Error('Data feed contract name is not set');
- }
-
if (tokenAddresses?.customFeedAdjusted) {
console.log('Using single adjusted feed as aggregator for DataFeed');
}
+ const roles = getAllRoles();
await deployTokenDataFeed(
hre,
aggregator,
- dataFeedContractName,
+ roles.tokenRoles[token].customFeedAdmin!,
getDeploymentGenericConfig(hre, token, 'dataFeed'),
);
};
@@ -687,18 +647,12 @@ export const deployMTokenDataFeedDv = async (
throw new Error('Token config is not found or customFeedDv is not set');
}
- const dataFeedContractName = getTokenContractNames(token).dataFeed;
-
- if (!dataFeedContractName) {
- throw new Error('Data feed contract name is not set');
- }
-
+ const roles = getAllRoles();
await deployTokenDataFeed(
hre,
tokenAddresses.customFeedDv,
- dataFeedContractName,
- getDeploymentGenericConfigOptional(token, 'dataFeedDv') ??
- getDeploymentGenericConfig(hre, token, 'dataFeed'),
+ roles.tokenRoles[token].customFeedAdmin!,
+ getDeploymentGenericConfig(hre, token, 'dataFeed'),
);
};
@@ -713,18 +667,12 @@ export const deployMTokenDataFeedRv = async (
throw new Error('Token config is not found or customFeedRv is not set');
}
- const dataFeedContractName = getTokenContractNames(token).dataFeed;
-
- if (!dataFeedContractName) {
- throw new Error('Data feed contract name is not set');
- }
-
+ const roles = getAllRoles();
await deployTokenDataFeed(
hre,
tokenAddresses.customFeedRv,
- dataFeedContractName,
- getDeploymentGenericConfigOptional(token, 'dataFeedRv') ??
- getDeploymentGenericConfig(hre, token, 'dataFeed'),
+ roles.tokenRoles[token].customFeedAdmin!,
+ getDeploymentGenericConfig(hre, token, 'dataFeed'),
);
};
@@ -743,13 +691,24 @@ export const deployMTokenCustomAggregator = async (
throw new Error('Custom aggregator contract name is not set');
}
- await deployCustomAggregator(hre, customAggregatorContractName, config);
+ const roles = getAllRoles();
+
+ if (!roles.tokenRoles[token].customFeedAdmin) {
+ throw new Error('Custom feed admin role is not set');
+ }
+
+ await deployCustomAggregator(
+ hre,
+ customAggregatorContractName,
+ roles.tokenRoles[token].customFeedAdmin,
+ config,
+ );
};
const deployTokenDataFeed = async (
hre: HardhatRuntimeEnvironment,
aggregator: string,
- dataFeedContractName: string,
+ adminRole: string,
networkConfig?: DeployDataFeedConfigRegular,
) => {
const addresses = getCurrentAddresses(hre);
@@ -758,20 +717,27 @@ const deployTokenDataFeed = async (
throw new Error('Network config is not found');
}
- await deployAndVerifyProxy(hre, dataFeedContractName, [
- addresses?.accessControl,
- aggregator,
- networkConfig.healthyDiff ?? 2592000,
- networkConfig.minAnswer ?? parseUnits('0.1', 8),
- networkConfig.maxAnswer ?? parseUnits('1000', 8),
- ]);
+ await deployAndVerifyProxy(
+ hre,
+ getCommonContractNames().dataFeed,
+ [
+ addresses?.accessControl,
+ aggregator,
+ networkConfig.healthyDiff ?? 2592000,
+ networkConfig.minAnswer ?? parseUnits('0.1', 8),
+ networkConfig.maxAnswer ?? parseUnits('1000', 8),
+ ],
+ undefined,
+ {
+ constructorArgs: [adminRole],
+ },
+ );
};
const deployTokenDataFeedComposite = async (
hre: HardhatRuntimeEnvironment,
numeratorFeed: string,
denominatorFeed: string,
- dataFeedContractName: string,
networkConfig?: DeployDataFeedConfigComposite,
) => {
const addresses = getCurrentAddresses(hre);
@@ -780,7 +746,7 @@ const deployTokenDataFeedComposite = async (
throw new Error('Network config is not found');
}
- await deployAndVerifyProxy(hre, dataFeedContractName, [
+ await deployAndVerifyProxy(hre, getCommonContractNames().dataFeedComposite, [
addresses?.accessControl,
numeratorFeed,
denominatorFeed,
@@ -793,7 +759,6 @@ export const deployTokenDataFeedMultiply = async (
hre: HardhatRuntimeEnvironment,
numeratorFeed: string,
denominatorFeed: string,
- dataFeedContractName: string,
networkConfig?: DeployDataFeedConfigComposite,
) => {
const addresses = getCurrentAddresses(hre);
@@ -802,7 +767,7 @@ export const deployTokenDataFeedMultiply = async (
throw new Error('Network config is not found');
}
- await deployAndVerifyProxy(hre, dataFeedContractName, [
+ await deployAndVerifyProxy(hre, getCommonContractNames().dataFeedMultiply, [
addresses?.accessControl,
numeratorFeed,
denominatorFeed,
@@ -814,6 +779,7 @@ export const deployTokenDataFeedMultiply = async (
const deployCustomAggregator = async (
hre: HardhatRuntimeEnvironment,
customAggregatorContractName: string,
+ adminRole: string,
networkConfig?: DeployCustomAggregatorConfig,
) => {
const addresses = getCurrentAddresses(hre);
@@ -840,6 +806,9 @@ const deployCustomAggregator = async (
customAggregatorContractName,
params,
undefined,
+ {
+ constructorArgs: [adminRole],
+ },
);
};
diff --git a/scripts/deploy/common/dv.ts b/scripts/deploy/common/dv.ts
index f7093775..3926d6e1 100644
--- a/scripts/deploy/common/dv.ts
+++ b/scripts/deploy/common/dv.ts
@@ -10,14 +10,16 @@ import {
sanctionListContracts,
ustbContracts,
} from '../../../config/constants/addresses';
-import { getTokenContractNames } from '../../../helpers/contracts';
+import { getCommonContractNames } from '../../../helpers/contracts';
+import { getAllRoles } from '../../../helpers/roles';
import {
DepositVault,
DepositVaultWithMToken,
DepositVaultWithUSTB,
} from '../../../typechain-types';
-export type DeployDvConfigCommon = {
+export type DeployDvConfigCommonLegacy = {
+ version?: 'v1';
feeReceiver?: string;
tokensReceiver?: `0x${string}` | RedemptionVaultType;
instantDailyLimit: BigNumberish;
@@ -33,11 +35,41 @@ export type DeployDvConfigCommon = {
*/
minMTokenAmountForFirstDeposit?: BigNumberish;
/**
+ * @deprecated Cap now lives on mToken. Kept optional for config compatibility;
+ * not passed to DepositVault.initialize. Set via mToken initialize / setMaxSupplyCap.
* @default constants.MaxUint256
*/
maxSupplyCap?: BigNumberish;
};
+type DeployVaultConfigCommon = {
+ version: 'v2';
+ variationTolerance: BigNumberish;
+ minAmount?: BigNumberish;
+ instantFee: BigNumberish;
+ enableSanctionsList?: boolean;
+ tokensReceiver?: `0x${string}` | RedemptionVaultType;
+ minInstantFee?: BigNumberish;
+ maxInstantFee?: BigNumberish;
+ maxInstantShare?: BigNumberish;
+ maxApproveRequestId?: BigNumberish;
+ sequentialRequestProcessing?: boolean;
+};
+
+type DeployDvConfigCommonNew = DeployVaultConfigCommon & {
+ minMTokenAmountForFirstDeposit?: BigNumberish;
+ /**
+ * @deprecated Cap now lives on mToken. Kept optional for config compatibility;
+ * not passed to DepositVault.initialize. Set via mToken initialize / setMaxSupplyCap.
+ */
+ maxSupplyCap?: BigNumberish;
+ maxAmountPerRequest?: BigNumberish;
+};
+
+export type DeployDvConfigCommon =
+ | DeployDvConfigCommonLegacy
+ | DeployDvConfigCommonNew;
+
export type DeployDvRegularConfig = DeployDvConfigCommon & {
type?: 'REGULAR';
};
@@ -75,33 +107,25 @@ export const deployDepositVault = async (
token: MTokenName,
type: 'dv' | 'dvUstb' | 'dvAave' | 'dvMorpho' | 'dvMToken',
) => {
+ if (token.startsWith('TAC')) {
+ throw new Error('TAC tokens are not supported anymore');
+ }
+
const addresses = getCurrentAddresses(hre);
const deployer = await getDeployer(hre);
const tokenAddresses = addresses?.[token];
const networkConfig = getNetworkConfig(hre, token, type);
- if (!tokenAddresses) {
- throw new Error('Token config is not found');
+ if (networkConfig.version !== 'v2') {
+ throw new Error('v1 configs are not supported anymore');
}
- let dataFeed: string | undefined;
-
- if (token.startsWith('TAC')) {
- const originalTokenName = token.replace('TAC', '');
- dataFeed = addresses?.[originalTokenName as MTokenName]?.dataFeed;
- console.log(
- `Detected TAC wrapper, will be used data feed from ${originalTokenName}: ${dataFeed}`,
- );
- } else {
- dataFeed = tokenAddresses?.dataFeedDv ?? tokenAddresses?.dataFeed;
+ if (!tokenAddresses) {
+ throw new Error('Token config is not found');
}
- const dvContractName = getTokenContractNames(token)[type];
-
- if (!dvContractName) {
- throw new Error('DV contract name is not found');
- }
+ const dataFeed = tokenAddresses?.dataFeedDv ?? tokenAddresses?.dataFeed;
const sanctionsList = networkConfig.enableSanctionsList
? sanctionListContracts[hre.network.config.chainId!]
@@ -142,39 +166,54 @@ export const deployDepositVault = async (
extraParams.push(networkConfig.mTokenDepositVault);
}
+ const ustbMTokenInitializer =
+ 'initialize((uint256,uint256,uint256,address,address,address,address,address,uint256,uint256,uint256,uint256,bool),(uint256,uint256),address)' as const;
const params = [
- addresses?.accessControl,
{
+ variationTolerance: networkConfig.variationTolerance,
+ minAmount: networkConfig.minAmount ?? 0,
+ instantFee: networkConfig.instantFee,
+ ac: addresses?.accessControl,
+ sanctionsList,
mToken: tokenAddresses?.token,
mTokenDataFeed: dataFeed,
+ tokensReceiver: networkConfig.tokensReceiver ?? deployer.address,
+ minInstantFee: networkConfig.minInstantFee ?? 0,
+ maxInstantFee: networkConfig.maxInstantFee ?? 100_00,
+ maxInstantShare: networkConfig.maxInstantShare ?? 100_00,
+ maxApproveRequestId: networkConfig.maxApproveRequestId ?? 100,
+ sequentialRequestProcessing:
+ networkConfig.sequentialRequestProcessing ?? false,
},
{
- feeReceiver: networkConfig.feeReceiver ?? deployer.address,
- tokensReceiver,
- },
- {
- instantDailyLimit: networkConfig.instantDailyLimit,
- instantFee: networkConfig.instantFee,
+ minMTokenAmountForFirstDeposit:
+ networkConfig.minMTokenAmountForFirstDeposit ?? 0,
+ maxAmountPerRequest:
+ networkConfig.maxAmountPerRequest ?? constants.MaxUint256,
},
- sanctionsList,
- networkConfig.variationTolerance,
- networkConfig.minAmount ?? 0,
- networkConfig.minMTokenAmountForFirstDeposit ?? 0,
- networkConfig.maxSupplyCap ?? constants.MaxUint256,
...extraParams,
] as
| Parameters
- | Parameters<
- DepositVaultWithUSTB['initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,uint256,uint256,address)']
- >
- | Parameters<
- DepositVaultWithMToken['initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,uint256,uint256,address)']
- >;
-
- await deployAndVerifyProxy(hre, dvContractName, params, undefined, {
- initializer:
- networkConfig.type === 'USTB' || networkConfig.type === 'MTOKEN'
- ? 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,uint256,uint256,address)'
- : 'initialize',
- });
+ | Parameters
+ | Parameters;
+
+ const allRoles = getAllRoles();
+ const constructorParams = [
+ allRoles.tokenRoles[token].depositVaultAdmin,
+ allRoles.tokenRoles[token].greenlisted,
+ ];
+
+ await deployAndVerifyProxy(
+ hre,
+ getCommonContractNames()[type],
+ params,
+ undefined,
+ {
+ constructorArgs: constructorParams,
+ initializer:
+ networkConfig.type === 'USTB' || networkConfig.type === 'MTOKEN'
+ ? ustbMTokenInitializer
+ : 'initialize',
+ },
+ );
};
diff --git a/scripts/deploy/common/roles.ts b/scripts/deploy/common/roles.ts
index 388c88d6..2180c515 100644
--- a/scripts/deploy/common/roles.ts
+++ b/scripts/deploy/common/roles.ts
@@ -63,7 +63,7 @@ export const grantAllProductRoles = async (
allRoles.common.greenlistedOperator,
tokenRoles.burner,
tokenRoles.minter,
- tokenRoles.pauser,
+ tokenRoles.tokenManager,
];
const vaultManagerRoles = [
@@ -132,8 +132,11 @@ export const grantAllProductRoles = async (
await sendAndWaitForCustomTxSign(
hre,
await accessControl.populateTransaction.grantRoleMult(
- rolesToGrant,
- addressesToGrant,
+ rolesToGrant.map((role, index) => ({
+ role,
+ account: addressesToGrant[index],
+ delay: 0, // TODO: add default delay?
+ })),
),
{
action: 'update-ac',
@@ -156,8 +159,7 @@ export const revokeDefaultRolesFromDeployer = async (
await sendAndWaitForCustomTxSign(
hre,
await accessControl.populateTransaction.revokeRoleMult(
- roles,
- roles.map(() => deployer.address),
+ roles.map((role) => ({ role, account: deployer.address })),
),
{
action: 'deployer',
@@ -188,7 +190,7 @@ export const grantDefaultAdminRoleToAcAdmin = async (
await sendAndWaitForCustomTxSign(
hre,
- await accessControl.populateTransaction.grantRole(
+ await accessControl.populateTransaction['grantRole(bytes32,address)'](
allRoles.common.defaultAdmin,
networkConfig?.acAdminAddress ?? acAdminAddress,
),
diff --git a/scripts/deploy/common/rv.ts b/scripts/deploy/common/rv.ts
index 2f8de38a..3656f762 100644
--- a/scripts/deploy/common/rv.ts
+++ b/scripts/deploy/common/rv.ts
@@ -9,16 +9,17 @@ import {
getCurrentAddresses,
RedemptionVaultType,
sanctionListContracts,
+ TokenAddresses,
} from '../../../config/constants/addresses';
-import { getTokenContractNames } from '../../../helpers/contracts';
+import { getCommonContractNames } from '../../../helpers/contracts';
+import { getAllRoles } from '../../../helpers/roles';
import {
- MBasisRedemptionVaultWithSwapper,
RedemptionVault,
RedemptionVaultWithMToken,
- RedemptionVaultWIthBUIDL,
} from '../../../typechain-types';
-export type DeployRvConfigCommon = {
+export type DeployRvConfigCommonLegacy = {
+ version?: 'v1';
feeReceiver?: string;
tokensReceiver?: string;
instantDailyLimit: BigNumberish;
@@ -44,16 +45,19 @@ export type DeployRvConfigCommon = {
minFiatRedeemAmount?: BigNumberish;
};
-export type DeployRvRegularConfig = {
- type: 'REGULAR';
-} & DeployRvConfigCommon;
-
-export type DeployRvBuidlConfig = {
- type: 'BUIDL';
- buidlRedemption: string;
- minBuidlBalance: BigNumberish;
- minBuidlToRedeem: BigNumberish;
-} & DeployRvConfigCommon;
+type DeployVaultConfigCommon = {
+ version: 'v2';
+ variationTolerance: BigNumberish;
+ minAmount?: BigNumberish;
+ instantFee: BigNumberish;
+ enableSanctionsList?: boolean;
+ tokensReceiver?: string;
+ minInstantFee?: BigNumberish;
+ maxInstantFee?: BigNumberish;
+ maxInstantShare?: BigNumberish;
+ maxApproveRequestId?: BigNumberish;
+ sequentialRequestProcessing?: boolean;
+};
type SwapperVault =
| {
@@ -62,6 +66,25 @@ type SwapperVault =
}
| 'dummy';
+type DeployRvConfigCommonNew = DeployVaultConfigCommon & {
+ requestRedeemer?: string;
+ loanConfig?: {
+ loanLp?: string;
+ loanRepaymentAddress?: string;
+ loanApr: BigNumberish;
+ loanSwapperVault: Exclude;
+ };
+};
+
+export type DeployRvConfigCommon =
+ | DeployRvConfigCommonLegacy
+ | DeployRvConfigCommonNew;
+
+export type DeployRvRegularConfig = {
+ type: 'REGULAR';
+} & DeployRvConfigCommon;
+
+// TODO: remove
export type DeployRvSwapperConfig = {
type: 'SWAPPER';
swapperVault: SwapperVault;
@@ -83,7 +106,6 @@ export type DeployRvMTokenConfig = {
export type DeployRvConfig =
| DeployRvRegularConfig
- | DeployRvBuidlConfig
| DeployRvSwapperConfig
| DeployRvAaveConfig
| DeployRvMorphoConfig
@@ -91,11 +113,48 @@ export type DeployRvConfig =
const DUMMY_ADDRESS = '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF';
+const resolveLoanAddresses = async (
+ hre: HardhatRuntimeEnvironment,
+ networkConfig: DeployRvConfigCommonNew,
+ addresses: Record,
+) => {
+ if (!networkConfig.loanConfig) {
+ return {
+ loanLp: constants.AddressZero,
+ loanRepaymentAddress: constants.AddressZero,
+ loanSwapperVault: constants.AddressZero,
+ loanApr: constants.AddressZero,
+ };
+ }
+ const swapperVault = networkConfig.loanConfig.loanSwapperVault;
+
+ const swapperVaultAddress =
+ addresses[swapperVault.mToken]?.[swapperVault.redemptionVaultType];
+
+ if (!swapperVaultAddress) {
+ throw new Error('Swapper vault address is not found');
+ }
+
+ const deployer = await getDeployer(hre);
+
+ return {
+ loanLp: networkConfig.loanConfig.loanLp ?? deployer.address,
+ loanRepaymentAddress:
+ networkConfig.loanConfig.loanRepaymentAddress ?? deployer.address,
+ loanSwapperVault: swapperVaultAddress,
+ loanApr: networkConfig.loanConfig.loanApr,
+ };
+};
+
export const deployRedemptionVault = async (
hre: HardhatRuntimeEnvironment,
token: MTokenName,
- type: 'rv' | 'rvBuidl' | 'rvSwapper' | 'rvAave' | 'rvMorpho' | 'rvMToken',
+ type: 'rv' | 'rvSwapper' | 'rvAave' | 'rvMorpho' | 'rvMToken',
) => {
+ if (token.startsWith('TAC')) {
+ throw new Error('TAC tokens are not supported anymore');
+ }
+
const addresses = getCurrentAddresses(hre);
const deployer = await getDeployer(hre);
const tokenAddresses = addresses?.[token];
@@ -106,60 +165,23 @@ export const deployRedemptionVault = async (
throw new Error('Token config is not found');
}
- const contractName = getTokenContractNames(token)[type];
+ if (networkConfig.version !== 'v2') {
+ throw new Error('v1 configs are not supported anymore');
+ }
- if (!contractName) {
- throw new Error('Unsupported token/type combination');
+ if (networkConfig.type === 'SWAPPER') {
+ throw new Error('Swapper configs are not supported anymore');
}
+ const allRoles = getAllRoles();
+
const extraParams: unknown[] = [];
if (networkConfig.type === 'MTOKEN') {
extraParams.push(networkConfig.redemptionVault);
- } else if (networkConfig.type === 'BUIDL') {
- extraParams.push(networkConfig.buidlRedemption);
- extraParams.push(networkConfig.minBuidlToRedeem);
- extraParams.push(networkConfig.minBuidlBalance);
- } else if (networkConfig.type === 'SWAPPER') {
- const swapperVault = networkConfig.swapperVault;
-
- let swapperVaultAddress: string | undefined;
-
- if (swapperVault === 'dummy') {
- swapperVaultAddress = DUMMY_ADDRESS;
- } else {
- swapperVaultAddress =
- addresses[swapperVault.mToken]?.[swapperVault.redemptionVaultType];
- }
-
- if (!swapperVaultAddress) {
- throw new Error('Swapper vault address is not found');
- }
-
- if (swapperVaultAddress === DUMMY_ADDRESS) {
- console.log('Using dummy swapper vault address');
- }
-
- const liquidityProvider =
- networkConfig.liquidityProvider === 'dummy'
- ? DUMMY_ADDRESS
- : networkConfig.liquidityProvider ?? deployer.address;
-
- extraParams.push(swapperVaultAddress);
- extraParams.push(liquidityProvider);
}
- let dataFeed: string | undefined;
-
- if (token.startsWith('TAC')) {
- const originalTokenName = token.replace('TAC', '');
- dataFeed = addresses?.[originalTokenName as MTokenName]?.dataFeed;
- console.log(
- `Detected TAC wrapper, will be used data feed from ${originalTokenName}: ${dataFeed}`,
- );
- } else {
- dataFeed = tokenAddresses?.dataFeedRv ?? tokenAddresses?.dataFeed;
- }
+ const dataFeed = tokenAddresses?.dataFeedRv ?? tokenAddresses?.dataFeed;
const sanctionsList = networkConfig.enableSanctionsList
? sanctionListContracts[hre.network.config.chainId!]
@@ -169,52 +191,55 @@ export const deployRedemptionVault = async (
throw new Error('Sanctions list address is not found');
}
+ // FIXME: fix according to new initialize params
const params = [
- addresses?.accessControl,
{
+ variationTolerance: networkConfig.variationTolerance,
+ minAmount: networkConfig.minAmount ?? parseUnits('0', 18),
+ instantFee: networkConfig.instantFee,
+ ac: addresses?.accessControl,
+ sanctionsList,
mToken: tokenAddresses?.token,
mTokenDataFeed: dataFeed,
- },
- {
- feeReceiver: networkConfig.feeReceiver ?? deployer.address,
tokensReceiver: networkConfig.tokensReceiver ?? deployer.address,
+ minInstantFee: networkConfig.minInstantFee ?? 0,
+ maxInstantFee: networkConfig.maxInstantFee ?? 100_00,
+ maxInstantShare: networkConfig.maxInstantShare ?? 100_00,
+ maxApproveRequestId: networkConfig.maxApproveRequestId ?? 100,
+ sequentialRequestProcessing:
+ networkConfig.sequentialRequestProcessing ?? false,
},
{
- instantDailyLimit: networkConfig.instantDailyLimit,
- instantFee: networkConfig.instantFee,
- },
- sanctionsList,
- networkConfig.variationTolerance,
- networkConfig.minAmount ?? parseUnits('0', 18),
- {
- fiatAdditionalFee:
- networkConfig.fiatAdditionalFee ?? parseUnits('0.1', 2),
- fiatFlatFee: networkConfig.fiatFlatFee ?? parseUnits('30', 18),
- minFiatRedeemAmount:
- networkConfig.minFiatRedeemAmount ?? parseUnits('1000', 18),
+ requestRedeemer: networkConfig.requestRedeemer ?? deployer.address,
+ ...(await resolveLoanAddresses(
+ hre,
+ networkConfig,
+ addresses as Record,
+ )),
},
- networkConfig.requestRedeemer ?? deployer.address,
...extraParams,
] as
| Parameters
| Parameters<
- RedemptionVaultWIthBUIDL['initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,(uint256,uint256,uint256),address,address,uint256,uint256)']
- >
- | Parameters<
- MBasisRedemptionVaultWithSwapper['initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,(uint256,uint256,uint256),address,address,address)']
- >
- | Parameters<
- RedemptionVaultWithMToken['initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,(uint256,uint256,uint256),address,address)']
+ RedemptionVaultWithMToken['initialize((uint256,uint256,uint256,address,address,address,address,address,uint256,uint256,uint256,uint256,bool),(address,address,address,address,uint256),address)']
>;
- await deployAndVerifyProxy(hre, contractName, params, undefined, {
- initializer:
- networkConfig.type === 'SWAPPER'
- ? 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,(uint256,uint256,uint256),address,address,address)'
- : networkConfig.type === 'BUIDL'
- ? 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,(uint256,uint256,uint256),address,address,uint256,uint256)'
- : networkConfig.type === 'MTOKEN'
- ? 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,(uint256,uint256,uint256),address,address)'
- : 'initialize',
- });
+ const constructorParams = [
+ allRoles.tokenRoles[token].redemptionVaultAdmin,
+ allRoles.tokenRoles[token].greenlisted,
+ ];
+
+ await deployAndVerifyProxy(
+ hre,
+ getCommonContractNames()[type],
+ params,
+ undefined,
+ {
+ constructorArgs: constructorParams,
+ initializer:
+ networkConfig.type === 'MTOKEN'
+ ? 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,(uint256,uint256,uint256),address,address)'
+ : 'initialize',
+ },
+ );
};
diff --git a/scripts/deploy/common/token.ts b/scripts/deploy/common/token.ts
index fd869816..0637a00a 100644
--- a/scripts/deploy/common/token.ts
+++ b/scripts/deploy/common/token.ts
@@ -1,10 +1,13 @@
+import { constants } from 'ethers';
import { HardhatRuntimeEnvironment } from 'hardhat/types';
-import { deployAndVerifyProxy } from './utils';
+import { deployAndVerifyProxy, getDeployer } from './utils';
import { MTokenName } from '../../../config';
import { getCurrentAddresses } from '../../../config/constants/addresses';
-import { getTokenContractNames } from '../../../helpers/contracts';
+import { getCommonContractNames } from '../../../helpers/contracts';
+import { mTokensMetadata } from '../../../helpers/mtokens-metadata';
+import { getAllRoles } from '../../../helpers/roles';
export const deployMToken = async (
hre: HardhatRuntimeEnvironment,
@@ -15,7 +18,33 @@ export const deployMToken = async (
if (!addresses?.accessControl)
throw new Error('Access control address is not set');
- const tokenContractName = getTokenContractNames(token).token;
+ const allRoles = getAllRoles();
+ const roles = allRoles.tokenRoles[token];
+ const metadata = mTokensMetadata[token];
+ const deployer = await getDeployer(hre);
+ const isPermissioned = !!metadata.isPermissioned;
- await deployAndVerifyProxy(hre, tokenContractName, [addresses.accessControl]);
+ await deployAndVerifyProxy(
+ hre,
+ getCommonContractNames().token,
+ [
+ addresses.accessControl,
+ deployer.address,
+ constants.MaxUint256,
+ isPermissioned,
+ false,
+ metadata.name,
+ metadata.symbol,
+ ],
+ undefined,
+ {
+ constructorArgs: [
+ roles.tokenManager,
+ roles.minter,
+ roles.burner,
+ roles.greenlisted,
+ roles.minBalanceExempt,
+ ],
+ },
+ );
};
diff --git a/scripts/deploy/common/types.ts b/scripts/deploy/common/types.ts
index 41e30cb1..19f8aad6 100644
--- a/scripts/deploy/common/types.ts
+++ b/scripts/deploy/common/types.ts
@@ -27,7 +27,6 @@ import {
} from './roles';
import {
DeployRvAaveConfig,
- DeployRvBuidlConfig,
DeployRvMorphoConfig,
DeployRvMTokenConfig,
DeployRvRegularConfig,
@@ -40,6 +39,7 @@ import { PartialConfigPerNetwork, PaymentTokenName } from '../../../config';
import { VaultType } from '../../../config/constants/addresses';
import { RateLimiter } from '../../../typechain-types';
+// TODO: fix selectors
export const VAULT_FUNCTION_SELECTORS = {
// Deposit vault functions
depositInstant: toFunctionSelector(
@@ -127,7 +127,6 @@ export type DeploymentConfig = {
dvMorpho?: DeployDvMorphoConfig;
dvMToken?: DeployDvMTokenConfig;
rv?: DeployRvRegularConfig;
- rvBuidl?: DeployRvBuidlConfig;
rvSwapper?: DeployRvSwapperConfig;
rvAave?: DeployRvAaveConfig;
rvMorpho?: DeployRvMorphoConfig;
diff --git a/scripts/deploy/common/utils.ts b/scripts/deploy/common/utils.ts
index 8ca74c11..e64a860e 100644
--- a/scripts/deploy/common/utils.ts
+++ b/scripts/deploy/common/utils.ts
@@ -175,6 +175,7 @@ export const deployAndVerifyProxy = async (
await tryEtherscanVerifyImplementation(
hre,
deployment.address,
+ contractName,
...(opts?.constructorArgs ?? []),
);
diff --git a/scripts/deploy/common/vault-resolver.ts b/scripts/deploy/common/vault-resolver.ts
index d8debb03..01e9a7dd 100644
--- a/scripts/deploy/common/vault-resolver.ts
+++ b/scripts/deploy/common/vault-resolver.ts
@@ -19,13 +19,11 @@ export const routingRedemptionVaultPriority: RedemptionVaultType[] = [
'redemptionVaultAave',
'redemptionVaultMorpho',
'redemptionVault',
- 'redemptionVaultBuidl',
];
export const roleGrantRedemptionVaultPriority: RedemptionVaultType[] = [
'redemptionVaultMToken',
'redemptionVaultSwapper',
- 'redemptionVaultBuidl',
'redemptionVaultUstb',
'redemptionVaultAave',
'redemptionVaultMorpho',
diff --git a/scripts/deploy/configs/hypeBTC.ts b/scripts/deploy/configs/hypeBTC.ts
index dcafe54b..3b56806f 100644
--- a/scripts/deploy/configs/hypeBTC.ts
+++ b/scripts/deploy/configs/hypeBTC.ts
@@ -44,7 +44,7 @@ export const hypeBTCDeploymentConfig: DeploymentConfig = {
liquidityProvider: undefined,
swapperVault: {
mToken: 'mTBILL',
- redemptionVaultType: 'redemptionVaultBuidl',
+ redemptionVaultType: 'redemptionVaultUstb',
},
},
},
diff --git a/scripts/deploy/configs/mAPOLLO.ts b/scripts/deploy/configs/mAPOLLO.ts
index c25223f9..4b9c9a38 100644
--- a/scripts/deploy/configs/mAPOLLO.ts
+++ b/scripts/deploy/configs/mAPOLLO.ts
@@ -44,7 +44,7 @@ export const mAPOLLODeploymentConfig: DeploymentConfig = {
liquidityProvider: '0x0461bD693caE49bE9d030E5c212e080F9c78B846',
swapperVault: {
mToken: 'mTBILL',
- redemptionVaultType: 'redemptionVaultBuidl',
+ redemptionVaultType: 'redemptionVaultUstb',
},
enableSanctionsList: true,
},
diff --git a/scripts/deploy/configs/mBASIS.ts b/scripts/deploy/configs/mBASIS.ts
index 6d291a82..ae348b37 100644
--- a/scripts/deploy/configs/mBASIS.ts
+++ b/scripts/deploy/configs/mBASIS.ts
@@ -44,7 +44,7 @@ export const mBASISDeploymentConfig: DeploymentConfig = {
liquidityProvider: undefined,
swapperVault: {
mToken: 'mTBILL',
- redemptionVaultType: 'redemptionVaultBuidl',
+ redemptionVaultType: 'redemptionVaultUstb',
},
},
},
@@ -75,7 +75,7 @@ export const mBASISDeploymentConfig: DeploymentConfig = {
liquidityProvider: '0x7388e98baCfFF1B3618d7d5bEbeDe483C9526FEd',
swapperVault: {
mToken: 'mTBILL',
- redemptionVaultType: 'redemptionVaultBuidl',
+ redemptionVaultType: 'redemptionVaultUstb',
},
},
},
diff --git a/scripts/deploy/configs/mFARM.ts b/scripts/deploy/configs/mFARM.ts
index 69ad59bc..9a8f0caa 100644
--- a/scripts/deploy/configs/mFARM.ts
+++ b/scripts/deploy/configs/mFARM.ts
@@ -44,7 +44,7 @@ export const mFARMDeploymentConfig: DeploymentConfig = {
liquidityProvider: undefined,
swapperVault: {
mToken: 'mTBILL',
- redemptionVaultType: 'redemptionVaultBuidl',
+ redemptionVaultType: 'redemptionVaultUstb',
},
},
postDeploy: {
diff --git a/scripts/deploy/configs/mFONE.ts b/scripts/deploy/configs/mFONE.ts
index 23695d4a..0878756d 100644
--- a/scripts/deploy/configs/mFONE.ts
+++ b/scripts/deploy/configs/mFONE.ts
@@ -48,7 +48,7 @@ export const mFONEDeploymentConfig: DeploymentConfig = {
liquidityProvider: undefined,
swapperVault: {
mToken: 'mTBILL',
- redemptionVaultType: 'redemptionVaultBuidl',
+ redemptionVaultType: 'redemptionVaultUstb',
},
},
postDeploy: {
@@ -81,7 +81,7 @@ export const mFONEDeploymentConfig: DeploymentConfig = {
liquidityProvider: '0x4dc293e0d6BEfe6FCF9d1FFDEaA5266BD15C3071',
swapperVault: {
mToken: 'mTBILL',
- redemptionVaultType: 'redemptionVaultBuidl',
+ redemptionVaultType: 'redemptionVaultUstb',
},
enableSanctionsList: true,
},
diff --git a/scripts/deploy/configs/mHYPER.ts b/scripts/deploy/configs/mHYPER.ts
index b47d8f82..feaef211 100644
--- a/scripts/deploy/configs/mHYPER.ts
+++ b/scripts/deploy/configs/mHYPER.ts
@@ -46,7 +46,7 @@ export const mHYPERDeploymentConfig: DeploymentConfig = {
liquidityProvider: '0x0461bD693caE49bE9d030E5c212e080F9c78B846',
swapperVault: {
mToken: 'mTBILL',
- redemptionVaultType: 'redemptionVaultBuidl',
+ redemptionVaultType: 'redemptionVaultUstb',
},
enableSanctionsList: true,
},
diff --git a/scripts/deploy/configs/mSL.ts b/scripts/deploy/configs/mSL.ts
index 5686af16..23558326 100644
--- a/scripts/deploy/configs/mSL.ts
+++ b/scripts/deploy/configs/mSL.ts
@@ -1,4 +1,3 @@
-import { constants } from 'ethers';
import { parseUnits } from 'ethers/lib/utils';
import { chainIds } from '../../../config';
@@ -21,30 +20,45 @@ export const mSLDeploymentConfig: DeploymentConfig = {
networkConfigs: {
[chainIds.sepolia]: {
dv: {
- feeReceiver: undefined,
- tokensReceiver: undefined,
- instantDailyLimit: constants.MaxUint256,
+ version: 'v2',
instantFee: parseUnits('1', 2),
- minMTokenAmountForFirstDeposit: parseUnits('100'),
- minAmount: parseUnits('0.01'),
variationTolerance: parseUnits('0.1', 2),
},
- rvSwapper: {
- type: 'SWAPPER',
- feeReceiver: undefined,
- tokensReceiver: undefined,
- instantDailyLimit: constants.MaxUint256,
+ rv: {
+ type: 'REGULAR',
+ version: 'v2',
instantFee: parseUnits('1', 2),
- minAmount: parseUnits('0.01'),
variationTolerance: parseUnits('0.1', 2),
- fiatAdditionalFee: parseUnits('0.1', 2),
- fiatFlatFee: parseUnits('0.1', 18),
- minFiatRedeemAmount: parseUnits('1', 18),
- requestRedeemer: undefined,
- liquidityProvider: undefined,
- swapperVault: {
- mToken: 'mTBILL',
- redemptionVaultType: 'redemptionVaultBuidl',
+ },
+ postDeploy: {
+ grantRoles: {
+ tokenManagerAddress: '0xa0819ae43115420beb161193b8D8Ba64C9f9faCC',
+ oracleManagerAddress: '0xa0819ae43115420beb161193b8D8Ba64C9f9faCC',
+ vaultsManagerAddress: '0xa0819ae43115420beb161193b8D8Ba64C9f9faCC',
+ },
+ addPaymentTokens: {
+ vaults: [
+ {
+ type: 'depositVault',
+ paymentTokens: [
+ {
+ token: 'usdc',
+ },
+ ],
+ },
+ {
+ type: 'redemptionVault',
+ paymentTokens: [
+ {
+ token: 'usdc',
+ },
+ ],
+ },
+ ],
+ },
+ setRoundData: {
+ type: 'REGULAR',
+ data: parseUnits('1', 8),
},
},
},
@@ -74,7 +88,7 @@ export const mSLDeploymentConfig: DeploymentConfig = {
liquidityProvider: '0x0461bD693caE49bE9d030E5c212e080F9c78B846',
swapperVault: {
mToken: 'mTBILL',
- redemptionVaultType: 'redemptionVaultBuidl',
+ redemptionVaultType: 'redemptionVaultUstb',
},
enableSanctionsList: true,
},
diff --git a/scripts/deploy/configs/mTBILL.ts b/scripts/deploy/configs/mTBILL.ts
index 5f2464f0..c9cf7e20 100644
--- a/scripts/deploy/configs/mTBILL.ts
+++ b/scripts/deploy/configs/mTBILL.ts
@@ -22,45 +22,64 @@ export const mTBILLDeploymentConfig: DeploymentConfig = {
networkConfigs: {
[chainIds.sepolia]: {
dv: {
- feeReceiver: undefined,
- tokensReceiver: undefined,
- instantDailyLimit: constants.MaxUint256,
+ version: 'v2',
instantFee: parseUnits('1', 2),
- minMTokenAmountForFirstDeposit: parseUnits('100'),
- minAmount: parseUnits('0.01'),
variationTolerance: parseUnits('0.1', 2),
},
rv: {
+ version: 'v2',
type: 'REGULAR',
- feeReceiver: undefined,
- tokensReceiver: undefined,
- instantDailyLimit: constants.MaxUint256,
- instantFee: parseUnits('1', 2),
- minAmount: parseUnits('0.01'),
- variationTolerance: parseUnits('0.1', 2),
- fiatAdditionalFee: parseUnits('0.1', 2),
- fiatFlatFee: parseUnits('0.1', 18),
- minFiatRedeemAmount: parseUnits('1', 18),
- requestRedeemer: undefined,
- },
- rvBuidl: {
- type: 'BUIDL',
- feeReceiver: undefined,
- tokensReceiver: undefined,
- instantDailyLimit: constants.MaxUint256,
instantFee: parseUnits('1', 2),
- minAmount: parseUnits('0.01'),
variationTolerance: parseUnits('0.1', 2),
- fiatAdditionalFee: parseUnits('0.1', 2),
- fiatFlatFee: parseUnits('0.1', 18),
- minFiatRedeemAmount: parseUnits('1', 18),
- requestRedeemer: undefined,
- buidlRedemption: '0x4cb1479705EA6F0dD63415111aF56eaCfBa019bb', // mocked BUIDL redemption
- minBuidlBalance: parseUnits('250000', 18),
- minBuidlToRedeem: parseUnits('250000', 18),
+ loanConfig: {
+ loanApr: parseUnits('0.1', 2),
+ loanSwapperVault: {
+ mToken: 'mSL',
+ redemptionVaultType: 'redemptionVault',
+ },
+ },
},
postDeploy: {
- grantRoles: {},
+ setRoundData: {
+ type: 'GROWTH',
+ data: parseUnits('1', 8),
+ apr: parseUnits('0.1', 8),
+ },
+ grantRoles: {
+ tokenManagerAddress: '0xa0819ae43115420beb161193b8D8Ba64C9f9faCC',
+ oracleManagerAddress: '0xa0819ae43115420beb161193b8D8Ba64C9f9faCC',
+ vaultsManagerAddress: '0xa0819ae43115420beb161193b8D8Ba64C9f9faCC',
+ },
+ addPaymentTokens: {
+ vaults: [
+ {
+ type: 'depositVault',
+ paymentTokens: [
+ {
+ token: 'usdc',
+ },
+ ],
+ },
+ {
+ type: 'redemptionVault',
+ paymentTokens: [
+ {
+ token: 'usdc',
+ },
+ ],
+ },
+ ],
+ },
+ addFeeWaived: [
+ {
+ fromVault: {
+ mToken: 'mSL',
+ type: 'redemptionVault',
+ },
+ toWaive: [{ mToken: 'mTBILL', type: 'redemptionVault' }],
+ value: true,
+ },
+ ],
axelarIts: {
operator: '0xa0819ae43115420beb161193b8D8Ba64C9f9faCC',
},
@@ -162,26 +181,7 @@ export const mTBILLDeploymentConfig: DeploymentConfig = {
requestRedeemer: '0x1Bd4d8D25Ec7EBA10e94BE71Fd9c6BF672e31E06',
enableSanctionsList: true,
},
- rvBuidl: {
- type: 'BUIDL',
- feeReceiver: '0x875c06A295C41c27840b9C9dfDA7f3d819d8bC6A',
- tokensReceiver: '0x1Bd4d8D25Ec7EBA10e94BE71Fd9c6BF672e31E06',
- instantDailyLimit: parseUnits('1000'),
- instantFee: parseUnits('0.07', 2),
- minAmount: parseUnits('0.1'),
- variationTolerance: parseUnits('0.1', 2),
- fiatAdditionalFee: parseUnits('0.1', 2),
- fiatFlatFee: parseUnits('30', 18),
- minFiatRedeemAmount: parseUnits('1000', 18),
- requestRedeemer: '0x1Bd4d8D25Ec7EBA10e94BE71Fd9c6BF672e31E06',
- enableSanctionsList: true,
- buidlRedemption: '0x31D3F59Ad4aAC0eeE2247c65EBE8Bf6E9E470a53',
- minBuidlBalance: parseUnits('1', 6),
- minBuidlToRedeem: parseUnits('1', 6),
- },
- postDeploy: {
- grantRoles: {},
- },
+ postDeploy: {},
},
[chainIds.base]: {
dv: {
diff --git a/scripts/deploy/deploy_PauseManager.ts b/scripts/deploy/deploy_PauseManager.ts
new file mode 100644
index 00000000..8570131b
--- /dev/null
+++ b/scripts/deploy/deploy_PauseManager.ts
@@ -0,0 +1,24 @@
+import { BigNumber } from 'ethers';
+import { HardhatRuntimeEnvironment } from 'hardhat/types';
+
+import { DeployFunction } from './common/types';
+import { deployAndVerifyProxy } from './common/utils';
+
+import { getCurrentAddresses } from '../../config/constants/addresses';
+import { getCommonContractNames } from '../../helpers/contracts';
+
+const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
+ const addresses = getCurrentAddresses(hre);
+
+ if (!addresses?.accessControl) {
+ throw new Error('Access control address is not set');
+ }
+
+ await deployAndVerifyProxy(hre, getCommonContractNames().pauseManager, [
+ addresses.accessControl,
+ BigNumber.from('0xFFFFFFFF'),
+ 3600,
+ ]);
+};
+
+export default func;
diff --git a/scripts/deploy/deploy_RVBuidl.ts b/scripts/deploy/deploy_RVBuidl.ts
deleted file mode 100644
index 2fddc01d..00000000
--- a/scripts/deploy/deploy_RVBuidl.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { HardhatRuntimeEnvironment } from 'hardhat/types';
-
-import { deployRedemptionVault } from './common';
-import { DeployFunction } from './common/types';
-
-import { getMTokenOrThrow } from '../../helpers/utils';
-
-const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
- const mToken = getMTokenOrThrow(hre);
- await deployRedemptionVault(hre, mToken, 'rvBuidl');
-};
-
-export default func;
diff --git a/scripts/deploy/deploy_RVSwapper.ts b/scripts/deploy/deploy_RVSwapper.ts
deleted file mode 100644
index 60115958..00000000
--- a/scripts/deploy/deploy_RVSwapper.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { HardhatRuntimeEnvironment } from 'hardhat/types';
-
-import { deployRedemptionVault } from './common';
-import { DeployFunction } from './common/types';
-
-import { getMTokenOrThrow } from '../../helpers/utils';
-
-const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
- const mToken = getMTokenOrThrow(hre);
- await deployRedemptionVault(hre, mToken, 'rvSwapper');
-};
-
-export default func;
diff --git a/scripts/deploy/deploy_TimelockController.ts b/scripts/deploy/deploy_TimelockController.ts
new file mode 100644
index 00000000..55b0bbdd
--- /dev/null
+++ b/scripts/deploy/deploy_TimelockController.ts
@@ -0,0 +1,21 @@
+import { HardhatRuntimeEnvironment } from 'hardhat/types';
+
+import { DeployFunction } from './common/types';
+import { deployAndVerifyProxy } from './common/utils';
+
+import { getCurrentAddresses } from '../../config/constants/addresses';
+import { getCommonContractNames } from '../../helpers/contracts';
+
+const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
+ const addresses = getCurrentAddresses(hre);
+
+ if (!addresses?.timelockManager) {
+ throw new Error('Timelock manager address is not set');
+ }
+
+ await deployAndVerifyProxy(hre, getCommonContractNames().timelockController, [
+ addresses.timelockManager,
+ ]);
+};
+
+export default func;
diff --git a/scripts/deploy/deploy_TimelockManager.ts b/scripts/deploy/deploy_TimelockManager.ts
new file mode 100644
index 00000000..c2c42851
--- /dev/null
+++ b/scripts/deploy/deploy_TimelockManager.ts
@@ -0,0 +1,36 @@
+import { HardhatRuntimeEnvironment } from 'hardhat/types';
+
+import { DeployFunction } from './common/types';
+import { deployAndVerifyProxy } from './common/utils';
+
+import { getCurrentAddresses } from '../../config/constants/addresses';
+import { getCommonContractNames } from '../../helpers/contracts';
+
+const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
+ const [
+ councilMember1,
+ councilMember2,
+ councilMember3,
+ councilMember4,
+ councilMember5,
+ ] = await hre.ethers.getSigners();
+ const addresses = getCurrentAddresses(hre);
+
+ if (!addresses?.accessControl) {
+ throw new Error('Access control address is not set');
+ }
+
+ await deployAndVerifyProxy(hre, getCommonContractNames().timelockManager, [
+ addresses.accessControl,
+ 100,
+ [
+ councilMember1.address,
+ councilMember2.address,
+ councilMember3.address,
+ councilMember4.address,
+ councilMember5.address,
+ ],
+ ]);
+};
+
+export default func;
diff --git a/scripts/deploy/misc/acre/deploy_AcreAdapter.ts b/scripts/deploy/misc/acre/deploy_AcreAdapter.ts
index ccaba3aa..d07c6968 100644
--- a/scripts/deploy/misc/acre/deploy_AcreAdapter.ts
+++ b/scripts/deploy/misc/acre/deploy_AcreAdapter.ts
@@ -67,9 +67,6 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
{
value: 'redemptionVaultSwapper',
},
- {
- value: 'redemptionVaultBuidl',
- },
{
value: 'redemptionVaultUstb',
},
diff --git a/scripts/deploy/misc/mocs/deploy_BuidlRedemptionMock.ts b/scripts/deploy/misc/mocs/deploy_BuidlRedemptionMock.ts
deleted file mode 100644
index bc60e22d..00000000
--- a/scripts/deploy/misc/mocs/deploy_BuidlRedemptionMock.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { HardhatRuntimeEnvironment } from 'hardhat/types';
-
-import { getCurrentAddresses } from '../../../../config/constants/addresses';
-import { etherscanVerify } from '../../../../helpers/utils';
-import { RedemptionTest__factory } from '../../../../typechain-types';
-import { DeployFunction } from '../../common/types';
-import { getDeployer } from '../../common/utils';
-
-const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
- const deployer = await getDeployer(hre);
-
- const addresses = getCurrentAddresses(hre);
-
- console.log('Deploying BuidlRedemptionMock...');
-
- const deployment = await new RedemptionTest__factory(deployer).deploy(
- '0xE6e05cf306d41585BEE8Ae48F9f2DD7E0955e6D3', // test BUIDL token on sepolia
- addresses!.paymentTokens!.usdc!.token!,
- );
-
- console.log('Deployed BuidlRedemptionMock :', deployment.address);
-
- if (deployment.deployTransaction) {
- console.log('Waiting 5 blocks...');
- await deployment.deployTransaction.wait(5);
- console.log('Waited.');
- }
- await etherscanVerify(hre, deployment.address);
-};
-
-export default func;
diff --git a/scripts/deploy/post-deploy/add_FeeWaived.ts b/scripts/deploy/post-deploy/add_FeeWaived.ts
index 8d54e4c4..97021ba4 100644
--- a/scripts/deploy/post-deploy/add_FeeWaived.ts
+++ b/scripts/deploy/post-deploy/add_FeeWaived.ts
@@ -1,12 +1,12 @@
import { HardhatRuntimeEnvironment } from 'hardhat/types';
import { getMTokenOrThrow } from '../../../helpers/utils';
-import { addFeeWaived } from '../common/common-vault';
+import { setFeeWaivedAccounts } from '../common/common-vault';
import { DeployFunction } from '../common/types';
const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
const mToken = getMTokenOrThrow(hre);
- await addFeeWaived(hre, mToken);
+ await setFeeWaivedAccounts(hre, mToken);
};
export default func;
diff --git a/scripts/deploy/post-deploy/set_SanctionsList.ts b/scripts/deploy/post-deploy/set_SanctionsList.ts
index d43a1a20..45a4038a 100644
--- a/scripts/deploy/post-deploy/set_SanctionsList.ts
+++ b/scripts/deploy/post-deploy/set_SanctionsList.ts
@@ -44,10 +44,6 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
type: 'redemptionVault',
address: tokenAddresses.redemptionVault,
},
- {
- type: 'redemptionVaultBuidl',
- address: tokenAddresses.redemptionVaultBuidl,
- },
{
type: 'redemptionVaultSwapper',
address: tokenAddresses.redemptionVaultSwapper,
diff --git a/scripts/deploy/post-deploy/wire_AccessControl.ts b/scripts/deploy/post-deploy/wire_AccessControl.ts
new file mode 100644
index 00000000..4de3ca89
--- /dev/null
+++ b/scripts/deploy/post-deploy/wire_AccessControl.ts
@@ -0,0 +1,79 @@
+import { ethers } from 'ethers';
+import { HardhatRuntimeEnvironment } from 'hardhat/types';
+
+import { getCurrentAddresses } from '../../../config/constants/addresses';
+import {
+ MidasAccessControl__factory,
+ MidasTimelockManager__factory,
+} from '../../../typechain-types';
+import { DeployFunction } from '../common/types';
+import { sendAndWaitForCustomTxSign } from '../common/utils';
+
+const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
+ const addresses = getCurrentAddresses(hre);
+
+ if (!addresses?.accessControl) {
+ throw new Error('Access control address is not set');
+ }
+
+ if (!addresses?.timelockManager) {
+ throw new Error('Timelock manager address is not set');
+ }
+
+ if (!addresses?.timelockController) {
+ throw new Error('Timelock controller address is not set');
+ }
+
+ if (!addresses?.pauseManager) {
+ throw new Error('Pause manager address is not set');
+ }
+
+ const accessControl = MidasAccessControl__factory.connect(
+ addresses.accessControl,
+ hre.ethers.provider,
+ );
+
+ const timelockManager = MidasTimelockManager__factory.connect(
+ addresses.timelockManager,
+ hre.ethers.provider,
+ );
+
+ const timelockManagerWired =
+ (await timelockManager.timelock()) !== ethers.constants.AddressZero;
+
+ const accessControlWired =
+ (await accessControl.timelockManager()) !== ethers.constants.AddressZero;
+
+ if (!timelockManagerWired) {
+ const tx = await timelockManager.populateTransaction.initializeTimelock(
+ addresses.timelockController,
+ );
+ const txRes = await sendAndWaitForCustomTxSign(hre, tx, {
+ comment: `Initialize timelock controller in timelock manager`,
+ action: 'update-ac',
+ });
+
+ console.log(`Initialize timelock controller in timelock manager`, txRes);
+ } else {
+ console.log('Timelock controller is already wired');
+ }
+
+ if (!accessControlWired) {
+ const tx = await accessControl.populateTransaction.initializeRelationships(
+ addresses.timelockManager,
+ addresses.pauseManager,
+ );
+ const txRes = await sendAndWaitForCustomTxSign(hre, tx, {
+ comment: `Initialize relationships in access control`,
+ action: 'update-ac',
+ });
+
+ console.log(`Initialize relationships in access control`, txRes);
+ } else {
+ console.log('Relationships in access control are already wired');
+ }
+
+ console.log('Wire transactions sent');
+};
+
+export default func;
diff --git a/scripts/generate-errors-readme.ts b/scripts/generate-errors-readme.ts
new file mode 100644
index 00000000..dab35990
--- /dev/null
+++ b/scripts/generate-errors-readme.ts
@@ -0,0 +1,107 @@
+// Generates a registry of every custom error across our compiled contracts.
+//
+// yarn compile # make sure artifacts are fresh
+// yarn docs:errors # writes ERRORS.md
+// yarn docs:errors 0x8d666f60 # look up a single selector
+//
+// Reads Hardhat artifacts under artifacts/contracts (our own contracts only).
+// Inherited errors from dependencies (OpenZeppelin, Chainlink CCIP, ...) are
+// still captured because Hardhat flattens them into each contract's ABI.
+import { ethers } from 'ethers';
+
+import { readdirSync, readFileSync, writeFileSync, statSync } from 'fs';
+import { join } from 'path';
+
+const repoRoot = join(__dirname, '..');
+const artifactsDir = join(repoRoot, 'artifacts', 'contracts');
+const outFile = join(repoRoot, 'ERRORS.md');
+
+// Directory names to skip anywhere in the tree.
+const skipDirs = new Set(['build-info', 'mocks', 'testers']);
+
+const lookup = process.argv[2]?.toLowerCase();
+
+type ErrorEntry = { signature: string; sources: Set };
+
+// Recursively collect artifact JSON files (skip debug + excluded dirs).
+const walk = (dir: string): string[] => {
+ const out: string[] = [];
+ for (const entry of readdirSync(dir)) {
+ const full = join(dir, entry);
+ if (statSync(full).isDirectory()) {
+ if (skipDirs.has(entry)) continue;
+ out.push(...walk(full));
+ } else if (entry.endsWith('.json') && !entry.endsWith('.dbg.json')) {
+ out.push(full);
+ }
+ }
+ return out;
+};
+
+// selector -> { signature, sources }
+const errors = new Map();
+
+for (const file of walk(artifactsDir)) {
+ let artifact: { contractName?: string; abi?: unknown[] };
+ try {
+ artifact = JSON.parse(readFileSync(file, 'utf8'));
+ } catch {
+ continue;
+ }
+ if (!Array.isArray(artifact.abi)) continue;
+ const contractName = artifact.contractName || file;
+
+ let iface: ethers.utils.Interface;
+ try {
+ iface = new ethers.utils.Interface(artifact.abi as ethers.utils.Fragment[]);
+ } catch {
+ continue;
+ }
+
+ for (const signature of Object.keys(iface.errors)) {
+ const selector = ethers.utils.id(signature).slice(0, 10);
+ if (!errors.has(selector)) {
+ errors.set(selector, { signature, sources: new Set() });
+ }
+ errors.get(selector)!.sources.add(contractName);
+ }
+}
+
+if (lookup) {
+ const hit = errors.get(lookup);
+ if (hit) {
+ console.log(`\n👉 ${lookup} ${hit.signature}`);
+ console.log(` defined in: ${[...hit.sources].sort().join(', ')}`);
+ } else {
+ console.log(
+ `\nNo error with selector ${lookup} found in ${errors.size} known errors.`,
+ );
+ console.log('Did you run `yarn compile` first?');
+ }
+ process.exit(hit ? 0 : 1);
+}
+
+const rows = [...errors.entries()]
+ .map(([selector, { signature, sources }]) => ({
+ selector,
+ signature,
+ sources: [...sources].sort().join(', '),
+ }))
+ .sort((a, b) => a.signature.localeCompare(b.signature));
+
+const md = [
+ '# Custom Errors',
+ '',
+ 'Auto-generated by `scripts/generate-errors-readme.ts` from compiled artifacts.',
+ `Total: ${rows.length} errors.`,
+ '',
+ '| Selector | Error | Declared in |',
+ '| --- | --- | --- |',
+ ...rows.map(
+ (r) => `| \`${r.selector}\` | \`${r.signature}\` | ${r.sources} |`,
+ ),
+ '',
+].join('\n');
+
+writeFileSync(outFile, md);
+console.log(`Wrote ${rows.length} errors to ${outFile}`);
diff --git a/scripts/upgrades/common/upgrade-contracts.ts b/scripts/upgrades/common/upgrade-contracts.ts
index 21a80082..8d4628a0 100644
--- a/scripts/upgrades/common/upgrade-contracts.ts
+++ b/scripts/upgrades/common/upgrade-contracts.ts
@@ -19,9 +19,19 @@ import {
import { getDeployer } from '../../deploy/common/utils';
// TODO: refactor this whole file and make upgrades more generic
-type ContractType = 'customAggregator' | 'customAggregatorGrowth' | 'token';
+type ContractType =
+ | 'dataFeed'
+ | 'dataFeedComposite'
+ | 'dataFeedMultiply'
+ | 'customAggregator'
+ | 'customAggregatorGrowth'
+ | 'token';
-type ContractTypeToUpgrade = 'customFeed' | 'customFeedGrowth' | 'token';
+type ContractTypeToUpgrade =
+ | 'dataFeed'
+ | 'customFeed'
+ | 'customFeedGrowth'
+ | 'token';
type MTokenContractsToUpgrade = {
mToken: MTokenName;
@@ -30,11 +40,54 @@ type MTokenContractsToUpgrade = {
contractType: ContractType;
contractTypeTo?: ContractType;
overrideImplementation?: string;
+ constructorArgs?: unknown[];
initializer?: string;
initializerArgs?: unknown[];
}[];
};
+type ContractToUpgrade = {
+ mToken?: MTokenName;
+ contractType: string;
+ contractTypeTo?: string;
+ contractName: string;
+ proxyAddress: string;
+ overrideImplementation?: string;
+ initializer?: string;
+ initializerArgs?: unknown[];
+ constructorArgs?: unknown[];
+};
+
+export const proposeUpgradeContractsRaw = async (
+ hre: HardhatRuntimeEnvironment,
+ upgradeId: string,
+ contractsToUpgrade: ContractToUpgrade[],
+) => {
+ return upgradeAllContractsRaw(
+ hre,
+ upgradeId,
+ contractsToUpgrade,
+ async (hre, params, salt) => {
+ return await proposeTimeLockUpgradeTx(hre, params, salt);
+ },
+ );
+};
+
+export const executeUpgradeContractsRaw = async (
+ hre: HardhatRuntimeEnvironment,
+ upgradeId: string,
+ contractsToUpgrade: ContractToUpgrade[],
+) => {
+ return upgradeAllContractsRaw(
+ hre,
+ upgradeId,
+ contractsToUpgrade,
+ async (hre, params, salt) => {
+ return await executeTimeLockUpgradeTx(hre, params, salt);
+ },
+ );
+};
+
export const proposeUpgradeContracts = async (
hre: HardhatRuntimeEnvironment,
upgradeId: string,
@@ -56,7 +109,6 @@ export const executeUpgradeContracts = async (
hre: HardhatRuntimeEnvironment,
upgradeId: string,
contractType: ContractTypeToUpgrade,
-
mTokenContractsToUpgrade: MTokenContractsToUpgrade[],
) => {
return upgradeAllContracts(
@@ -112,16 +164,7 @@ const upgradeAllContracts = async (
console.log('mTokensToUpgrade', mTokenContractsToUpgrade);
- const upgradeContracts: {
- mToken: MTokenName;
- contractType: ContractType;
- contractTypeTo?: ContractType;
- contractName: string;
- proxyAddress: string;
- overrideImplementation?: string;
- initializer?: string;
- initializerCalldata?: string;
- }[] = [];
+ const contractsToUpgrade: ContractToUpgrade[] = [];
for (const { mToken, contracts, addresses } of mTokenContractsToUpgrade) {
for (const {
@@ -130,6 +173,7 @@ const upgradeAllContracts = async (
overrideImplementation,
initializer,
initializerArgs,
+ constructorArgs,
} of contracts) {
const type = contractTypeTo ?? contractType;
const contractName =
@@ -139,12 +183,7 @@ const upgradeAllContracts = async (
throw new Error(`Contract name not found for ${mToken} ${type}`);
}
- const contract = await hre.ethers.getContractAt(
- contractName,
- addresses[contractTypeToUpgrade]!,
- );
-
- upgradeContracts.push({
+ contractsToUpgrade.push({
mToken,
contractType: type,
contractTypeTo,
@@ -152,19 +191,38 @@ const upgradeAllContracts = async (
proxyAddress: addresses[contractTypeToUpgrade]!,
overrideImplementation,
initializer,
- initializerCalldata: initializer
- ? contract.interface.encodeFunctionData(
- initializer,
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (initializerArgs ?? []) as readonly any[],
- )
- : undefined,
+ initializerArgs: initializerArgs,
+ constructorArgs,
});
}
}
+ return await upgradeAllContractsRaw(
+ hre,
+ upgradeId,
+ contractsToUpgrade,
+ callBack,
+ );
+};
+
+const upgradeAllContractsRaw = async (
+ hre: HardhatRuntimeEnvironment,
+ upgradeId: string,
+ upgradeContracts: ContractToUpgrade[],
+ callBack: (
+ hre: HardhatRuntimeEnvironment,
+ params: GetUpgradeTxParams,
+ salt: string,
+ ) => Promise,
+) => {
upgradeContracts.sort((a, b) => {
- return a.mToken.localeCompare(b.mToken, 'en', { sensitivity: 'base' });
+ return (a.mToken ?? a.contractName).localeCompare(
+ b.mToken ?? b.contractName,
+ 'en',
+ {
+ sensitivity: 'base',
+ },
+ );
});
console.log('upgradeContracts', upgradeContracts);
@@ -182,11 +240,14 @@ const upgradeAllContracts = async (
contractType,
contractName,
proxyAddress,
+ constructorArgs,
} = upgradeContract;
if (overrideImplementation) {
console.log(
- `Using override implementation (${overrideImplementation}) for ${mToken}`,
+ `Using override implementation (${overrideImplementation}) for ${
+ mToken ? `${mToken} ${contractType}` : contractName
+ }`,
);
continue;
}
@@ -199,13 +260,19 @@ const upgradeAllContracts = async (
{
redeployImplementation: 'onchange',
getTxResponse: true,
+ constructorArgs,
},
),
);
if (deployedNew) {
logDeploy(contractName, contractType, implementationAddress);
- await etherscanVerify(hre, implementationAddress).catch((e) => {
+ await etherscanVerify(
+ hre,
+ implementationAddress,
+ contractName,
+ ...(constructorArgs ?? []),
+ ).catch((e) => {
console.error('Verification failed', e);
});
} else {
@@ -221,8 +288,9 @@ const upgradeAllContracts = async (
}
const failedUpgrades: {
- mToken: MTokenName;
- contractType: ContractType;
+ mToken?: MTokenName;
+ contractName: string;
+ contractType: string;
error: string;
}[] = [];
@@ -233,13 +301,27 @@ const upgradeAllContracts = async (
Proxy: ${deployment.proxyAddress}
Implementation: ${deployment.implementationAddress}`,
);
+
+ const contract = await hre.ethers.getContractAt(
+ deployment.contractName,
+ deployment.proxyAddress,
+ );
+
+ const initializerCalldata =
+ deployment.initializerArgs && deployment.initializer
+ ? contract.interface.encodeFunctionData(
+ deployment.initializer,
+ deployment.initializerArgs,
+ )
+ : undefined;
+
const result = await callBack(
hre,
{
proxyAddress: deployment.proxyAddress,
newImplementation: deployment.implementationAddress,
initializer: deployment.initializer,
- initializerCalldata: deployment.initializerCalldata,
+ initializerCalldata,
contractName: deployment.contractName,
},
upgradeId,
@@ -253,6 +335,7 @@ Implementation: ${deployment.implementationAddress}`,
failedUpgrades.push({
mToken: deployment.mToken,
+ contractName: deployment.contractName,
contractType: deployment.contractType,
error: e instanceof Error ? e.message : (e as string),
});
diff --git a/scripts/upgrades/configs/upgrade-configs.ts b/scripts/upgrades/configs/upgrade-configs.ts
index 1840fbfe..875d8604 100644
--- a/scripts/upgrades/configs/upgrade-configs.ts
+++ b/scripts/upgrades/configs/upgrade-configs.ts
@@ -1,5 +1,3 @@
-import { constants } from 'ethers';
-
import { chainIds } from '../../../config';
import { UpgradeConfig } from '../common/types';
@@ -46,12 +44,6 @@ export const upgradeConfigs: UpgradeConfig = {
},
},
'batch-upgrade-scope-w-supply-cap': {
- initializers: {
- depositVault: {
- initializer: 'initializeV2',
- defaultInitializerArgs: [constants.MaxUint256],
- },
- },
vaults: {
[chainIds.sepolia]: {
overrides: {
diff --git a/scripts/upgrades/proposeUpgrade_Aggregators.ts b/scripts/upgrades/proposeUpgrade_Aggregators.ts
deleted file mode 100644
index ef2707d9..00000000
--- a/scripts/upgrades/proposeUpgrade_Aggregators.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { parseUnits } from 'ethers/lib/utils';
-import { HardhatRuntimeEnvironment } from 'hardhat/types';
-
-import { proposeUpgradeContracts } from './common/upgrade-contracts';
-
-import { getCurrentAddresses } from '../../config/constants/addresses';
-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 networkAddresses = getCurrentAddresses(hre);
- const mToken = getMTokenOrThrow(hre);
- const tokenAddresses = networkAddresses?.[mToken];
-
- if (!tokenAddresses) {
- throw new Error('Token addresses not found');
- }
-
- await proposeUpgradeContracts(hre, upgradeId, 'customFeed', [
- {
- mToken,
- addresses: tokenAddresses,
- contracts: [
- {
- contractType: 'customAggregator',
- initializer: 'initializeV2',
- initializerArgs: [parseUnits('0.66', 8)],
- },
- ],
- },
- ]);
-};
-
-export default func;
diff --git a/scripts/upgrades/upgrade_AccessControl.ts b/scripts/upgrades/upgrade_AccessControl.ts
new file mode 100644
index 00000000..d65f132b
--- /dev/null
+++ b/scripts/upgrades/upgrade_AccessControl.ts
@@ -0,0 +1,34 @@
+import { HardhatRuntimeEnvironment } from 'hardhat/types';
+
+import {
+ executeUpgradeContractsRaw,
+ proposeUpgradeContractsRaw,
+} from './common/upgrade-contracts';
+
+import { getCurrentAddresses } from '../../config/constants/addresses';
+import { getCommonContractNames } from '../../helpers/contracts';
+import { getActionOrThrow, upgradeActions } from '../../helpers/utils';
+import { DeployFunction } from '../deploy/common/types';
+
+const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
+ const upgradeId = 'q2-testnet-custom-aggregator-upgrade-v2';
+
+ const networkAddresses = getCurrentAddresses(hre);
+
+ const action = getActionOrThrow(hre, upgradeActions);
+
+ const fn =
+ action === 'propose'
+ ? proposeUpgradeContractsRaw
+ : executeUpgradeContractsRaw;
+
+ await fn(hre, upgradeId, [
+ {
+ contractName: getCommonContractNames().ac,
+ proxyAddress: networkAddresses?.accessControl ?? '',
+ contractType: 'accessControl',
+ },
+ ]);
+};
+
+export default func;
diff --git a/scripts/upgrades/upgrade_Aggregators.ts b/scripts/upgrades/upgrade_Aggregators.ts
new file mode 100644
index 00000000..8538045b
--- /dev/null
+++ b/scripts/upgrades/upgrade_Aggregators.ts
@@ -0,0 +1,54 @@
+import { HardhatRuntimeEnvironment } from 'hardhat/types';
+
+import {
+ executeUpgradeContractsRaw,
+ proposeUpgradeContractsRaw,
+} from './common/upgrade-contracts';
+
+import { MTokenName } from '../../config';
+import { getCurrentAddresses } from '../../config/constants/addresses';
+import { getRolesForToken } from '../../helpers/roles';
+import { getActionOrThrow, upgradeActions } from '../../helpers/utils';
+import { DeployFunction } from '../deploy/common/types';
+
+const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
+ const upgradeId = 'q2-testnet-custom-aggregator-upgrade';
+
+ const networkAddresses = getCurrentAddresses(hre);
+ const mTokens = ['mTBILL', 'mSL'] as MTokenName[];
+
+ const action = getActionOrThrow(hre, upgradeActions);
+
+ const fn =
+ action === 'propose'
+ ? proposeUpgradeContractsRaw
+ : executeUpgradeContractsRaw;
+
+ const types = ['customFeed', 'customFeedGrowth'] as const;
+
+ const values = mTokens
+ .map((mToken) => {
+ return types
+ .map((type) => {
+ return {
+ mToken,
+ proxyAddress: networkAddresses?.[mToken]?.[type] ?? '',
+ contractType:
+ type === 'customFeed'
+ ? 'customAggregator'
+ : 'customAggregatorGrowth',
+ contractName:
+ type === 'customFeed'
+ ? 'CustomAggregatorV3CompatibleFeed'
+ : 'CustomAggregatorV3CompatibleFeedGrowth',
+ constructorArgs: [getRolesForToken(mToken).customFeedAdmin ?? ''],
+ };
+ })
+ .filter((v) => v.proxyAddress !== '');
+ })
+ .flat();
+
+ await fn(hre, upgradeId, values);
+};
+
+export default func;
diff --git a/scripts/upgrades/upgrade_DV.ts b/scripts/upgrades/upgrade_DV.ts
deleted file mode 100644
index a4cc120f..00000000
--- a/scripts/upgrades/upgrade_DV.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import * as hre from 'hardhat';
-import { HardhatRuntimeEnvironment } from 'hardhat/types';
-import { DeployFunction } from 'hardhat-deploy/types';
-
-import { getCurrentAddresses } from '../../config/constants/addresses';
-import { getTokenContractNames } from '../../helpers/contracts';
-import { getDeployer } from '../deploy/common/utils';
-
-const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
- const addresses = getCurrentAddresses(hre);
-
- const deployer = await getDeployer(hre);
-
- const deployment = await hre.upgrades.prepareUpgrade(
- addresses?.hbXAUt?.depositVault ?? '',
- await hre.ethers.getContractFactory(
- getTokenContractNames('hbXAUt').dv!,
- deployer,
- ),
- {
- redeployImplementation: 'onchange',
- },
- );
-
- if (typeof deployment !== 'string') {
- await deployment.wait(5);
- console.log(deployment.to);
- } else {
- console.log(deployment);
- }
-};
-
-func(hre).then(console.log).catch(console.error);
diff --git a/scripts/upgrades/upgrade_Feeds.ts b/scripts/upgrades/upgrade_Feeds.ts
new file mode 100644
index 00000000..cd6323f1
--- /dev/null
+++ b/scripts/upgrades/upgrade_Feeds.ts
@@ -0,0 +1,35 @@
+import { HardhatRuntimeEnvironment } from 'hardhat/types';
+
+import {
+ executeUpgradeContractsRaw,
+ proposeUpgradeContractsRaw,
+} from './common/upgrade-contracts';
+
+import { getCurrentAddresses } from '../../config/constants/addresses';
+import { getAllRoles } from '../../helpers/roles';
+import { getActionOrThrow, upgradeActions } from '../../helpers/utils';
+import { DeployFunction } from '../deploy/common/types';
+
+const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
+ const upgradeId = 'q2-testnet-custom-aggregator-upgrade';
+
+ const networkAddresses = getCurrentAddresses(hre);
+
+ const action = getActionOrThrow(hre, upgradeActions);
+
+ const fn =
+ action === 'propose'
+ ? proposeUpgradeContractsRaw
+ : executeUpgradeContractsRaw;
+
+ await fn(hre, upgradeId, [
+ {
+ contractType: 'dataFeed',
+ contractName: 'DataFeed',
+ proxyAddress: networkAddresses?.paymentTokens?.usdc?.dataFeed ?? '',
+ constructorArgs: [getAllRoles().common.defaultAdmin],
+ },
+ ]);
+};
+
+export default func;
diff --git a/scripts/upgrades/upgrade_MTokens.ts b/scripts/upgrades/upgrade_MTokens.ts
new file mode 100644
index 00000000..4df9f4cd
--- /dev/null
+++ b/scripts/upgrades/upgrade_MTokens.ts
@@ -0,0 +1,67 @@
+import { HardhatRuntimeEnvironment } from 'hardhat/types';
+
+import {
+ executeUpgradeContracts,
+ proposeUpgradeContracts,
+} from './common/upgrade-contracts';
+
+import { MTokenName } from '../../config';
+import { getCurrentAddresses } from '../../config/constants/addresses';
+import { mTokensMetadata } from '../../helpers/mtokens-metadata';
+import { getRolesForToken } from '../../helpers/roles';
+import { getActionOrThrow, upgradeActions } from '../../helpers/utils';
+import { DeployFunction } from '../deploy/common/types';
+import { getDeployer } from '../deploy/common/utils';
+
+const clawbackRecipients = {} as Record;
+
+const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
+ const upgradeId = 'q2-testnet-custom-aggregator-upgrade';
+
+ const networkAddresses = getCurrentAddresses(hre);
+ const mTokens = ['mTBILL', 'mSL'] as MTokenName[];
+
+ const action = getActionOrThrow(hre, upgradeActions);
+
+ const fn =
+ action === 'propose' ? proposeUpgradeContracts : executeUpgradeContracts;
+
+ const deployer = await getDeployer(hre);
+
+ await fn(
+ hre,
+ upgradeId,
+ 'token',
+ mTokens.map((mToken) => {
+ const clawbackRecipient = clawbackRecipients[mToken] ?? deployer.address;
+ const roles = getRolesForToken(mToken);
+ const isPermissioned = !!mTokensMetadata[mToken]?.isPermissioned;
+
+ return {
+ mToken,
+ addresses: networkAddresses?.[mToken] ?? {},
+ contracts: [
+ {
+ contractType: 'token',
+ // initializer: 'initializeV3',
+ // initializerArgs: [
+ // clawbackRecipient,
+ // constants.MaxUint256,
+ // isPermissioned,
+ // false,
+ // ],
+ constructorArgs: [
+ roles.tokenManager,
+ roles.minter,
+ roles.burner,
+ roles.greenlisted,
+ roles.minBalanceExempt,
+ ],
+ },
+ ],
+ };
+ }),
+ );
+};
+
+export default func;
diff --git a/scripts/upgrades/upgrade_RedemptionVaultMToken.ts b/scripts/upgrades/upgrade_RedemptionVaultMToken.ts
deleted file mode 100644
index 1943ca53..00000000
--- a/scripts/upgrades/upgrade_RedemptionVaultMToken.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import { HardhatRuntimeEnvironment } from 'hardhat/types';
-
-import { getCurrentAddresses } from '../../config/constants/addresses';
-import { getTokenContractNames } from '../../helpers/contracts';
-import { getMTokenOrThrow } from '../../helpers/utils';
-import { DeployFunction } from '../deploy/common/types';
-import { getDeployer } from '../deploy/common/utils';
-
-/**
- * Upgrades a RedemptionVaultWithSwapper proxy to use the
- * RedemptionVaultWithMToken implementation.
- *
- * Usage:
- * npx hardhat runscript scripts/upgrades/upgrade_RedemptionVaultMToken.ts --mtoken mFONE --network
- *
- * The script uses `prepareUpgrade` which validates storage layout
- * compatibility and deploys the new implementation (if changed).
- */
-const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
- const mToken = getMTokenOrThrow(hre);
-
- const addresses = getCurrentAddresses(hre);
- const proxyAddress = addresses?.[mToken]?.redemptionVaultSwapper;
-
- if (!proxyAddress) {
- throw new Error(
- `No redemptionVaultSwapper address found for ${mToken} on chain ${hre.network.config.chainId}`,
- );
- }
-
- const deployer = await getDeployer(hre);
- const contractName = getTokenContractNames(mToken).rvMToken;
-
- console.log(
- `Upgrading ${mToken} Swapper proxy (${proxyAddress}) -> ${contractName}`,
- );
-
- const deployment = await hre.upgrades.prepareUpgrade(
- proxyAddress,
- await hre.ethers.getContractFactory(contractName, deployer),
- {
- redeployImplementation: 'onchange',
- },
- );
-
- if (typeof deployment !== 'string') {
- await deployment.wait(5);
- console.log('New implementation deployed at:', deployment.to);
- } else {
- console.log('Implementation address:', deployment);
- }
-};
-
-export default func;
diff --git a/scripts/upgrades/upgrade_RedemptionVaultSwapper.ts b/scripts/upgrades/upgrade_RedemptionVaultSwapper.ts
deleted file mode 100644
index 27b850a3..00000000
--- a/scripts/upgrades/upgrade_RedemptionVaultSwapper.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import * as hre from 'hardhat';
-import { HardhatRuntimeEnvironment } from 'hardhat/types';
-import { DeployFunction } from 'hardhat-deploy/types';
-
-import { getCurrentAddresses } from '../../config/constants/addresses';
-import { getTokenContractNames } from '../../helpers/contracts';
-import { getDeployer } from '../deploy/common/utils';
-
-const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
- const addresses = getCurrentAddresses(hre);
-
- const deployer = await getDeployer(hre);
-
- const deployment = await hre.upgrades.prepareUpgrade(
- addresses?.hbXAUt?.redemptionVaultSwapper ?? '',
- await hre.ethers.getContractFactory(
- getTokenContractNames('hbXAUt').rvSwapper!,
- deployer,
- ),
- {
- redeployImplementation: 'onchange',
- },
- );
-
- if (typeof deployment !== 'string') {
- await deployment.wait(5);
- console.log(deployment.to);
- } else {
- console.log(deployment);
- }
-};
-
-func(hre).then(console.log).catch(console.error);
diff --git a/scripts/upgrades/upgrade_TimelockManager.ts b/scripts/upgrades/upgrade_TimelockManager.ts
new file mode 100644
index 00000000..13cc55bf
--- /dev/null
+++ b/scripts/upgrades/upgrade_TimelockManager.ts
@@ -0,0 +1,33 @@
+import { HardhatRuntimeEnvironment } from 'hardhat/types';
+
+import {
+ executeUpgradeContractsRaw,
+ proposeUpgradeContractsRaw,
+} from './common/upgrade-contracts';
+
+import { getCurrentAddresses } from '../../config/constants/addresses';
+import { getActionOrThrow, upgradeActions } from '../../helpers/utils';
+import { DeployFunction } from '../deploy/common/types';
+
+const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
+ const upgradeId = 'q2-testnet-timelock-manager-upgrade';
+
+ const networkAddresses = getCurrentAddresses(hre);
+
+ const action = getActionOrThrow(hre, upgradeActions);
+
+ const fn =
+ action === 'propose'
+ ? proposeUpgradeContractsRaw
+ : executeUpgradeContractsRaw;
+
+ await fn(hre, upgradeId, [
+ {
+ contractType: 'timelockManager',
+ contractName: 'MidasTimelockManager',
+ proxyAddress: networkAddresses?.timelockManager ?? '',
+ },
+ ]);
+};
+
+export default func;
diff --git a/scripts/upgrades/upgrade_Vaults.ts b/scripts/upgrades/upgrade_Vaults.ts
new file mode 100644
index 00000000..ab8c9f5a
--- /dev/null
+++ b/scripts/upgrades/upgrade_Vaults.ts
@@ -0,0 +1,57 @@
+import { HardhatRuntimeEnvironment } from 'hardhat/types';
+
+import {
+ executeUpgradeContractsRaw,
+ proposeUpgradeContractsRaw,
+} from './common/upgrade-contracts';
+
+import { MTokenName } from '../../config';
+import { getCurrentAddresses } from '../../config/constants/addresses';
+import { getCommonContractNames } from '../../helpers/contracts';
+import { getRolesForToken } from '../../helpers/roles';
+import { getActionOrThrow, upgradeActions } from '../../helpers/utils';
+import { DeployFunction } from '../deploy/common/types';
+
+const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
+ const upgradeId = 'q2-testnet-custom-aggregator-upgrade';
+
+ const networkAddresses = getCurrentAddresses(hre);
+ const mTokens = ['mTBILL', 'mSL'] as MTokenName[];
+
+ const action = getActionOrThrow(hre, upgradeActions);
+
+ const fn =
+ action === 'propose'
+ ? proposeUpgradeContractsRaw
+ : executeUpgradeContractsRaw;
+
+ const values = mTokens
+ .map((mToken) => {
+ const roles = getRolesForToken(mToken);
+
+ return [
+ {
+ mToken,
+ proxyAddress: networkAddresses?.[mToken]?.depositVault ?? '',
+ contractType: 'depositVault',
+ contractName: getCommonContractNames().dv,
+ constructorArgs: [roles.depositVaultAdmin ?? '', roles.greenlisted],
+ },
+ {
+ mToken,
+ proxyAddress: networkAddresses?.[mToken]?.redemptionVault ?? '',
+ contractType: 'redemptionVault',
+ contractName: getCommonContractNames().rv,
+ constructorArgs: [
+ roles.redemptionVaultAdmin ?? '',
+ roles.greenlisted,
+ ],
+ },
+ ];
+ })
+ .flat();
+
+ await fn(hre, upgradeId, values);
+};
+
+export default func;
diff --git a/test/common/ac.helpers.ts b/test/common/ac.helpers.ts
index 2db77673..27295725 100644
--- a/test/common/ac.helpers.ts
+++ b/test/common/ac.helpers.ts
@@ -1,12 +1,30 @@
+import { increase } from '@nomicfoundation/hardhat-network-helpers/dist/src/helpers/time';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { expect } from 'chai';
+import { BigNumber, BigNumberish, constants, Contract } from 'ethers';
-import { Account, OptionalCommonParams, getAccount } from './common.helpers';
+import {
+ Account,
+ AccountOrContract,
+ OptionalCommonParams,
+ asyncForEach,
+ getAccount,
+ handleRevert,
+} from './common.helpers';
+import {
+ executeTimelockOperationTester,
+ bulkScheduleTimelockOperationTester,
+} from './timelock-manager.helpers';
+import { getAllRoles } from '../../helpers/roles';
+import { encodeFnSelector } from '../../helpers/utils';
import {
Blacklistable,
Greenlistable,
+ IMidasAccessControlManaged__factory,
MidasAccessControl,
+ MidasAccessControlTimelockController,
+ MidasTimelockManager,
} from '../../typechain-types';
type CommonParamsBlackList = {
@@ -22,175 +40,856 @@ type CommonParamsGreenList = {
owner: SignerWithAddress;
};
+export const NULL_DELAY = 0;
+// uint32 max value
+export const NO_DELAY = BigNumber.from('0xFFFFFFFF');
+
export const acErrors = {
- WMAC_HASNT_ROLE: 'WMAC: hasnt role',
- WMAC_HAS_ROLE: 'WMAC: has role',
+ WMAC_BLACKLISTED: (args?: unknown[], contract?: Contract) => ({
+ contract,
+ customErrorName: 'Blacklisted',
+ args,
+ }),
+ WMAC_HASNT_PERMISSION: (args?: unknown[], contract?: Contract) => ({
+ contract,
+ customErrorName: 'NoFunctionPermission',
+ args,
+ }),
};
export const blackList = async (
+ { blacklistable, accessControl, owner }: CommonParamsBlackList,
+ account: AccountOrContract,
+ opt?: OptionalCommonParams,
+) => {
+ const allRoles = getAllRoles();
+ await grantRoleTester(
+ { accessControl, owner },
+ allRoles.common.blacklisted,
+ account,
+ 0,
+ opt,
+ );
+};
+
+export const unBlackList = async (
{ blacklistable, accessControl, owner }: CommonParamsBlackList,
account: Account,
opt?: OptionalCommonParams,
) => {
account = getAccount(account);
+ const allRoles = getAllRoles();
- if (opt?.revertMessage) {
- await expect(
+ if (
+ await handleRevert(
accessControl
.connect(opt?.from ?? owner)
- .grantRole(await blacklistable.BLACKLISTED_ROLE(), account),
- ).revertedWith(opt?.revertMessage);
+ .revokeRole.bind(this, allRoles.common.blacklisted, account),
+ accessControl,
+ opt,
+ )
+ ) {
return;
}
await expect(
accessControl
.connect(opt?.from ?? owner)
- .grantRole(await blacklistable.BLACKLISTED_ROLE(), account),
+ .revokeRole(allRoles.common.blacklisted, account),
).to.emit(
accessControl,
- accessControl.interface.events['RoleGranted(bytes32,address,address)'].name,
- ).to.not.reverted;
+ accessControl.interface.events['RoleRevoked(bytes32,address,address)'].name,
+ );
- expect(
- await accessControl.hasRole(
- await accessControl.BLACKLISTED_ROLE(),
- account,
- ),
- ).eq(true);
+ expect(await accessControl.hasRole(allRoles.common.blacklisted, account)).eq(
+ false,
+ );
};
-export const unBlackList = async (
- { blacklistable, accessControl, owner }: CommonParamsBlackList,
+export const greenList = async (
+ { greenlistable, accessControl, owner, role }: CommonParamsGreenList,
+ account: AccountOrContract,
+ opt?: OptionalCommonParams,
+) => {
+ await grantRoleTester(
+ { accessControl, owner },
+ role ?? (await greenlistable.greenlistedRole()),
+ account,
+ 0,
+ opt,
+ );
+};
+
+export const unGreenList = async (
+ { greenlistable, accessControl, owner, role }: CommonParamsGreenList,
account: Account,
opt?: OptionalCommonParams,
) => {
- account = getAccount(account);
+ await revokeRoleTester(
+ { accessControl, owner },
+ role ?? (await greenlistable.greenlistedRole()),
+ account,
+ opt,
+ );
+};
- if (opt?.revertMessage) {
- await expect(
- accessControl
- .connect(opt?.from ?? owner)
- .revokeRole(await blacklistable.BLACKLISTED_ROLE(), account),
- ).revertedWith(opt?.revertMessage);
+export const grantRoleMultTester = async (
+ {
+ accessControl,
+ owner,
+ }: {
+ accessControl: MidasAccessControl;
+ owner: SignerWithAddress;
+ },
+ params: {
+ role: string;
+ account: string;
+ delay?: BigNumberish;
+ }[],
+ opt?: OptionalCommonParams,
+) => {
+ const from = opt?.from ?? owner;
+
+ const callFn = accessControl.connect(from).grantRoleMult.bind(
+ this,
+ params.map((param) => ({
+ ...param,
+ delay: param.delay ?? 0,
+ })),
+ );
+
+ if (await handleRevert(callFn, accessControl, opt)) {
return;
}
- await expect(
- accessControl
- .connect(opt?.from ?? owner)
- .revokeRole(await blacklistable.BLACKLISTED_ROLE(), account),
- ).to.emit(
+ const hadRoles = await Promise.all(
+ params.map(({ role, account }) => accessControl.hasRole(role, account)),
+ );
+
+ const txPromise = callFn();
+ let grantMultExpect: ReturnType | undefined;
+ for (const [index, { role, account }] of params.entries()) {
+ if (hadRoles[index]) {
+ continue;
+ }
+
+ if (grantMultExpect === undefined) {
+ grantMultExpect = expect(txPromise)
+ .to.emit(
+ accessControl,
+ accessControl.interface.events['RoleGranted(bytes32,address,address)']
+ .name,
+ )
+ .withArgs(role, account, from.address);
+ } else {
+ grantMultExpect = grantMultExpect.to
+ .emit(
+ accessControl,
+ accessControl.interface.events['RoleGranted(bytes32,address,address)']
+ .name,
+ )
+ .withArgs(role, account, from.address);
+ }
+ }
+
+ if (grantMultExpect !== undefined) {
+ await grantMultExpect;
+ } else {
+ await txPromise;
+ }
+
+ await asyncForEach(
+ params.entries(),
+ async ([index, { account, role, delay }]) => {
+ expect(await accessControl.hasRole(role, account)).eq(true);
+ if (delay !== undefined && BigNumber.from(delay).gt(0)) {
+ const [actualDelay] = await accessControl.getRoleTimelockDelay(role, 0);
+ expect(actualDelay).eq(delay);
+ }
+ },
+ );
+};
+
+export const revokeRoleMultTester = async (
+ {
accessControl,
- accessControl.interface.events['RoleRevoked(bytes32,address,address)'].name,
- ).to.not.reverted;
+ owner,
+ }: { accessControl: MidasAccessControl; owner: SignerWithAddress },
+ params: {
+ role: string;
+ account: string;
+ }[],
+ opt?: OptionalCommonParams,
+) => {
+ const from = opt?.from ?? owner;
+
+ const callFn = accessControl.connect(from).revokeRoleMult.bind(this, params);
+
+ if (await handleRevert(callFn, accessControl, opt)) {
+ return;
+ }
+
+ const hadRoles = await Promise.all(
+ params.map(({ role, account }) => accessControl.hasRole(role, account)),
+ );
+
+ const txPromise = callFn();
+ let revokeMultExpect: ReturnType | undefined;
+ for (const [index, { role, account }] of params.entries()) {
+ if (!hadRoles[index]) {
+ continue;
+ }
+
+ if (revokeMultExpect === undefined) {
+ revokeMultExpect = expect(txPromise)
+ .to.emit(
+ accessControl,
+ accessControl.interface.events['RoleRevoked(bytes32,address,address)']
+ .name,
+ )
+ .withArgs(role, account, from.address);
+ } else {
+ revokeMultExpect = revokeMultExpect.to
+ .emit(
+ accessControl,
+ accessControl.interface.events['RoleRevoked(bytes32,address,address)']
+ .name,
+ )
+ .withArgs(role, account, from.address);
+ }
+ }
+
+ if (revokeMultExpect !== undefined) {
+ await revokeMultExpect;
+ } else {
+ await txPromise;
+ }
- expect(
- await accessControl.hasRole(
- await accessControl.BLACKLISTED_ROLE(),
- account,
- ),
- ).eq(false);
+ await asyncForEach(params, async ({ role, account }) => {
+ expect(await accessControl.hasRole(role, account)).eq(false);
+ });
};
-export const greenListToggler = async (
- { greenlistable, accessControl, owner, role }: CommonParamsGreenList,
- account: Account,
+export const grantRoleTester = async (
+ {
+ accessControl,
+ owner,
+ }: { accessControl: MidasAccessControl; owner: SignerWithAddress },
+ role: string,
+ account: AccountOrContract,
+ delay?: BigNumberish,
opt?: OptionalCommonParams,
) => {
+ const from = opt?.from ?? owner;
+
account = getAccount(account);
- if (opt?.revertMessage) {
- await expect(
- accessControl
- .connect(opt?.from ?? owner)
- .grantRole(
- role ?? (await greenlistable.greenlistTogglerRole()),
- account,
- ),
- ).revertedWith(opt?.revertMessage);
+ const callFn =
+ delay === undefined
+ ? accessControl
+ .connect(from)
+ ['grantRole(bytes32,address)'].bind(this, role, account)
+ : accessControl
+ .connect(from)
+ ['grantRole(bytes32,address,uint32)'].bind(
+ this,
+ role,
+ account,
+ delay,
+ );
+
+ if (await handleRevert(callFn, accessControl, opt)) {
return;
}
- await expect(
- accessControl
- .connect(opt?.from ?? owner)
- .grantRole(role ?? (await greenlistable.greenlistTogglerRole()), account),
- ).to.emit(
- accessControl,
- accessControl.interface.events['RoleGranted(bytes32,address,address)'].name,
- ).to.not.reverted;
+ const hadRole = await accessControl.hasRole(role, account);
+
+ if (hadRole) {
+ await callFn();
+ } else {
+ await expect(callFn())
+ .to.emit(
+ accessControl,
+ accessControl.interface.events['RoleGranted(bytes32,address,address)']
+ .name,
+ )
+ .withArgs(role, account, from.address);
+ }
- expect(
- await accessControl.hasRole(
- role ?? (await greenlistable.greenlistTogglerRole()),
- account,
- ),
- ).eq(true);
+ expect(await accessControl.hasRole(role, account)).eq(true);
+
+ if (delay !== undefined && BigNumber.from(delay).gt(0)) {
+ const [actualDelay] = await accessControl.getRoleTimelockDelay(role, 0);
+ expect(actualDelay).eq(delay);
+ }
};
-export const greenList = async (
- { greenlistable, accessControl, owner, role }: CommonParamsGreenList,
- account: Account,
+export const revokeRoleTester = async (
+ {
+ accessControl,
+ owner,
+ }: { accessControl: MidasAccessControl; owner: SignerWithAddress },
+ role: string,
+ account: AccountOrContract,
opt?: OptionalCommonParams,
) => {
+ const from = opt?.from ?? owner;
+
account = getAccount(account);
+ const callFn = accessControl
+ .connect(from)
+ .revokeRole.bind(this, role, account);
- if (opt?.revertMessage) {
- await expect(
- accessControl
- .connect(opt?.from ?? owner)
- .grantRole(role ?? (await greenlistable.GREENLISTED_ROLE()), account),
- ).revertedWith(opt?.revertMessage);
+ if (await handleRevert(callFn, accessControl, opt)) {
return;
}
- await expect(
- accessControl
- .connect(opt?.from ?? owner)
- .grantRole(role ?? (await greenlistable.GREENLISTED_ROLE()), account),
- ).to.emit(
+ const hadRole = await accessControl.hasRole(role, account);
+
+ if (!hadRole) {
+ await callFn();
+ } else {
+ await expect(callFn())
+ .to.emit(
+ accessControl,
+ accessControl.interface.events['RoleRevoked(bytes32,address,address)']
+ .name,
+ )
+ .withArgs(role, account, from.address);
+ }
+
+ expect(await accessControl.hasRole(role, account)).eq(false);
+};
+
+export const setIsUserFacingRoleTester = async (
+ {
accessControl,
- accessControl.interface.events['RoleGranted(bytes32,address,address)'].name,
- ).to.not.reverted;
+ owner,
+ }: { accessControl: MidasAccessControl; owner: SignerWithAddress },
+ params: { role: string; enabled: boolean }[],
+ opt?: OptionalCommonParams,
+) => {
+ const from = opt?.from ?? owner;
+
+ const callFn = accessControl
+ .connect(from)
+ .setUserFacingRoleMult.bind(this, params);
- expect(
- await accessControl.hasRole(
- role ?? (await accessControl.GREENLISTED_ROLE()),
- account,
- ),
- ).eq(true);
+ if (await handleRevert(callFn, accessControl, opt)) {
+ return;
+ }
+
+ const statesBefore = await Promise.all(
+ params.map(async (param) => {
+ return await accessControl.isUserFacingRole(param.role);
+ }),
+ );
+
+ const txPromise = callFn();
+ let userFacingExpect: ReturnType | undefined;
+ for (const [index, stateBefore] of statesBefore.entries()) {
+ const param = params[index];
+ if (stateBefore !== param.enabled) {
+ if (userFacingExpect === undefined) {
+ userFacingExpect = expect(txPromise)
+ .to.emit(
+ accessControl,
+ accessControl.interface.events['SetUserFacingRole(bytes32,bool)']
+ .name,
+ )
+ .withArgs(param.role, param.enabled);
+ } else {
+ userFacingExpect = userFacingExpect.to
+ .emit(
+ accessControl,
+ accessControl.interface.events['SetUserFacingRole(bytes32,bool)']
+ .name,
+ )
+ .withArgs(param.role, param.enabled);
+ }
+ }
+ }
+ if (userFacingExpect !== undefined) {
+ await userFacingExpect;
+ } else {
+ await txPromise;
+ }
+
+ await asyncForEach(params, async (param) => {
+ expect(await accessControl.isUserFacingRole(param.role)).eq(param.enabled);
+ });
};
-export const unGreenList = async (
- { greenlistable, accessControl, owner, role }: CommonParamsGreenList,
- account: Account,
+export const setGrantOperatorRoleTester = async (
+ {
+ accessControl,
+ owner,
+ }: { accessControl: MidasAccessControl; owner: SignerWithAddress },
+ targetContract: string,
+ params: {
+ functionSelector: string;
+ operator: string;
+ enabled: boolean;
+ delay?: BigNumberish;
+ }[],
opt?: OptionalCommonParams,
) => {
- account = getAccount(account);
+ const from = opt?.from ?? owner;
- if (opt?.revertMessage) {
- await expect(
- accessControl
- .connect(opt?.from ?? owner)
- .revokeRole(role ?? (await greenlistable.GREENLISTED_ROLE()), account),
- ).revertedWith(opt?.revertMessage);
+ const callFn = accessControl.connect(from).setGrantOperatorRoleMult.bind(
+ this,
+ targetContract,
+ params.map((param) => ({
+ ...param,
+ delay: param.delay ?? 0,
+ })),
+ );
+
+ const masterRole = params.length
+ ? await IMidasAccessControlManaged__factory.connect(
+ targetContract,
+ accessControl.provider,
+ ).contractAdminRole()
+ : constants.HashZero;
+
+ if (await handleRevert(callFn, accessControl, opt)) {
return;
}
- await expect(
- accessControl
- .connect(opt?.from ?? owner)
- .revokeRole(role ?? (await greenlistable.GREENLISTED_ROLE()), account),
- ).to.emit(
+ const statesBefore = await Promise.all(
+ params.map(async (param) => {
+ return await accessControl[
+ 'isFunctionAccessGrantOperator(bytes32,address,bytes4,address)'
+ ](masterRole, targetContract, param.functionSelector, param.operator);
+ }),
+ );
+
+ const txPromise = callFn();
+ let grantOperatorExpect: ReturnType | undefined;
+ for (const [index, stateBefore] of statesBefore.entries()) {
+ const param = params[index];
+
+ if (stateBefore !== param.enabled) {
+ if (grantOperatorExpect === undefined) {
+ grantOperatorExpect = expect(txPromise)
+ .to.emit(
+ accessControl,
+ accessControl.interface.events[
+ 'SetGrantOperatorRole(bytes32,address,address,bytes4,bool)'
+ ].name,
+ )
+ .withArgs(
+ masterRole,
+ targetContract,
+ param.operator,
+ param.functionSelector,
+ param.enabled,
+ );
+ } else {
+ grantOperatorExpect = grantOperatorExpect.to
+ .emit(
+ accessControl,
+ accessControl.interface.events[
+ 'SetGrantOperatorRole(bytes32,address,address,bytes4,bool)'
+ ].name,
+ )
+ .withArgs(
+ masterRole,
+ targetContract,
+ param.operator,
+ param.functionSelector,
+ param.enabled,
+ );
+ }
+ }
+ }
+ if (grantOperatorExpect !== undefined) {
+ await grantOperatorExpect;
+ } else {
+ await txPromise;
+ }
+
+ await asyncForEach(statesBefore.entries(), async ([index, stateBefore]) => {
+ const param = params[index];
+
+ expect(
+ await accessControl[
+ 'isFunctionAccessGrantOperator(bytes32,address,bytes4,address)'
+ ](masterRole, targetContract, param.functionSelector, param.operator),
+ ).eq(param.enabled);
+
+ if (param.delay !== undefined && BigNumber.from(param.delay).gt(0)) {
+ const operatorKey = await accessControl.grantOperatorRoleKey(
+ masterRole,
+ targetContract,
+ param.functionSelector,
+ );
+ const [actualDelay] = await accessControl.getRoleTimelockDelay(
+ operatorKey,
+ 0,
+ );
+ expect(actualDelay).eq(param.delay);
+ }
+ });
+};
+
+export type SetPermissionRoleParam = {
+ functionSelector: string;
+ account: string;
+ enabled: boolean;
+ delay?: BigNumberish;
+};
+
+const normalizeSetPermissionRoleParams = (
+ functionSelector: string,
+ params: {
+ account: string;
+ enabled: boolean;
+ delay?: BigNumberish;
+ }[],
+ defaultDelay?: BigNumberish,
+): SetPermissionRoleParam[] =>
+ params.map((param) => ({
+ functionSelector,
+ account: param.account,
+ enabled: param.enabled,
+ delay: param.delay ?? defaultDelay ?? 0,
+ }));
+
+export const setPermissionRoleMultTester = async (
+ {
accessControl,
- accessControl.interface.events['RoleRevoked(bytes32,address,address)'].name,
- ).to.not.reverted;
-
- expect(
- await accessControl.hasRole(
- role ?? (await accessControl.GREENLISTED_ROLE()),
- account,
- ),
- ).eq(false);
+ owner,
+ }: {
+ accessControl: MidasAccessControl;
+ owner: SignerWithAddress;
+ },
+ masterRole: string | undefined,
+ targetContract: string,
+ params: SetPermissionRoleParam[],
+ opt?: OptionalCommonParams,
+) => {
+ const from = opt?.from ?? owner;
+
+ const normalizedParams = params.map((param) => ({
+ functionSelector: param.functionSelector,
+ account: param.account,
+ enabled: param.enabled,
+ delay: param.delay ?? 0,
+ }));
+
+ const callFn = () =>
+ masterRole
+ ? accessControl
+ .connect(from)
+ [
+ 'setPermissionRoleMult(bytes32,address,(bytes4,address,uint32,bool)[])'
+ ](masterRole, targetContract, normalizedParams)
+ : accessControl
+ .connect(from)
+ ['setPermissionRoleMult(address,(bytes4,address,uint32,bool)[])'](
+ targetContract,
+ normalizedParams,
+ );
+
+ if (await handleRevert(callFn, accessControl, opt)) {
+ return;
+ }
+
+ masterRole =
+ masterRole ??
+ (await IMidasAccessControlManaged__factory.connect(
+ targetContract,
+ accessControl.provider,
+ ).contractAdminRole());
+
+ const statesBefore = await Promise.all(
+ normalizedParams.map(async (param) => {
+ return await accessControl[
+ 'hasFunctionPermission(bytes32,address,bytes4,address)'
+ ](masterRole, targetContract, param.functionSelector, param.account);
+ }),
+ );
+
+ const txPromise = callFn();
+ let permissionExpect: ReturnType | undefined;
+ for (const [index, stateBefore] of statesBefore.entries()) {
+ const param = normalizedParams[index];
+
+ if (stateBefore !== param.enabled) {
+ if (permissionExpect === undefined) {
+ permissionExpect = expect(txPromise)
+ .to.emit(
+ accessControl,
+ accessControl.interface.events[
+ 'SetPermissionRole(bytes32,address,address,bytes4,bool)'
+ ].name,
+ )
+ .withArgs(
+ masterRole,
+ targetContract,
+ param.account,
+ param.functionSelector,
+ param.enabled,
+ );
+ } else {
+ permissionExpect = permissionExpect.to
+ .emit(
+ accessControl,
+ accessControl.interface.events[
+ 'SetPermissionRole(bytes32,address,address,bytes4,bool)'
+ ].name,
+ )
+ .withArgs(
+ masterRole,
+ targetContract,
+ param.account,
+ param.functionSelector,
+ param.enabled,
+ );
+ }
+ }
+ }
+ if (permissionExpect !== undefined) {
+ await permissionExpect;
+ } else {
+ await txPromise;
+ }
+
+ const delayChecks = new Map();
+ for (const param of normalizedParams) {
+ const delay = param.delay ?? 0;
+ if (BigNumber.from(delay).gt(0)) {
+ const key = await accessControl.permissionRoleKey(
+ masterRole,
+ targetContract,
+ param.functionSelector,
+ );
+ if (!delayChecks.has(key)) {
+ delayChecks.set(key, delay);
+ }
+ }
+ }
+
+ for (const [key, delay] of delayChecks.entries()) {
+ const [actualDelay] = await accessControl.getRoleTimelockDelay(key, 0);
+ expect(actualDelay).eq(delay);
+ }
+
+ await asyncForEach(statesBefore.entries(), async ([index]) => {
+ const param = normalizedParams[index];
+
+ expect(
+ await accessControl[
+ 'hasFunctionPermission(bytes32,address,bytes4,address)'
+ ](masterRole, targetContract, param.functionSelector, param.account),
+ ).eq(param.enabled);
+ });
+};
+
+export const setPermissionRoleTester = async (
+ {
+ accessControl,
+ owner,
+ }: {
+ accessControl: MidasAccessControl;
+ owner: SignerWithAddress;
+ },
+ masterRole: string | undefined,
+ targetContract: string,
+ functionSelector: string,
+ params: {
+ account: string;
+ enabled: boolean;
+ delay?: BigNumberish;
+ }[],
+ delay?: BigNumberish,
+ opt?: OptionalCommonParams,
+) =>
+ setPermissionRoleMultTester(
+ { accessControl, owner },
+ masterRole,
+ targetContract,
+ normalizeSetPermissionRoleParams(functionSelector, params, delay),
+ opt,
+ );
+
+type SetupFunctionAccessGrantOperatorParams = {
+ accessControl: MidasAccessControl;
+ owner: SignerWithAddress;
+ // TODO: remove it
+ masterRole: string;
+ targetContract: string;
+ functionSelector: string;
+ grantOperator: SignerWithAddress;
+};
+
+export const setupGrantOperatorRole = async ({
+ accessControl,
+ owner,
+ targetContract,
+ functionSelector,
+ grantOperator,
+}: SetupFunctionAccessGrantOperatorParams) => {
+ await setGrantOperatorRoleTester({ accessControl, owner }, targetContract, [
+ {
+ functionSelector,
+ operator: grantOperator.address,
+ enabled: true,
+ },
+ ]);
+};
+
+export const setupPermissionRole = async (
+ {
+ accessControl,
+ owner,
+ }: { accessControl: MidasAccessControl; owner: SignerWithAddress },
+ vaultRole: string,
+ vaultAddress: string,
+ functionSignature: string,
+ account: string,
+) => {
+ const selector = encodeFnSelector(functionSignature);
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: vaultRole,
+ targetContract: vaultAddress,
+ functionSelector: selector,
+ grantOperator: owner,
+ });
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ vaultRole,
+ vaultAddress,
+ selector,
+ [
+ {
+ account,
+ enabled: true,
+ },
+ ],
+ );
+};
+type CommonParamsAccessControl = {
+ timelockManager: MidasTimelockManager;
+ accessControl: MidasAccessControl;
+ owner: SignerWithAddress;
+ timelock: MidasAccessControlTimelockController;
+};
+
+// TODO: refactor, role and delays should be an array of objects
+export const setRoleTimelocksTester = async (
+ { accessControl, owner }: CommonParamsAccessControl,
+ roles: string[],
+ delays: BigNumberish[],
+ opt?: OptionalCommonParams,
+) => {
+ const from = opt?.from ?? owner;
+
+ const params = roles.map((role, index) => ({
+ role,
+ delay: delays[index],
+ }));
+
+ const callFn = accessControl
+ .connect(from)
+ .setRoleDelayMult.bind(this, params);
+
+ if (await handleRevert(callFn, accessControl, opt)) {
+ return;
+ }
+
+ await expect(callFn())
+ .to.emit(accessControl, 'SetRoleDelays')
+ .withArgs((actualParams: { role: string; delay: BigNumberish }[]) => {
+ expect(actualParams.length).eq(params.length);
+ params.forEach((param, index) => {
+ expect(actualParams[index].role).eq(param.role);
+ expect(actualParams[index].delay).eq(param.delay);
+ });
+ return true;
+ });
+
+ await asyncForEach(roles.entries(), async ([index, role]) => {
+ const delayParam = delays[index];
+ const [delay, isDefault] = await accessControl.getRoleTimelockDelay(
+ role,
+ 0,
+ );
+ const expectedDelay = BigNumber.from(0).eq(delayParam)
+ ? 3600
+ : NO_DELAY.eq(delayParam)
+ ? 0
+ : delayParam;
+
+ expect(delay).eq(expectedDelay);
+ expect(isDefault).eq(BigNumber.from(0).eq(delayParam));
+ });
+};
+
+export const setRoleTimelocksAndExecute = async (
+ {
+ owner,
+ accessControl,
+ timelock,
+ timelockManager,
+ }: CommonParamsAccessControl,
+ params: {
+ role: string;
+ delay: BigNumberish;
+ }[],
+ opt?: OptionalCommonParams,
+) => {
+ const [delay] = await accessControl.getRoleTimelockDelay(
+ constants.HashZero,
+ 0,
+ );
+
+ const data = accessControl.interface.encodeFunctionData('setRoleDelayMult', [
+ params,
+ ]);
+
+ const from = opt?.from ?? owner;
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [accessControl.address],
+ [data],
+ { isSetCouncilOperation: false },
+ { from },
+ );
+ await increase(delay + 1);
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ accessControl.address,
+ data,
+ from.address,
+ { from },
+ );
+};
+
+export const setDefaultDelayTest = async (
+ { accessControl, owner }: CommonParamsAccessControl,
+ defaultDelay: BigNumberish,
+ opt?: OptionalCommonParams,
+) => {
+ const from = opt?.from ?? owner;
+ const callFn = accessControl
+ .connect(from)
+ .setDefaultDelay.bind(this, defaultDelay);
+
+ if (await handleRevert(callFn, accessControl, opt)) {
+ return;
+ }
+
+ await expect(callFn())
+ .to.emit(
+ accessControl,
+ accessControl.interface.events['SetDefaultDelay(uint32)'].name,
+ )
+ .withArgs(defaultDelay);
+
+ expect(await accessControl.defaultDelay()).to.eq(defaultDelay);
};
diff --git a/test/common/axelar.helpers.ts b/test/common/axelar.helpers.ts
index 63bf0f0a..72c6dacf 100644
--- a/test/common/axelar.helpers.ts
+++ b/test/common/axelar.helpers.ts
@@ -1,9 +1,13 @@
import { expect } from 'chai';
-import { BigNumberish, constants, Contract } from 'ethers';
+import { BigNumberish, constants } from 'ethers';
import { parseUnits } from 'ethers/lib/utils';
import { ethers } from 'hardhat';
-import { OptionalCommonParams, tokenAmountToBase18 } from './common.helpers';
+import {
+ handleRevert,
+ OptionalCommonParams,
+ tokenAmountToBase18,
+} from './common.helpers';
import { calcExpectedMintAmount } from './deposit-vault.helpers';
import { axelarFixture } from './fixtures';
import { calcExpectedTokenOutAmount } from './redemption-vault.helpers';
@@ -51,10 +55,6 @@ export const sendIts = async (
},
opt?: {
revertOnDst?: boolean;
- revertWithCustomError?: {
- contract: Contract;
- error: string;
- };
} & OptionalCommonParams,
) => {
const from = opt?.from ?? owner;
@@ -73,27 +73,19 @@ export const sendIts = async (
const itsFrom = direction === 'A_TO_B' ? axelarItsA : axelarItsB;
- const params = [
- mTokenId,
- direction === 'A_TO_B' ? chainNameB : chainNameA,
- recipient,
- amountParsed,
- ] as const;
-
- if (opt?.revertMessage && !opt?.revertOnDst) {
- await expect(
- itsFrom.connect(opt?.from ?? owner).interchainTransfer(...params),
- ).revertedWith(opt?.revertMessage);
- return;
- }
-
- if (opt?.revertWithCustomError && !opt?.revertOnDst) {
- await expect(
- itsFrom.connect(opt?.from ?? owner).interchainTransfer(...params),
- ).revertedWithCustomError(
- opt?.revertWithCustomError.contract,
- opt?.revertWithCustomError.error,
+ const callFn = itsFrom
+ .connect(opt?.from ?? owner)
+ .interchainTransfer.bind(
+ this,
+ mTokenId,
+ direction === 'A_TO_B' ? chainNameB : chainNameA,
+ recipient,
+ amountParsed,
+ [],
+ 0,
);
+
+ if (!opt?.revertOnDst && (await handleRevert(callFn, itsFrom, opt))) {
return;
}
@@ -101,8 +93,10 @@ export const sendIts = async (
const balanceFromBefore = await mTBILL.balanceOf(from.address);
const balanceToBefore = await mTBILL.balanceOf(recipient);
- await expect(itsFrom.connect(from).interchainTransfer(...params)).not
- .reverted;
+ await expect(callFn()).to.emit(
+ mTBILL,
+ mTBILL.interface.events['Transfer(address,address,uint256)'].name,
+ );
const totalSupplyAfter = await mTBILL.totalSupply();
const balanceFromAfter = await mTBILL.balanceOf(from.address);
@@ -152,12 +146,7 @@ export const depositAndSend = async (
referrerId?: string;
minReceiveAmount?: BigNumberish;
},
- opt?: {
- revertWithCustomError?: {
- contract: Contract;
- error: string;
- };
- } & OptionalCommonParams,
+ opt?: OptionalCommonParams,
) => {
const from = opt?.from ?? owner;
recipient ??= from.address;
@@ -200,20 +189,11 @@ export const depositAndSend = async (
);
const params = [amountParsed, encodedData] as const;
- if (opt?.revertMessage) {
- await expect(
- executor.connect(opt?.from ?? owner).depositAndSend(...params),
- ).revertedWith(opt?.revertMessage);
- return;
- }
+ const callFn = executor
+ .connect(opt?.from ?? owner)
+ .depositAndSend.bind(this, ...params);
- if (opt?.revertWithCustomError) {
- await expect(
- executor.connect(opt?.from ?? owner).depositAndSend(...params),
- ).revertedWithCustomError(
- opt?.revertWithCustomError.contract,
- opt?.revertWithCustomError.error,
- );
+ if (await handleRevert(callFn, executor, opt)) {
return;
}
@@ -221,7 +201,7 @@ export const depositAndSend = async (
const balanceFromBefore = await pToken.balanceOf(from.address);
const balanceToBefore = await mTBILL.balanceOf(recipient);
- await expect(executor.connect(from).depositAndSend(...params))
+ await expect(callFn())
.to.emit(
executor,
executor.interface.events['Deposited(bytes,bytes,string,uint256,uint256)']
@@ -265,12 +245,7 @@ export const redeemAndSend = async (
direction?: 'A_TO_A' | 'B_TO_B' | 'B_TO_A' | 'A_TO_B';
minReceiveAmount?: BigNumberish;
},
- opt?: {
- revertWithCustomError?: {
- contract: Contract;
- error: string;
- };
- } & OptionalCommonParams,
+ opt?: OptionalCommonParams,
) => {
const from = opt?.from ?? owner;
recipient ??= from.address;
@@ -306,18 +281,9 @@ export const redeemAndSend = async (
);
const params = [amountParsed, encodedData] as const;
- const txFn = executor.connect(from).redeemAndSend.bind(this, ...params);
-
- if (opt?.revertMessage) {
- await expect(txFn()).revertedWith(opt?.revertMessage);
- return;
- }
+ const callFn = executor.connect(from).redeemAndSend.bind(this, ...params);
- if (opt?.revertWithCustomError) {
- await expect(txFn()).revertedWithCustomError(
- opt?.revertWithCustomError.contract,
- opt?.revertWithCustomError.error,
- );
+ if (await handleRevert(callFn, executor, opt)) {
return;
}
@@ -325,7 +291,7 @@ export const redeemAndSend = async (
const balanceFromBefore = await mTBILL.balanceOf(from.address);
const balanceToBefore = await pToken.balanceOf(recipient);
- await expect(txFn())
+ await expect(callFn())
.to.emit(
executor,
executor.interface.events['Redeemed(bytes,bytes,string,uint256,uint256)']
diff --git a/test/common/common.helpers.ts b/test/common/common.helpers.ts
index ca7c9f3f..10d358ec 100644
--- a/test/common/common.helpers.ts
+++ b/test/common/common.helpers.ts
@@ -1,22 +1,49 @@
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { expect } from 'chai';
-import { BigNumber, BigNumberish, Contract } from 'ethers';
+import {
+ BigNumber,
+ BigNumberish,
+ Contract,
+ ContractFactory,
+ ContractTransaction,
+} from 'ethers';
import { parseUnits, solidityKeccak256 } from 'ethers/lib/utils';
import { ethers } from 'hardhat';
+import { DefaultFixture } from './fixtures';
+
import {
ERC20,
+ ERC20__factory,
ERC20Mock,
IERC20Metadata,
- MTBILL,
- Pausable,
+ MidasPauseManager,
+ MToken,
+ USTBMock,
} from '../../typechain-types';
-
-export type OptionalCommonParams = {
- from?: SignerWithAddress;
- revertMessage?: string;
+type RevertCustomError = {
+ contract?: Contract;
+ customErrorName: string;
+ args?: unknown[];
};
+export type OptionalCommonParams =
+ | {
+ from?: SignerWithAddress;
+ revertMessage?: string;
+ }
+ | {
+ from?: SignerWithAddress;
+ revertCustomError: RevertCustomError;
+ }
+ | {
+ from?: SignerWithAddress;
+ revertCustomError: (
+ args?: unknown[],
+ contract?: Contract,
+ ) => RevertCustomError;
+ };
+
export type Account = SignerWithAddress | string;
export type AccountOrContract = Account | Contract;
@@ -24,6 +51,50 @@ export const keccak256 = (role: string) => {
return solidityKeccak256(['string'], [role]);
};
+export const shouldRevert = (opt?: OptionalCommonParams) => {
+ return (
+ opt &&
+ (('revertMessage' in opt && opt.revertMessage) ||
+ ('revertCustomError' in opt && opt.revertCustomError))
+ );
+};
+
+export const handleRevert = async (
+ txOrTxFn: (() => Promise) | Promise,
+ contract: Contract,
+ opt?: OptionalCommonParams,
+) => {
+ if (!opt || !shouldRevert(opt)) return false;
+
+ const getPromise = () =>
+ typeof txOrTxFn === 'function' ? txOrTxFn() : txOrTxFn;
+
+ if ('revertCustomError' in opt && opt.revertCustomError) {
+ const txPromise = getPromise();
+ const revertCustomError =
+ typeof opt.revertCustomError === 'function'
+ ? opt.revertCustomError(undefined, contract)
+ : opt.revertCustomError;
+
+ const match = expect(txPromise).revertedWithCustomError(
+ revertCustomError.contract ?? contract,
+ revertCustomError.customErrorName,
+ );
+
+ await (revertCustomError.args
+ ? match.withArgs(...revertCustomError.args)
+ : match);
+
+ return true;
+ } else if ('revertMessage' in opt && opt.revertMessage) {
+ const txPromise = getPromise();
+ await expect(txPromise).revertedWith(opt.revertMessage);
+ return true;
+ } else {
+ return false;
+ }
+};
+
export const getAccount = (account: AccountOrContract) => {
return (
(account as SignerWithAddress).address ??
@@ -32,88 +103,411 @@ export const getAccount = (account: AccountOrContract) => {
);
};
+type PauseParams = {
+ pauseManager: MidasPauseManager;
+ owner: SignerWithAddress;
+};
+
+export const pauseGlobalTest = async (
+ { pauseManager, owner }: PauseParams,
+ opt?: OptionalCommonParams,
+) => {
+ const from = opt?.from ?? owner;
+
+ const callFn = pauseManager.connect(from).globalPause.bind(this);
+
+ if (await handleRevert(callFn, pauseManager, opt)) {
+ return;
+ }
+
+ const wasPaused = await pauseManager.globalPaused();
+
+ if (wasPaused) {
+ await callFn();
+ } else {
+ await expect(callFn())
+ .to.emit(
+ pauseManager,
+ pauseManager.interface.events['GlobalPauseStatusChange(bool)'].name,
+ )
+ .withArgs(true);
+ }
+
+ expect(await pauseManager.globalPaused()).eq(true);
+};
+
+export const unpauseGlobalTest = async (
+ { pauseManager, owner }: PauseParams,
+ opt?: OptionalCommonParams,
+) => {
+ const from = opt?.from ?? owner;
+
+ if (
+ await handleRevert(
+ pauseManager.connect(from).globalUnpause.bind(this),
+ pauseManager,
+ opt,
+ )
+ ) {
+ return;
+ }
+
+ const wasPaused = await pauseManager.globalPaused();
+
+ if (!wasPaused) {
+ await pauseManager.connect(from).globalUnpause();
+ } else {
+ await expect(pauseManager.connect(from).globalUnpause())
+ .to.emit(
+ pauseManager,
+ pauseManager.interface.events['GlobalPauseStatusChange(bool)'].name,
+ )
+ .withArgs(false);
+ }
+
+ expect(await pauseManager.globalPaused()).eq(false);
+};
+
+// TODO: rename to pauseContracts
export const pauseVault = async (
- vault: Pausable,
+ { pauseManager, owner }: PauseParams,
+ contracts: Contract | Contract[],
opt?: OptionalCommonParams,
) => {
- const [defaultSigner] = await ethers.getSigners();
+ const from = opt?.from ?? owner;
- if (opt?.revertMessage) {
- await expect(
- vault.connect(opt?.from ?? defaultSigner).pause(),
- ).revertedWith(opt?.revertMessage);
+ const contractsArr = Array.isArray(contracts) ? contracts : [contracts];
+
+ if (
+ await handleRevert(
+ pauseManager.connect(from).bulkPauseContract.bind(
+ this,
+ contractsArr.map((c) => c.address),
+ ),
+ pauseManager,
+ opt,
+ )
+ ) {
return;
}
- await expect(await vault.connect(opt?.from ?? defaultSigner).pause()).not
- .reverted;
+ const contractAddresses = contractsArr.map((c) => c.address);
+ const addressesToPause = (
+ await Promise.all(
+ contractAddresses.map(async (contractAddr) => ({
+ contractAddr,
+ paused: await pauseManager.contractPaused(contractAddr),
+ })),
+ )
+ )
+ .filter(({ paused }) => !paused)
+ .map(({ contractAddr }) => contractAddr);
+
+ const tx = pauseManager.connect(from).bulkPauseContract(contractAddresses);
+ if (addressesToPause.length > 0) {
+ let pauseExpect = expect(tx);
+ for (const contractAddr of addressesToPause) {
+ pauseExpect = pauseExpect.to
+ .emit(
+ pauseManager,
+ pauseManager.interface.events[
+ 'ContractPauseStatusChange(address,bool)'
+ ].name,
+ )
+ .withArgs(contractAddr, true);
+ }
+ await pauseExpect;
+ } else {
+ await tx;
+ }
- expect(await vault.paused()).eq(true);
+ await asyncForEach(contractsArr, async (contract) => {
+ expect(await pauseManager.isPaused(contract.address, '0x00000000')).eq(
+ true,
+ );
+ expect(await pauseManager.contractPaused(contract.address)).eq(true);
+ });
};
+// TODO: rename to unpauseContracts
+export const unpauseVault = async (
+ { owner, pauseManager }: PauseParams,
+ contracts: Contract | Contract[],
+ opt?: OptionalCommonParams,
+) => {
+ const from = opt?.from ?? owner;
+
+ const contractsArr = Array.isArray(contracts) ? contracts : [contracts];
+
+ if (
+ await handleRevert(
+ pauseManager.connect(from).bulkUnpauseContract.bind(
+ this,
+ contractsArr.map((c) => c.address),
+ ),
+ pauseManager,
+ opt,
+ )
+ ) {
+ return;
+ }
+
+ const contractAddresses = contractsArr.map((c) => c.address);
+ const addressesToUnpause = (
+ await Promise.all(
+ contractAddresses.map(async (contractAddr) => ({
+ contractAddr,
+ paused: await pauseManager.contractPaused(contractAddr),
+ })),
+ )
+ )
+ .filter(({ paused }) => paused)
+ .map(({ contractAddr }) => contractAddr);
+
+ const tx = pauseManager.connect(from).bulkUnpauseContract(contractAddresses);
+ if (addressesToUnpause.length > 0) {
+ let unpauseExpect = expect(tx);
+ for (const contractAddr of addressesToUnpause) {
+ unpauseExpect = unpauseExpect.to
+ .emit(
+ pauseManager,
+ pauseManager.interface.events[
+ 'ContractPauseStatusChange(address,bool)'
+ ].name,
+ )
+ .withArgs(contractAddr, false);
+ }
+ await unpauseExpect;
+ } else {
+ await tx;
+ }
+
+ await asyncForEach(contractsArr, async (contract) => {
+ expect(await pauseManager.isPaused(contract.address, '0x00000000')).eq(
+ false,
+ );
+ expect(await pauseManager.contractPaused(contract.address)).eq(false);
+ });
+};
+
+// TODO: rename to pauseContractsFn
export const pauseVaultFn = async (
- vault: Pausable,
- fnSelector: string,
+ { pauseManager, owner }: PauseParams,
+ contracts: Contract | Contract[],
+ fnSelector: string | string[],
opt?: OptionalCommonParams,
) => {
- const [defaultSigner] = await ethers.getSigners();
+ const from = opt?.from ?? owner;
- if (opt?.revertMessage) {
- await expect(
- vault.connect(opt?.from ?? defaultSigner).pauseFn(fnSelector),
- ).revertedWith(opt?.revertMessage);
+ const selectors = Array.isArray(fnSelector) ? fnSelector : [fnSelector];
+
+ const contractsArr = Array.isArray(contracts) ? contracts : [contracts];
+
+ if (
+ await handleRevert(
+ pauseManager.connect(from).bulkPauseContractFn.bind(
+ this,
+ contractsArr.map((c) => c.address),
+ selectors,
+ ),
+ pauseManager,
+ opt,
+ )
+ ) {
return;
}
- await expect(
- await vault.connect(opt?.from ?? defaultSigner).pauseFn(fnSelector),
- ).not.reverted;
+ const contractAddresses = contractsArr.map((c) => c.address);
+ const fnPauseTargets = (
+ await Promise.all(
+ contractAddresses.flatMap((contractAddr) =>
+ selectors.map(async (fnSelector) => ({
+ contractAddr,
+ fnSelector,
+ paused: await pauseManager.isFunctionPaused(contractAddr, fnSelector),
+ })),
+ ),
+ )
+ ).filter(({ paused }) => !paused);
+
+ const tx = pauseManager
+ .connect(from)
+ .bulkPauseContractFn(contractAddresses, selectors);
+ if (fnPauseTargets.length > 0) {
+ let pauseFnExpect = expect(tx);
+ for (const { contractAddr, fnSelector } of fnPauseTargets) {
+ pauseFnExpect = pauseFnExpect.to
+ .emit(
+ pauseManager,
+ pauseManager.interface.events[
+ 'FnPauseStatusChange(address,bytes4,bool)'
+ ].name,
+ )
+ .withArgs(contractAddr, fnSelector, true);
+ }
+ await pauseFnExpect;
+ } else {
+ await tx;
+ }
- expect(await vault.fnPaused(fnSelector)).eq(true);
+ await asyncForEach(contractsArr, async (contract) => {
+ await asyncForEach(selectors, async (fnSelector) => {
+ expect(await pauseManager.isPaused(contract.address, fnSelector)).eq(
+ true,
+ );
+ expect(
+ await pauseManager.contractFnPaused(contract.address, fnSelector),
+ ).eq(true);
+ });
+ });
};
+// TODO: rename to unpauseContractsFn
export const unpauseVaultFn = async (
- vault: Pausable,
- fnSelector: string,
+ { pauseManager, owner }: PauseParams,
+ contracts: Contract | Contract[],
+ fnSelector: string | string[],
opt?: OptionalCommonParams,
) => {
- const [defaultSigner] = await ethers.getSigners();
+ const from = opt?.from ?? owner;
+
+ const selectors = Array.isArray(fnSelector) ? fnSelector : [fnSelector];
- if (opt?.revertMessage) {
- await expect(
- vault.connect(opt?.from ?? defaultSigner).unpauseFn(fnSelector),
- ).revertedWith(opt?.revertMessage);
+ const contractsArr = Array.isArray(contracts) ? contracts : [contracts];
+ if (
+ await handleRevert(
+ pauseManager.connect(from).bulkUnpauseContractFn.bind(
+ this,
+ contractsArr.map((c) => c.address),
+ selectors,
+ ),
+ pauseManager,
+ opt,
+ )
+ ) {
return;
}
- await expect(
- await vault.connect(opt?.from ?? defaultSigner).unpauseFn(fnSelector),
- ).not.reverted;
+ const contractAddresses = contractsArr.map((c) => c.address);
+ const fnUnpauseTargets = (
+ await Promise.all(
+ contractAddresses.flatMap((contractAddr) =>
+ selectors.map(async (fnSelector) => ({
+ contractAddr,
+ fnSelector,
+ paused: await pauseManager.isFunctionPaused(contractAddr, fnSelector),
+ })),
+ ),
+ )
+ ).filter(({ paused }) => paused);
+
+ const tx = pauseManager
+ .connect(from)
+ .bulkUnpauseContractFn(contractAddresses, selectors);
+ if (fnUnpauseTargets.length > 0) {
+ let unpauseFnExpect = expect(tx);
+ for (const { contractAddr, fnSelector } of fnUnpauseTargets) {
+ unpauseFnExpect = unpauseFnExpect.to
+ .emit(
+ pauseManager,
+ pauseManager.interface.events[
+ 'FnPauseStatusChange(address,bytes4,bool)'
+ ].name,
+ )
+ .withArgs(contractAddr, fnSelector, false);
+ }
+ await unpauseFnExpect;
+ } else {
+ await tx;
+ }
- expect(await vault.fnPaused(fnSelector)).eq(false);
+ await asyncForEach(contractsArr, async (contract) => {
+ await asyncForEach(selectors, async (fnSelector) => {
+ expect(await pauseManager.isPaused(contract.address, fnSelector)).eq(
+ false,
+ );
+ expect(
+ await pauseManager.contractFnPaused(contract.address, fnSelector),
+ ).eq(false);
+ });
+ });
};
-export const unpauseVault = async (
- vault: Pausable,
+export const adminPauseContractTest = async (
+ { pauseManager, owner }: PauseParams,
+ contract: Contract,
opt?: OptionalCommonParams,
) => {
- const [defaultSigner] = await ethers.getSigners();
+ const from = opt?.from ?? owner;
- if (opt?.revertMessage) {
- await expect(
- vault.connect(opt?.from ?? defaultSigner).unpause(),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ pauseManager
+ .connect(from)
+ .contractAdminPause.bind(this, contract.address),
+ pauseManager,
+ opt,
+ )
+ ) {
return;
}
+ const alreadyPaused = await pauseManager.contractPaused(contract.address);
+ const tx = pauseManager.connect(from).contractAdminPause(contract.address);
- await expect(await vault.connect(opt?.from ?? defaultSigner).unpause()).not
- .reverted;
+ if (alreadyPaused) {
+ await tx;
+ } else {
+ await expect(tx)
+ .to.emit(
+ pauseManager,
+ pauseManager.interface.events['ContractPauseStatusChange(address,bool)']
+ .name,
+ )
+ .withArgs(contract.address, true);
+ }
- expect(await vault.paused()).eq(false);
+ expect(await pauseManager.contractPaused(contract.address)).eq(true);
+ expect(await pauseManager.isPaused(contract.address, '0x00000000')).eq(true);
+};
+
+export const adminUnpauseContractTest = async (
+ { pauseManager, owner }: PauseParams,
+ contract: Contract,
+ opt?: OptionalCommonParams,
+) => {
+ const from = opt?.from ?? owner;
+ if (
+ await handleRevert(
+ pauseManager
+ .connect(from)
+ .contractAdminUnpause.bind(this, contract.address),
+ pauseManager,
+ opt,
+ )
+ ) {
+ return;
+ }
+ const alreadyPaused = await pauseManager.contractPaused(contract.address);
+ const tx = pauseManager.connect(from).contractAdminUnpause(contract.address);
+
+ if (!alreadyPaused) {
+ await tx;
+ } else {
+ await expect(tx)
+ .to.emit(
+ pauseManager,
+ pauseManager.interface.events['ContractPauseStatusChange(address,bool)']
+ .name,
+ )
+ .withArgs(contract.address, false);
+ }
+
+ expect(await pauseManager.contractPaused(contract.address)).eq(false);
+ expect(await pauseManager.isPaused(contract.address, '0x00000000')).eq(false);
};
export const mintToken = async (
- token: ERC20Mock | MTBILL,
+ token: ERC20Mock | MToken | USTBMock,
to: AccountOrContract,
amountN: number,
) => {
@@ -182,12 +576,126 @@ export const tokenAmountFromBase18 = async (
};
export const balanceOfBase18 = async (
- token: ERC20 | IERC20Metadata,
+ token: ERC20 | IERC20Metadata | string,
of: AccountOrContract,
) => {
+ if (typeof token === 'string') {
+ token = ERC20__factory.connect(token, ethers.provider);
+ }
+
if (token.address === ethers.constants.AddressZero)
return ethers.constants.Zero;
of = getAccount(of);
const balance = await token.balanceOf(of);
return tokenAmountToBase18(token, balance);
};
+
+export const getCurrentBlockTimestamp = async () => {
+ return (await ethers.provider.getBlock('latest')).timestamp;
+};
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export type Constructor = new (...args: any[]) => T;
+
+export const validateImplementation = async (
+ _implementationFactory: Constructor | ContractFactory,
+) => {
+ // FIXME: hardhat-upgrades call fails because it does not accept the constructor arguments
+ // const factory =
+ // typeof implementationFactory === 'function'
+ // ? new implementationFactory()
+ // : implementationFactory;
+ // await hre.upgrades.validateImplementation(factory);
+};
+
+export type InitializeParamCase = {
+ title: string;
+ params:
+ | Partial
+ | ((
+ fixture: DefaultFixture,
+ contract?: Contract,
+ ) => Partial | Promise>);
+ contract?:
+ | Contract
+ | ((fixture: DefaultFixture) => Contract | Promise);
+ revertCustomError: {
+ customErrorName: string;
+ args?:
+ | unknown[]
+ | ((fixture: DefaultFixture, contract?: Contract) => unknown[]);
+ };
+};
+
+export type InitializeParamsOpt = OptionalCommonParams & {
+ contract?: Contract;
+};
+
+const runInitializeParamCase = async (
+ fixture: DefaultFixture,
+ paramCase: InitializeParamCase,
+ initializeFunction: (
+ fixture: DefaultFixture,
+ params: Partial,
+ opt?: InitializeParamsOpt,
+ ) => Promise,
+) => {
+ const contract =
+ paramCase.contract === undefined
+ ? undefined
+ : typeof paramCase.contract === 'function'
+ ? await paramCase.contract(fixture)
+ : paramCase.contract;
+
+ const params =
+ typeof paramCase.params === 'function'
+ ? await paramCase.params(fixture, contract)
+ : paramCase.params;
+
+ const args =
+ typeof paramCase.revertCustomError.args === 'function'
+ ? paramCase.revertCustomError.args(fixture, contract)
+ : paramCase.revertCustomError.args;
+
+ await initializeFunction(fixture, params, {
+ contract,
+ revertCustomError: {
+ customErrorName: paramCase.revertCustomError.customErrorName,
+ args,
+ },
+ });
+};
+
+export const initializeParamsSuits = (
+ paramCases: InitializeParamCase[],
+ fixtureFn: () => Promise,
+ initializeFunction: (
+ fixture: DefaultFixture,
+ params: Partial,
+ opt?: InitializeParamsOpt,
+ ) => Promise,
+) => {
+ describe('initialization params', () => {
+ // TODO: should replace with async?
+ for (const paramCase of paramCases) {
+ it(`should fail: when ${paramCase.title}`, async () => {
+ const fixture = await fixtureFn();
+ await runInitializeParamCase(fixture, paramCase, initializeFunction);
+ });
+ }
+ });
+};
+
+export const asyncForEach = async (
+ array: Iterable,
+ callback: (item: T) => Promise,
+ sync = false,
+) => {
+ if (sync) {
+ for (const item of array) {
+ await callback(item);
+ }
+ } else {
+ return await Promise.all(Array.from(array).map(callback));
+ }
+};
diff --git a/test/common/custom-feed-growth.helpers.ts b/test/common/custom-feed-growth.helpers.ts
index 9ad7bf4e..906b0487 100644
--- a/test/common/custom-feed-growth.helpers.ts
+++ b/test/common/custom-feed-growth.helpers.ts
@@ -3,7 +3,11 @@ import { expect } from 'chai';
import { BigNumber, BigNumberish } from 'ethers';
import { parseUnits } from 'ethers/lib/utils';
-import { OptionalCommonParams } from './common.helpers';
+import {
+ handleRevert,
+ OptionalCommonParams,
+ shouldRevert,
+} from './common.helpers';
import { defaultDeploy } from './fixtures';
type CommonParamsSetRoundData = Pick<
@@ -18,10 +22,13 @@ export const setOnlyUp = async (
) => {
const sender = opt?.from ?? owner;
- if (opt?.revertMessage) {
- await expect(
- customFeedGrowth.connect(sender).setOnlyUp(newOnlyUp),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ customFeedGrowth.connect(sender).setOnlyUp.bind(this, newOnlyUp),
+ customFeedGrowth,
+ opt,
+ )
+ ) {
return;
}
@@ -48,10 +55,15 @@ export const setMinGrowthApr = async (
8,
);
- if (opt?.revertMessage) {
- await expect(
- customFeedGrowth.connect(sender).setMinGrowthApr(newMinGrowthAprParsed),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ customFeedGrowth
+ .connect(sender)
+ .setMinGrowthApr.bind(this, newMinGrowthAprParsed),
+ customFeedGrowth,
+ opt,
+ )
+ ) {
return;
}
@@ -80,10 +92,15 @@ export const setMaxGrowthApr = async (
8,
);
- if (opt?.revertMessage) {
- await expect(
- customFeedGrowth.connect(sender).setMaxGrowthApr(newMaxGrowthAprParsed),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ customFeedGrowth
+ .connect(sender)
+ .setMaxGrowthApr.bind(this, newMaxGrowthAprParsed),
+ customFeedGrowth,
+ opt,
+ )
+ ) {
return;
}
@@ -142,8 +159,7 @@ export const setRoundDataGrowth = async (
.connect(sender)
.setRoundData.bind(this, dataParsed, timestamp, growthParsed);
- if (opt?.revertMessage) {
- await expect(callFn()).revertedWith(opt?.revertMessage);
+ if (await handleRevert(callFn, customFeedGrowth, opt)) {
return;
}
@@ -244,7 +260,7 @@ export const setRoundDataSafeGrowth = async (
growthApr: number,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
+ if (shouldRevert(opt)) {
await setRoundDataGrowth(
{
customFeedGrowth,
@@ -299,5 +315,68 @@ export const applyGrowth = ({
return BigNumber.from(answer).add(interest);
};
+export const setMaxAnswerDeviationTest = async (
+ { customFeedGrowth, owner }: CommonParamsSetRoundData,
+ maxAnswerDeviation: BigNumberish,
+ opt?: OptionalCommonParams,
+) => {
+ const sender = opt?.from ?? owner;
+
+ if (
+ await handleRevert(
+ customFeedGrowth
+ .connect(sender)
+ .setMaxAnswerDeviation.bind(this, maxAnswerDeviation),
+ customFeedGrowth,
+ opt,
+ )
+ ) {
+ return;
+ }
+
+ await expect(
+ customFeedGrowth.connect(sender).setMaxAnswerDeviation(maxAnswerDeviation),
+ )
+ .to.emit(
+ customFeedGrowth,
+ customFeedGrowth.interface.events['MaxAnswerDeviationUpdated(uint256)']
+ .name,
+ )
+ .withArgs(sender, maxAnswerDeviation).to.not.reverted;
+
+ const maxAnswerDeviationAfter = await customFeedGrowth.maxAnswerDeviation();
+ expect(maxAnswerDeviationAfter).eq(maxAnswerDeviation);
+};
+
+export const setMinMaxAnswerTest = async (
+ { customFeedGrowth, owner }: CommonParamsSetRoundData,
+ minAnswer: BigNumberish,
+ maxAnswer: BigNumberish,
+ opt?: OptionalCommonParams,
+) => {
+ const sender = opt?.from ?? owner;
+
+ if (
+ await handleRevert(
+ customFeedGrowth
+ .connect(sender)
+ .setMinMaxAnswer.bind(this, minAnswer, maxAnswer),
+ customFeedGrowth,
+ opt,
+ )
+ ) {
+ return;
+ }
+
+ await expect(
+ customFeedGrowth.connect(sender).setMinMaxAnswer(minAnswer, maxAnswer),
+ )
+ .to.emit(customFeedGrowth, 'SetMinMaxAnswer')
+ .withArgs(minAnswer, maxAnswer).to.not.reverted;
+
+ expect(await customFeedGrowth.minAnswer()).eq(minAnswer);
+ expect(await customFeedGrowth.maxAnswer()).eq(maxAnswer);
+};
+
export const calculatePriceDiviation = (last: number, next: number) =>
Math.abs(((next - last) * 100) / last);
diff --git a/test/common/custom-feed.helpers.ts b/test/common/custom-feed.helpers.ts
index c176ea06..28ca6d7b 100644
--- a/test/common/custom-feed.helpers.ts
+++ b/test/common/custom-feed.helpers.ts
@@ -1,8 +1,9 @@
import { setNextBlockTimestamp } from '@nomicfoundation/hardhat-network-helpers/dist/src/helpers/time';
import { expect } from 'chai';
+import { BigNumberish } from 'ethers';
import { parseUnits } from 'ethers/lib/utils';
-import { OptionalCommonParams } from './common.helpers';
+import { handleRevert, OptionalCommonParams } from './common.helpers';
import { defaultDeploy } from './fixtures';
type CommonParamsSetRoundData = Pick<
@@ -19,10 +20,13 @@ export const setRoundData = async (
const dataParsed = parseUnits(data.toFixed(8).replace(/\.?0+$/, ''), 8);
- if (opt?.revertMessage) {
- await expect(
- customFeed.connect(sender).setRoundData(dataParsed),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ customFeed.connect(sender).setRoundData.bind(this, dataParsed),
+ customFeed,
+ opt,
+ )
+ ) {
return;
}
@@ -74,10 +78,13 @@ export const setRoundDataSafe = async (
const dataParsed = parseUnits(data.toFixed(8).replace(/\.?0+$/, ''), 8);
- if (opt?.revertMessage) {
- await expect(
- customFeed.connect(sender).setRoundDataSafe(dataParsed),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ customFeed.connect(sender).setRoundDataSafe.bind(this, dataParsed),
+ customFeed,
+ opt,
+ )
+ ) {
return;
}
@@ -120,5 +127,65 @@ export const setRoundDataSafe = async (
expect(await customFeed.lastAnswer()).eq(lastRoundDataAfter.answer);
};
+export const setMaxAnswerDeviationTest = async (
+ { customFeed, owner }: CommonParamsSetRoundData,
+ maxAnswerDeviation: BigNumberish,
+ opt?: OptionalCommonParams,
+) => {
+ const sender = opt?.from ?? owner;
+
+ if (
+ await handleRevert(
+ customFeed
+ .connect(sender)
+ .setMaxAnswerDeviation.bind(this, maxAnswerDeviation),
+ customFeed,
+ opt,
+ )
+ ) {
+ return;
+ }
+
+ await expect(
+ customFeed.connect(sender).setMaxAnswerDeviation(maxAnswerDeviation),
+ )
+ .to.emit(
+ customFeed,
+ customFeed.interface.events['MaxAnswerDeviationUpdated(uint256)'].name,
+ )
+ .withArgs(maxAnswerDeviation).to.not.reverted;
+
+ const maxAnswerDeviationAfter = await customFeed.maxAnswerDeviation();
+ expect(maxAnswerDeviationAfter).eq(maxAnswerDeviation);
+};
+
+export const setMinMaxAnswerTest = async (
+ { customFeed, owner }: CommonParamsSetRoundData,
+ minAnswer: BigNumberish,
+ maxAnswer: BigNumberish,
+ opt?: OptionalCommonParams,
+) => {
+ const sender = opt?.from ?? owner;
+
+ if (
+ await handleRevert(
+ customFeed
+ .connect(sender)
+ .setMinMaxAnswer.bind(this, minAnswer, maxAnswer),
+ customFeed,
+ opt,
+ )
+ ) {
+ return;
+ }
+
+ await expect(customFeed.connect(sender).setMinMaxAnswer(minAnswer, maxAnswer))
+ .to.emit(customFeed, 'SetMinMaxAnswer')
+ .withArgs(minAnswer, maxAnswer).to.not.reverted;
+
+ expect(await customFeed.minAnswer()).eq(minAnswer);
+ expect(await customFeed.maxAnswer()).eq(maxAnswer);
+};
+
export const calculatePriceDiviation = (last: number, next: number) =>
Math.abs(((next - last) * 100) / last);
diff --git a/test/common/data-feed.helpers.ts b/test/common/data-feed.helpers.ts
index b13b75b9..ba981ffc 100644
--- a/test/common/data-feed.helpers.ts
+++ b/test/common/data-feed.helpers.ts
@@ -1,6 +1,13 @@
+import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
+import { expect } from 'chai';
+import { BigNumberish } from 'ethers';
import { formatUnits, parseUnits } from 'ethers/lib/utils';
-import { amountToBase18 } from './common.helpers';
+import {
+ amountToBase18,
+ handleRevert,
+ OptionalCommonParams,
+} from './common.helpers';
import { defaultDeploy } from './fixtures';
type CommonParams = Pick<
@@ -8,6 +15,68 @@ type CommonParams = Pick<
'mockedAggregator'
>;
+type CommonParamsSetHealthyDiff = Pick<
+ Awaited>,
+ 'dataFeed' | 'owner'
+>;
+
+export const setHealthyDiffTest = async (
+ { dataFeed, owner }: CommonParamsSetHealthyDiff,
+ healthyDiff: BigNumberish,
+ opt?: OptionalCommonParams,
+) => {
+ const sender: SignerWithAddress = opt?.from ?? owner;
+
+ if (
+ await handleRevert(
+ () => dataFeed.connect(sender).setHealthyDiff(healthyDiff),
+ dataFeed,
+ opt,
+ )
+ ) {
+ return;
+ }
+
+ await expect(dataFeed.connect(sender).setHealthyDiff(healthyDiff))
+ .to.emit(dataFeed, 'SetHealthyDiff')
+ .withArgs(healthyDiff);
+
+ expect(await dataFeed.healthyDiff()).eq(healthyDiff);
+};
+
+export const setMinMaxExpectedAnswerTest = async (
+ { dataFeed, owner }: CommonParamsSetHealthyDiff,
+ maxExpectedAnswer: BigNumberish,
+ minExpectedAnswer: BigNumberish,
+ opt?: OptionalCommonParams,
+) => {
+ const sender: SignerWithAddress = opt?.from ?? owner;
+
+ if (
+ await handleRevert(
+ () =>
+ dataFeed
+ .connect(sender)
+ .setMinMaxExpectedAnswer(maxExpectedAnswer, minExpectedAnswer),
+ dataFeed,
+ opt,
+ )
+ ) {
+ return;
+ }
+
+ await expect(
+ dataFeed
+ .connect(sender)
+ .setMinMaxExpectedAnswer(maxExpectedAnswer, minExpectedAnswer),
+ )
+ .to.emit(dataFeed, 'SetMinMaxExpectedAnswer')
+ .withArgs(maxExpectedAnswer, minExpectedAnswer).to.not.reverted;
+
+ expect(await dataFeed.maxExpectedAnswer()).eq(maxExpectedAnswer);
+ expect(await dataFeed.minExpectedAnswer()).eq(minExpectedAnswer);
+};
+
export const setRoundData = async (
{ mockedAggregator }: CommonParams,
newPrice: number,
diff --git a/test/common/deploy.helpers.ts b/test/common/deploy.helpers.ts
index f6a7bb04..87563a16 100644
--- a/test/common/deploy.helpers.ts
+++ b/test/common/deploy.helpers.ts
@@ -11,7 +11,7 @@ export const deployProxyContract = async <
TContract extends Contract = Contract,
>(
contractName: string,
- initParams?: unknown[],
+ initParams?: unknown[] | readonly unknown[],
initializer = 'initialize',
constructorParams?: unknown[],
): Promise => {
diff --git a/test/common/deposit-vault-aave.helpers.ts b/test/common/deposit-vault-aave.helpers.ts
index 4ca9402c..f5385c63 100644
--- a/test/common/deposit-vault-aave.helpers.ts
+++ b/test/common/deposit-vault-aave.helpers.ts
@@ -6,6 +6,8 @@ import {
OptionalCommonParams,
balanceOfBase18,
getAccount,
+ handleRevert,
+ shouldRevert,
} from './common.helpers';
import {
depositInstantTest,
@@ -42,12 +44,15 @@ export const setAaveDepositsEnabledTest = async (
enabled: boolean,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
+ if (
+ await handleRevert(
depositVaultWithAave
.connect(opt?.from ?? owner)
- .setAaveDepositsEnabled(enabled),
- ).revertedWith(opt?.revertMessage);
+ .setAaveDepositsEnabled.bind(this, enabled),
+ depositVaultWithAave,
+ opt,
+ )
+ ) {
return;
}
@@ -70,10 +75,15 @@ export const setAavePoolTest = async (
pool: string,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
- depositVaultWithAave.connect(opt?.from ?? owner).setAavePool(token, pool),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ depositVaultWithAave
+ .connect(opt?.from ?? owner)
+ .setAavePool.bind(this, token, pool),
+ depositVaultWithAave,
+ opt,
+ )
+ ) {
return;
}
@@ -81,9 +91,7 @@ export const setAavePoolTest = async (
depositVaultWithAave.connect(opt?.from ?? owner).setAavePool(token, pool),
).to.emit(
depositVaultWithAave,
- depositVaultWithAave.interface.events[
- 'SetAavePool(address,address,address)'
- ].name,
+ depositVaultWithAave.interface.events['SetAavePool(address,address)'].name,
).to.not.reverted;
const poolAfter = await depositVaultWithAave.aavePools(token);
@@ -95,10 +103,15 @@ export const removeAavePoolTest = async (
token: string,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
- depositVaultWithAave.connect(opt?.from ?? owner).removeAavePool(token),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ depositVaultWithAave
+ .connect(opt?.from ?? owner)
+ .removeAavePool.bind(this, token),
+ depositVaultWithAave,
+ opt,
+ )
+ ) {
return;
}
@@ -106,8 +119,7 @@ export const removeAavePoolTest = async (
depositVaultWithAave.connect(opt?.from ?? owner).removeAavePool(token),
).to.emit(
depositVaultWithAave,
- depositVaultWithAave.interface.events['RemoveAavePool(address,address)']
- .name,
+ depositVaultWithAave.interface.events['RemoveAavePool(address)'].name,
).to.not.reverted;
const poolAfter = await depositVaultWithAave.aavePools(token);
@@ -137,7 +149,7 @@ export const depositInstantWithAaveTest = async (
) => {
tokenIn = getAccount(tokenIn);
- if (opt?.revertMessage) {
+ if (shouldRevert(opt)) {
await depositInstantTest(
{
depositVault: depositVaultWithAave,
@@ -203,12 +215,15 @@ export const setAutoInvestFallbackEnabledAaveTest = async (
enabled: boolean,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
+ if (
+ await handleRevert(
depositVaultWithAave
.connect(opt?.from ?? owner)
- .setAutoInvestFallbackEnabled(enabled),
- ).revertedWith(opt?.revertMessage);
+ .setAutoInvestFallbackEnabled.bind(this, enabled),
+ depositVaultWithAave,
+ opt,
+ )
+ ) {
return;
}
@@ -248,7 +263,7 @@ export const depositRequestWithAaveTest = async (
) => {
tokenIn = getAccount(tokenIn);
- if (opt?.revertMessage) {
+ if (shouldRevert(opt)) {
await depositRequestTest(
{
depositVault: depositVaultWithAave,
diff --git a/test/common/deposit-vault-morpho.helpers.ts b/test/common/deposit-vault-morpho.helpers.ts
index c6c4c8ca..15fd5a16 100644
--- a/test/common/deposit-vault-morpho.helpers.ts
+++ b/test/common/deposit-vault-morpho.helpers.ts
@@ -6,6 +6,8 @@ import {
OptionalCommonParams,
balanceOfBase18,
getAccount,
+ handleRevert,
+ shouldRevert,
} from './common.helpers';
import {
depositInstantTest,
@@ -42,12 +44,15 @@ export const setMorphoDepositsEnabledTest = async (
enabled: boolean,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
+ if (
+ await handleRevert(
depositVaultWithMorpho
.connect(opt?.from ?? owner)
- .setMorphoDepositsEnabled(enabled),
- ).revertedWith(opt?.revertMessage);
+ .setMorphoDepositsEnabled.bind(this, enabled),
+ depositVaultWithMorpho,
+ opt,
+ )
+ ) {
return;
}
@@ -72,12 +77,15 @@ export const setMorphoVaultTest = async (
vault: string,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
+ if (
+ await handleRevert(
depositVaultWithMorpho
.connect(opt?.from ?? owner)
- .setMorphoVault(token, vault),
- ).revertedWith(opt?.revertMessage);
+ .setMorphoVault.bind(this, token, vault),
+ depositVaultWithMorpho,
+ opt,
+ )
+ ) {
return;
}
@@ -87,9 +95,8 @@ export const setMorphoVaultTest = async (
.setMorphoVault(token, vault),
).to.emit(
depositVaultWithMorpho,
- depositVaultWithMorpho.interface.events[
- 'SetMorphoVault(address,address,address)'
- ].name,
+ depositVaultWithMorpho.interface.events['SetMorphoVault(address,address)']
+ .name,
).to.not.reverted;
const vaultAfter = await depositVaultWithMorpho.morphoVaults(token);
@@ -101,12 +108,15 @@ export const removeMorphoVaultTest = async (
token: string,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
+ if (
+ await handleRevert(
depositVaultWithMorpho
.connect(opt?.from ?? owner)
- .removeMorphoVault(token),
- ).revertedWith(opt?.revertMessage);
+ .removeMorphoVault.bind(this, token),
+ depositVaultWithMorpho,
+ opt,
+ )
+ ) {
return;
}
@@ -114,9 +124,7 @@ export const removeMorphoVaultTest = async (
depositVaultWithMorpho.connect(opt?.from ?? owner).removeMorphoVault(token),
).to.emit(
depositVaultWithMorpho,
- depositVaultWithMorpho.interface.events[
- 'RemoveMorphoVault(address,address)'
- ].name,
+ depositVaultWithMorpho.interface.events['RemoveMorphoVault(address)'].name,
).to.not.reverted;
const vaultAfter = await depositVaultWithMorpho.morphoVaults(token);
@@ -146,7 +154,7 @@ export const depositInstantWithMorphoTest = async (
) => {
tokenIn = getAccount(tokenIn);
- if (opt?.revertMessage) {
+ if (shouldRevert(opt)) {
await depositInstantTest(
{
depositVault: depositVaultWithMorpho,
@@ -211,12 +219,15 @@ export const setAutoInvestFallbackEnabledMorphoTest = async (
enabled: boolean,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
+ if (
+ await handleRevert(
depositVaultWithMorpho
.connect(opt?.from ?? owner)
- .setAutoInvestFallbackEnabled(enabled),
- ).revertedWith(opt?.revertMessage);
+ .setAutoInvestFallbackEnabled.bind(this, enabled),
+ depositVaultWithMorpho,
+ opt,
+ )
+ ) {
return;
}
@@ -257,7 +268,7 @@ export const depositRequestWithMorphoTest = async (
) => {
tokenIn = getAccount(tokenIn);
- if (opt?.revertMessage) {
+ if (shouldRevert(opt)) {
await depositRequestTest(
{
depositVault: depositVaultWithMorpho,
diff --git a/test/common/deposit-vault-mtoken.helpers.ts b/test/common/deposit-vault-mtoken.helpers.ts
index 78667460..3c1d0fc3 100644
--- a/test/common/deposit-vault-mtoken.helpers.ts
+++ b/test/common/deposit-vault-mtoken.helpers.ts
@@ -6,6 +6,8 @@ import {
OptionalCommonParams,
balanceOfBase18,
getAccount,
+ handleRevert,
+ shouldRevert,
} from './common.helpers';
import {
depositInstantTest,
@@ -40,12 +42,15 @@ export const setMTokenDepositsEnabledTest = async (
enabled: boolean,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
+ if (
+ await handleRevert(
depositVaultWithMToken
.connect(opt?.from ?? owner)
- .setMTokenDepositsEnabled(enabled),
- ).revertedWith(opt?.revertMessage);
+ .setMTokenDepositsEnabled.bind(this, enabled),
+ depositVaultWithMToken,
+ opt,
+ )
+ ) {
return;
}
@@ -69,12 +74,15 @@ export const setMTokenDepositVaultTest = async (
newVault: string,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
+ if (
+ await handleRevert(
depositVaultWithMToken
.connect(opt?.from ?? owner)
- .setMTokenDepositVault(newVault),
- ).revertedWith(opt?.revertMessage);
+ .setMTokenDepositVault.bind(this, newVault),
+ depositVaultWithMToken,
+ opt,
+ )
+ ) {
return;
}
@@ -84,9 +92,8 @@ export const setMTokenDepositVaultTest = async (
.setMTokenDepositVault(newVault),
).to.emit(
depositVaultWithMToken,
- depositVaultWithMToken.interface.events[
- 'SetMTokenDepositVault(address,address)'
- ].name,
+ depositVaultWithMToken.interface.events['SetMTokenDepositVault(address)']
+ .name,
).to.not.reverted;
const vaultAfter = await depositVaultWithMToken.mTokenDepositVault();
@@ -115,7 +122,7 @@ export const depositInstantWithMTokenTest = async (
) => {
tokenIn = getAccount(tokenIn);
- if (opt?.revertMessage) {
+ if (shouldRevert(opt)) {
await depositInstantTest(
{
depositVault: depositVaultWithMToken,
@@ -125,7 +132,7 @@ export const depositInstantWithMTokenTest = async (
waivedFee,
minAmount,
customRecipient,
- checkTokensReceiver: !expectedMTokenDeposited,
+ checkTokensReceiver: true,
},
tokenIn,
amountUsdIn,
@@ -161,7 +168,7 @@ export const depositInstantWithMTokenTest = async (
waivedFee,
minAmount,
customRecipient,
- checkTokensReceiver: !expectedMTokenDeposited,
+ checkTokensReceiver: true,
},
tokenIn,
amountUsdIn,
@@ -189,12 +196,15 @@ export const setAutoInvestFallbackEnabledMTokenTest = async (
enabled: boolean,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
+ if (
+ await handleRevert(
depositVaultWithMToken
.connect(opt?.from ?? owner)
- .setAutoInvestFallbackEnabled(enabled),
- ).revertedWith(opt?.revertMessage);
+ .setAutoInvestFallbackEnabled.bind(this, enabled),
+ depositVaultWithMToken,
+ opt,
+ )
+ ) {
return;
}
@@ -234,7 +244,7 @@ export const depositRequestWithMTokenTest = async (
) => {
tokenIn = getAccount(tokenIn);
- if (opt?.revertMessage) {
+ if (shouldRevert(opt)) {
await depositRequestTest(
{
depositVault: depositVaultWithMToken,
@@ -243,7 +253,8 @@ export const depositRequestWithMTokenTest = async (
mTokenToUsdDataFeed,
waivedFee,
customRecipient,
- checkTokensReceiver: !expectedMTokenDeposited,
+ checkTokensReceiver: true,
+ checkMTokenSupplyUnchanged: !expectedMTokenDeposited,
},
tokenIn,
amountUsdIn,
@@ -277,7 +288,8 @@ export const depositRequestWithMTokenTest = async (
mTokenToUsdDataFeed,
waivedFee,
customRecipient,
- checkTokensReceiver: !expectedMTokenDeposited,
+ checkTokensReceiver: true,
+ checkMTokenSupplyUnchanged: !expectedMTokenDeposited,
},
tokenIn,
amountUsdIn,
diff --git a/test/common/deposit-vault-ustb.helpers.ts b/test/common/deposit-vault-ustb.helpers.ts
index e3916300..7f18a756 100644
--- a/test/common/deposit-vault-ustb.helpers.ts
+++ b/test/common/deposit-vault-ustb.helpers.ts
@@ -5,6 +5,8 @@ import {
AccountOrContract,
OptionalCommonParams,
getAccount,
+ handleRevert,
+ shouldRevert,
} from './common.helpers';
import { depositInstantTest } from './deposit-vault.helpers';
import { defaultDeploy } from './fixtures';
@@ -57,12 +59,15 @@ export const setUstbDepositsEnabledTest = async (
enabled: boolean,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
+ if (
+ await handleRevert(
depositVaultWithUSTB
.connect(opt?.from ?? owner)
- .setUstbDepositsEnabled(enabled),
- ).revertedWith(opt?.revertMessage);
+ .setUstbDepositsEnabled.bind(this, enabled),
+ depositVaultWithUSTB,
+ opt,
+ )
+ ) {
return;
}
@@ -104,7 +109,7 @@ export const depositInstantWithUstbTest = async (
) => {
tokenIn = getAccount(tokenIn);
- if (opt?.revertMessage) {
+ if (shouldRevert(opt)) {
await depositInstantTest(
{
depositVault: depositVaultWithUSTB,
diff --git a/test/common/deposit-vault.helpers.ts b/test/common/deposit-vault.helpers.ts
index 26ea8a44..96dd771e 100644
--- a/test/common/deposit-vault.helpers.ts
+++ b/test/common/deposit-vault.helpers.ts
@@ -1,15 +1,24 @@
+import { setNextBlockTimestamp } from '@nomicfoundation/hardhat-network-helpers/dist/src/helpers/time';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { expect } from 'chai';
-import { BigNumber, BigNumberish, constants } from 'ethers';
-import { parseUnits } from 'ethers/lib/utils';
+import {
+ BigNumber,
+ BigNumberish,
+ constants,
+ ContractTransaction,
+} from 'ethers';
+import { formatUnits, parseUnits } from 'ethers/lib/utils';
import {
AccountOrContract,
OptionalCommonParams,
balanceOfBase18,
getAccount,
+ getCurrentBlockTimestamp,
+ handleRevert,
} from './common.helpers';
import { defaultDeploy } from './fixtures';
+import { calculateWindowRateLimitCapacity } from './manageable-vault.helpers';
import {
DataFeedTest__factory,
@@ -24,37 +33,59 @@ import {
MToken,
} from '../../typechain-types';
+type DepositVaultType =
+ | DepositVault
+ | DepositVaultTest
+ | DepositVaultWithAaveTest
+ | DepositVaultWithMorphoTest
+ | DepositVaultWithMTokenTest
+ | DepositVaultWithUSTBTest;
type CommonParamsDeposit = {
- depositVault:
- | DepositVault
- | DepositVaultTest
- | DepositVaultWithAaveTest
- | DepositVaultWithMorphoTest
- | DepositVaultWithMTokenTest
- | DepositVaultWithUSTBTest;
+ depositVault: DepositVaultType;
mTBILL: MToken;
} & Pick<
Awaited>,
'owner' | 'mTokenToUsdDataFeed'
>;
+const getTotalFromInstantShare = (
+ amountIn: BigNumber,
+ instantShare?: BigNumberish,
+) => {
+ if (instantShare === undefined) {
+ return amountIn;
+ }
+
+ if (BigNumber.from(instantShare).eq(constants.Zero)) {
+ return BigNumber.from(0);
+ }
+
+ return amountIn.mul(100_00).div(instantShare);
+};
+
export const depositInstantTest = async (
{
depositVault,
owner,
mTBILL,
mTokenToUsdDataFeed,
- waivedFee,
minAmount,
customRecipient,
checkTokensReceiver = true,
expectedMintAmount,
+ referrerId,
+ holdback,
}: CommonParamsDeposit & {
waivedFee?: boolean;
minAmount?: BigNumberish;
customRecipient?: AccountOrContract;
checkTokensReceiver?: boolean;
expectedMintAmount?: BigNumberish;
+ referrerId?: string;
+ holdback?: {
+ callFunction: () => Promise;
+ instantShare: BigNumberish;
+ };
},
tokenIn: ERC20 | string,
amountUsdIn: number,
@@ -67,7 +98,6 @@ export const depositInstantTest = async (
const tokenContract = ERC20__factory.connect(tokenIn, owner);
const tokensReceiver = await depositVault.tokensReceiver();
- const feeReceiver = await depositVault.feeReceiver();
const amountIn = parseUnits(amountUsdIn.toFixed(18).replace(/\.?0+$/, ''));
@@ -76,40 +106,40 @@ export const depositInstantTest = async (
? getAccount(customRecipient)
: sender.address;
- const callFn = withRecipient
- ? depositVault
- .connect(sender)
- ['depositInstant(address,uint256,uint256,bytes32,address)'].bind(
- this,
- tokenIn,
- amountIn,
- minAmount ?? constants.Zero,
- constants.HashZero,
- recipient,
- )
- : depositVault
- .connect(sender)
- ['depositInstant(address,uint256,uint256,bytes32)'].bind(
- this,
- tokenIn,
- amountIn,
- minAmount ?? constants.Zero,
- constants.HashZero,
- );
-
- if (opt?.revertMessage) {
- await expect(callFn()).revertedWith(opt?.revertMessage);
+ referrerId = referrerId ?? constants.HashZero;
+
+ const callFn =
+ holdback?.callFunction ??
+ (withRecipient
+ ? depositVault
+ .connect(sender)
+ ['depositInstant(address,uint256,uint256,bytes32,address)'].bind(
+ this,
+ tokenIn,
+ amountIn,
+ minAmount ?? constants.Zero,
+ referrerId,
+ recipient,
+ )
+ : depositVault
+ .connect(sender)
+ ['depositInstant(address,uint256,uint256,bytes32)'].bind(
+ this,
+ tokenIn,
+ amountIn,
+ minAmount ?? constants.Zero,
+ referrerId,
+ ));
+
+ if (await handleRevert(callFn, depositVault, opt)) {
return;
}
- const balanceBeforeContract = await balanceOfBase18(
+ const balanceBeforeReceiver = await balanceOfBase18(
tokenContract,
tokensReceiver,
);
- const feeReceiverBalanceBeforeContract = await balanceOfBase18(
- tokenContract,
- feeReceiver,
- );
+
const balanceBeforeUser = await balanceOfBase18(
tokenContract,
sender.address,
@@ -118,62 +148,82 @@ export const depositInstantTest = async (
const totalMintedBefore = await depositVault.totalMinted(sender.address);
const totalMintedBeforeRecipient = await depositVault.totalMinted(recipient);
- const maxSupplyCapBefore = await depositVault.maxSupplyCap();
+ const maxSupplyCapBefore = await mTBILL.maxSupplyCap();
const mTokenRate = await mTokenToUsdDataFeed.getDataInBase18();
- const { fee, mintAmount, amountInWithoutFee, actualAmountInUsd } =
- await calcExpectedMintAmount(
- sender,
- tokenIn,
- depositVault,
- mTokenRate,
- amountIn,
- true,
- );
+ const { fee, mintAmount, tokenInRate } = await calcExpectedMintAmount(
+ sender,
+ tokenIn,
+ depositVault,
+ mTokenRate,
+ amountIn,
+ true,
+ );
- await expect(callFn())
+ const instantLimitsBefore = await depositVault.getInstantLimitStatuses();
+ const timestampBefore = await getCurrentBlockTimestamp();
+
+ const callPromise = callFn();
+
+ await expect(callPromise)
.to.emit(
depositVault,
depositVault.interface.events[
- withRecipient
- ? 'DepositInstantWithCustomRecipient(address,address,address,uint256,uint256,uint256,uint256,bytes32)'
- : 'DepositInstant(address,address,uint256,uint256,uint256,uint256,bytes32)'
+ 'DepositInstant(address,address,address,uint256,uint256,uint256,uint256,uint256,bytes32)'
].name,
)
.withArgs(
- ...[
- sender.address,
- tokenContract.address,
- withRecipient ? recipient : undefined,
- actualAmountInUsd,
- amountUsdIn,
- fee,
- 0,
- constants.HashZero,
- ].filter((v) => v !== undefined),
+ sender.address,
+ tokenContract.address,
+ recipient,
+ fee,
+ mintAmount,
+ mTokenRate,
+ tokenInRate,
+ referrerId,
).to.not.reverted;
+ const instantLimitsAfter = await depositVault.getInstantLimitStatuses();
+ const timestampAfter = await getCurrentBlockTimestamp();
+
+ const expectedMinted = expectedMintAmount ?? mintAmount;
+
+ const expectedLimitsAfter = await Promise.all(
+ instantLimitsBefore.map(async (limit) => {
+ const { remaining, inFlight } = calculateWindowRateLimitCapacity({
+ amountInFlight: limit.inFlight,
+ lastUpdated: timestampBefore,
+ limit: limit.limit,
+ window: limit.window,
+ now: timestampAfter,
+ });
+
+ return {
+ ...limit,
+ remaining: remaining.gte(expectedMinted)
+ ? remaining.sub(expectedMinted)
+ : constants.Zero,
+ inFlight: inFlight.add(expectedMinted),
+ };
+ }),
+ );
+
const totalMintedAfter = await depositVault.totalMinted(sender.address);
const totalMintedAfterRecipient = await depositVault.totalMinted(recipient);
- const balanceAfterContract = await balanceOfBase18(
+ const balanceAfterReceiver = await balanceOfBase18(
tokenContract,
tokensReceiver,
);
- const feeReceiverBalanceAfterContract = await balanceOfBase18(
- tokenContract,
- feeReceiver,
- );
+
const balanceAfterUser = await balanceOfBase18(tokenContract, sender.address);
const balanceMtBillAfterUser = await balanceOfBase18(mTBILL, recipient);
- const maxSupplyCapAfter = await depositVault.maxSupplyCap();
+ const maxSupplyCapAfter = await mTBILL.maxSupplyCap();
- const expectedMinted = expectedMintAmount ?? mintAmount;
-
- expect(balanceMtBillAfterUser.sub(balanceMtBillBeforeUser)).eq(
- expectedMinted,
+ expect(balanceMtBillAfterUser).eq(
+ balanceMtBillBeforeUser.add(expectedMinted),
);
expect(totalMintedAfter).eq(totalMintedBefore.add(expectedMinted));
if (recipient !== sender.address) {
@@ -181,21 +231,28 @@ export const depositInstantTest = async (
}
if (checkTokensReceiver) {
- expect(balanceAfterContract).eq(
- balanceBeforeContract.add(amountInWithoutFee),
- );
+ expect(balanceAfterReceiver).eq(balanceBeforeReceiver.add(amountIn));
}
- expect(feeReceiverBalanceAfterContract).eq(
- feeReceiverBalanceBeforeContract.add(fee),
- );
- if (waivedFee) {
- expect(feeReceiverBalanceAfterContract).eq(
- feeReceiverBalanceBeforeContract,
- );
+ if (!holdback) {
+ expect(balanceAfterUser).eq(balanceBeforeUser.sub(amountIn));
}
- expect(balanceAfterUser).eq(balanceBeforeUser.sub(amountIn));
+
expect(maxSupplyCapAfter).eq(maxSupplyCapBefore);
+
+ for (const [index, limit] of instantLimitsBefore.entries()) {
+ expect(instantLimitsAfter[index].inFlight).eq(
+ expectedLimitsAfter[index].inFlight,
+ );
+ expect(instantLimitsAfter[index].remaining).eq(
+ expectedLimitsAfter[index].remaining,
+ );
+ expect(instantLimitsAfter[index].lastUpdated).eq(timestampAfter);
+ expect(instantLimitsAfter[index].window).eq(limit.window);
+ expect(instantLimitsAfter[index].limit).eq(limit.limit);
+ }
+
+ return callPromise;
};
export const depositRequestTest = async (
@@ -206,10 +263,21 @@ export const depositRequestTest = async (
waivedFee,
customRecipient,
checkTokensReceiver = true,
+ checkMTokenSupplyUnchanged = true,
+ customRecipientInstant,
+ instantShare,
+ minReceiveAmountInstantShare,
+ mTBILL,
+ referrerId,
}: CommonParamsDeposit & {
+ referrerId?: string;
waivedFee?: boolean;
customRecipient?: AccountOrContract;
checkTokensReceiver?: boolean;
+ checkMTokenSupplyUnchanged?: boolean;
+ customRecipientInstant?: AccountOrContract;
+ instantShare?: BigNumberish;
+ minReceiveAmountInstantShare?: BigNumberish;
},
tokenIn: ERC20 | string,
amountUsdIn: number,
@@ -222,7 +290,6 @@ export const depositRequestTest = async (
const tokenContract = ERC20__factory.connect(tokenIn, owner);
const tokensReceiver = await depositVault.tokensReceiver();
- const feeReceiver = await depositVault.feeReceiver();
const amountIn = parseUnits(amountUsdIn.toFixed(18).replace(/\.?0+$/, ''));
@@ -231,27 +298,39 @@ export const depositRequestTest = async (
? getAccount(customRecipient)
: sender.address;
- const callFn = withRecipient
- ? depositVault
- .connect(sender)
- ['depositRequest(address,uint256,bytes32,address)'].bind(
- this,
- tokenIn,
- amountIn,
- constants.HashZero,
- recipient,
- )
- : depositVault
- .connect(sender)
- ['depositRequest(address,uint256,bytes32)'].bind(
- this,
- tokenIn,
- amountIn,
- constants.HashZero,
- );
+ const recipientInstant =
+ customRecipientInstant || customRecipient
+ ? getAccount(customRecipientInstant ?? customRecipient!)
+ : sender.address;
+
+ referrerId = referrerId ?? constants.HashZero;
- if (opt?.revertMessage) {
- await expect(callFn()).revertedWith(opt?.revertMessage);
+ const callFn =
+ instantShare || withRecipient
+ ? depositVault
+ .connect(sender)
+ [
+ 'depositRequest(address,uint256,bytes32,address,uint256,uint256,address)'
+ ].bind(
+ this,
+ tokenIn,
+ amountIn,
+ referrerId,
+ recipient,
+ instantShare ?? constants.Zero,
+ minReceiveAmountInstantShare ?? constants.Zero,
+ recipientInstant,
+ )
+ : depositVault
+ .connect(sender)
+ ['depositRequest(address,uint256,bytes32)'].bind(
+ this,
+ tokenIn,
+ amountIn,
+ referrerId,
+ );
+
+ if (await handleRevert(callFn, depositVault, opt)) {
return {};
}
@@ -259,10 +338,7 @@ export const depositRequestTest = async (
tokenContract,
tokensReceiver,
);
- const feeReceiverBalanceBeforeContract = await balanceOfBase18(
- tokenContract,
- feeReceiver,
- );
+
const balanceBeforeUser = await balanceOfBase18(
tokenContract,
sender.address,
@@ -271,78 +347,138 @@ export const depositRequestTest = async (
const latestRequestIdBefore = await depositVault.currentRequestId();
const mTokenRate = await mTokenToUsdDataFeed.getDataInBase18();
- const maxSupplyCapBefore = await depositVault.maxSupplyCap();
+ const maxSupplyCapBefore = await mTBILL.maxSupplyCap();
+ const supplyBefore = await mTBILL.totalSupply();
+ const upcomingSupplyBefore = await depositVault.upcomingSupply();
+
+ const amountTokenInInstant = amountIn
+ .mul(instantShare ?? constants.Zero)
+ .div(100_00);
+ const amountTokenInRequest = amountIn.sub(amountTokenInInstant);
+
+ const calcMintAmountRequest = await calcExpectedMintAmount(
+ sender,
+ tokenIn,
+ depositVault,
+ mTokenRate,
+ amountTokenInRequest,
+ false,
+ );
+
+ const calcMintAmountInstant = await calcExpectedMintAmount(
+ sender,
+ tokenIn,
+ depositVault,
+ mTokenRate,
+ amountTokenInInstant,
+ true,
+ );
+
+ const nextExpectedRequestIdToProcessBefore =
+ await depositVault.nextExpectedRequestIdToProcess();
+
+ let callPromise: Awaited>;
- const { fee, mintAmount, amountInWithoutFee, actualAmountInUsd } =
- await calcExpectedMintAmount(
- sender,
+ if (amountTokenInInstant.gt(0)) {
+ callPromise = await depositInstantTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ waivedFee,
+ minAmount: minReceiveAmountInstantShare ?? constants.Zero,
+ customRecipient: recipientInstant,
+ checkTokensReceiver: false,
+ holdback: {
+ callFunction: callFn,
+ instantShare: instantShare ?? constants.Zero,
+ },
+ },
tokenIn,
- depositVault,
- mTokenRate,
- amountIn,
- false,
+ +formatUnits(amountTokenInInstant, 18),
+ { from: sender },
);
+ }
- await expect(callFn())
+ await expect(callPromise ?? callFn())
.to.emit(
depositVault,
depositVault.interface.events[
- withRecipient
- ? 'DepositRequestWithCustomRecipient(uint256,address,address,address,uint256,uint256,uint256,uint256,bytes32)'
- : 'DepositRequest(uint256,address,address,uint256,uint256,uint256,uint256,bytes32)'
+ 'DepositRequest(uint256,address,address,address,uint256,uint256,uint256,uint256,uint256,bytes32)'
].name,
)
.withArgs(
- ...[
- latestRequestIdBefore.add(1),
- sender.address,
- tokenContract.address,
- withRecipient ? recipient : undefined,
- amountIn,
- actualAmountInUsd,
- fee,
- mintAmount,
- constants.HashZero,
- ].filter((v) => v !== undefined),
+ latestRequestIdBefore,
+ sender.address,
+ tokenContract.address,
+ recipient,
+ amountTokenInRequest,
+ amountTokenInInstant,
+ calcMintAmountRequest.fee,
+ mTokenRate,
+ calcMintAmountRequest.tokenInRate,
+ referrerId,
).to.not.reverted;
+ const nextExpectedRequestIdToProcessAfter =
+ await depositVault.nextExpectedRequestIdToProcess();
+
+ expect(nextExpectedRequestIdToProcessAfter).eq(
+ nextExpectedRequestIdToProcessBefore,
+ );
+
const latestRequestIdAfter = await depositVault.currentRequestId();
+ const supplyAfter = await mTBILL.totalSupply();
const balanceAfterContract = await balanceOfBase18(
tokenContract,
tokensReceiver,
);
- const feeReceiverBalanceAfterContract = await balanceOfBase18(
- tokenContract,
- feeReceiver,
- );
+
const balanceAfterUser = await balanceOfBase18(tokenContract, sender.address);
const request = await depositVault.mintRequests(latestRequestIdBefore);
- const maxSupplyCapAfter = await depositVault.maxSupplyCap();
+ const maxSupplyCapAfter = await mTBILL.maxSupplyCap();
+ const upcomingSupplyAfter = await depositVault.upcomingSupply();
- expect(request.depositedUsdAmount).eq(actualAmountInUsd);
+ expect(request.depositedUsdAmount).eq(
+ calcMintAmountRequest.actualAmountInUsd,
+ );
expect(request.tokenOutRate).eq(mTokenRate);
- expect(request.sender).eq(recipient);
+ expect(request.recipient).eq(recipient);
expect(request.status).eq(0);
expect(request.tokenIn).eq(tokenContract.address);
expect(latestRequestIdAfter).eq(latestRequestIdBefore.add(1));
+
if (checkTokensReceiver) {
expect(balanceAfterContract).eq(
- balanceBeforeContract.add(amountInWithoutFee),
+ balanceBeforeContract.add(amountTokenInRequest.add(amountTokenInInstant)),
);
- }
- expect(feeReceiverBalanceAfterContract).eq(
- feeReceiverBalanceBeforeContract.add(fee),
- );
- if (waivedFee) {
- expect(feeReceiverBalanceAfterContract).eq(
- feeReceiverBalanceBeforeContract,
+ } else {
+ expect(balanceAfterContract).eq(
+ balanceBeforeContract.add(
+ calcMintAmountRequest.fee.add(calcMintAmountInstant.fee),
+ ),
);
}
+
expect(balanceAfterUser).eq(balanceBeforeUser.sub(amountIn));
expect(maxSupplyCapAfter).eq(maxSupplyCapBefore);
+ const estimatedMintAmountRequest = calcMintAmountRequest.usdForMintConvertion
+ .mul(constants.WeiPerEther)
+ .div(request.tokenOutRate);
+
+ expect(upcomingSupplyAfter).eq(
+ upcomingSupplyBefore.add(estimatedMintAmountRequest),
+ );
+
+ // those checks is already made in redeemInstantTest
+ if (checkMTokenSupplyUnchanged && !amountTokenInInstant.gt(0)) {
+ expect(supplyAfter).eq(supplyBefore);
+ }
+
return {
requestId: latestRequestIdBefore,
rate: mTokenRate,
@@ -350,135 +486,193 @@ export const depositRequestTest = async (
};
export const approveRequestTest = async (
- { depositVault, owner, mTBILL }: CommonParamsDeposit,
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ isAvgRate = false,
+ }: CommonParamsDeposit & {
+ isAvgRate?: boolean;
+ },
requestId: BigNumberish,
newRate: BigNumberish,
opt?: OptionalCommonParams,
) => {
const sender = opt?.from ?? owner;
- if (opt?.revertMessage) {
- await expect(
- depositVault.connect(sender).approveRequest(requestId, newRate),
- ).revertedWith(opt?.revertMessage);
+ const callFn = depositVault
+ .connect(sender)
+ .approveRequest.bind(this, requestId, newRate, isAvgRate);
+
+ if (await handleRevert(callFn, depositVault, opt)) {
return;
}
- const balanceMtBillBeforeUser = await balanceOfBase18(mTBILL, sender.address);
-
- const totalDepositedBefore = await depositVault.totalMinted(sender.address);
const requestData = await depositVault.mintRequests(requestId);
- const feePercent = await getFeePercent(
- requestData.sender,
- requestData.tokenIn,
- depositVault,
- false,
- );
-
- const expectedMintAmount = requestData.depositedUsdAmount
- .sub(requestData.depositedUsdAmount.mul(feePercent).div(10000))
- .mul(constants.WeiPerEther)
- .div(newRate);
-
- await expect(depositVault.connect(sender).approveRequest(requestId, newRate))
- .to.emit(
- depositVault,
- depositVault.interface.events['ApproveRequest(uint256,uint256)'].name,
- )
- .withArgs(requestId, newRate).to.not.reverted;
-
- const requestDataAfter = await depositVault.mintRequests(requestId);
-
- const totalDepositedAfter = await depositVault.totalMinted(sender.address);
-
- const balanceMtBillAfterUser = await balanceOfBase18(mTBILL, sender.address);
-
- expect(balanceMtBillAfterUser.sub(balanceMtBillBeforeUser)).eq(
- expectedMintAmount,
- );
- expect(totalDepositedAfter).eq(totalDepositedBefore.add(expectedMintAmount));
- expect(requestDataAfter.sender).eq(requestData.sender);
- expect(requestDataAfter.tokenIn).eq(requestData.tokenIn);
- expect(requestDataAfter.tokenOutRate).eq(newRate);
- expect(requestDataAfter.depositedUsdAmount).eq(
- requestData.depositedUsdAmount,
- );
- expect(requestDataAfter.status).eq(1);
-};
-
-export const safeApproveRequestTest = async (
- { depositVault, owner, mTBILL }: CommonParamsDeposit,
- requestId: BigNumberish,
- newRate: BigNumberish,
- opt?: OptionalCommonParams,
-) => {
- const sender = opt?.from ?? owner;
+ let actualRate = !isAvgRate
+ ? BigNumber.from(newRate)
+ : BigNumber.from(
+ expectedDepositHoldbackPartRateFromAvg(
+ requestData.depositedUsdAmount,
+ requestData.depositedInstantUsdAmount,
+ requestData.tokenOutRate,
+ newRate,
+ ),
+ );
- if (opt?.revertMessage) {
- await expect(
- depositVault.connect(sender).safeApproveRequest(requestId, newRate),
- ).revertedWith(opt?.revertMessage);
- return;
+ if (actualRate.eq(0)) {
+ actualRate = BigNumber.from(newRate);
}
- const requestData = await depositVault.mintRequests(requestId);
const balanceMtBillBeforeUser = await balanceOfBase18(
mTBILL,
- requestData.sender,
+ requestData.recipient,
);
const totalDepositedBefore = await depositVault.totalMinted(
- requestData.sender,
+ requestData.recipient,
);
+ const balanceBeforeVaultMToken = await balanceOfBase18(
+ mTBILL,
+ depositVault.address,
+ );
+ const totalSupplyBefore = await mTBILL.totalSupply();
+
const feePercent = await getFeePercent(
- requestData.sender,
+ requestData.recipient,
requestData.tokenIn,
depositVault,
false,
);
- const expectedMintAmount = requestData.depositedUsdAmount
- .sub(requestData.depositedUsdAmount.mul(feePercent).div(10000))
+ const expectedMintAmount = requestData.usdAmountWithoutFees
.mul(constants.WeiPerEther)
- .div(newRate);
+ .div(BigNumber.from(0).eq(actualRate) ? parseUnits('1') : actualRate);
- await expect(
- depositVault.connect(sender).safeApproveRequest(requestId, newRate),
- )
+ const nextExpectedRequestIdToProcessBefore =
+ await depositVault.nextExpectedRequestIdToProcess();
+
+ const upcomingSupplyBefore = await depositVault.upcomingSupply();
+ await expect(callFn())
.to.emit(
depositVault,
- depositVault.interface.events['SafeApproveRequest(uint256,uint256)'].name,
+ depositVault.interface.events['ApproveRequest(uint256,uint256,bool,bool)']
+ .name,
)
- .withArgs(requestId, newRate).to.not.reverted;
+ .withArgs(requestId, actualRate, false, isAvgRate).to.not.reverted;
+
+ const nextExpectedRequestIdToProcessAfter =
+ await depositVault.nextExpectedRequestIdToProcess();
+ if (nextExpectedRequestIdToProcessBefore.lte(requestId)) {
+ expect(nextExpectedRequestIdToProcessAfter).eq(
+ BigNumber.from(requestId).add(1),
+ );
+ } else {
+ expect(nextExpectedRequestIdToProcessAfter).eq(
+ nextExpectedRequestIdToProcessBefore,
+ );
+ }
+
+ const upcomingSupplyAfter = await depositVault.upcomingSupply();
const requestDataAfter = await depositVault.mintRequests(requestId);
const totalDepositedAfter = await depositVault.totalMinted(
- requestData.sender,
+ requestData.recipient,
);
+ const totalSupplyAfter = await mTBILL.totalSupply();
+
+ const balanceAfterVaultMToken = await balanceOfBase18(
+ mTBILL,
+ depositVault.address,
+ );
+
+ const estimatedMintAmountRequest = requestData.usdAmountWithoutFees
+ .mul(constants.WeiPerEther)
+ .div(requestData.tokenOutRate);
+
const balanceMtBillAfterUser = await balanceOfBase18(
mTBILL,
- requestData.sender,
+ requestData.recipient,
);
+ expect(upcomingSupplyAfter).eq(
+ upcomingSupplyBefore.sub(estimatedMintAmountRequest),
+ );
+
+ expect(totalSupplyAfter).eq(totalSupplyBefore.add(expectedMintAmount));
+
expect(balanceMtBillAfterUser.sub(balanceMtBillBeforeUser)).eq(
expectedMintAmount,
);
+ expect(balanceAfterVaultMToken).eq(balanceBeforeVaultMToken);
+ expect(requestDataAfter.status).eq(1);
expect(totalDepositedAfter).eq(totalDepositedBefore.add(expectedMintAmount));
- expect(requestDataAfter.sender).eq(requestData.sender);
+
+ expect(requestDataAfter.amountMToken).eq(expectedMintAmount);
+ expect(requestDataAfter.recipient).eq(requestData.recipient);
expect(requestDataAfter.tokenIn).eq(requestData.tokenIn);
- expect(requestDataAfter.tokenOutRate).eq(newRate);
+ expect(requestDataAfter.tokenOutRate).eq(requestData.tokenOutRate);
+ expect(requestDataAfter.approvedTokenOutRate).eq(actualRate);
expect(requestDataAfter.depositedUsdAmount).eq(
requestData.depositedUsdAmount,
);
- expect(requestDataAfter.status).eq(1);
+ expect(requestDataAfter.depositedInstantUsdAmount).eq(
+ requestData.depositedInstantUsdAmount,
+ );
+};
+
+export const expectedDepositHoldbackPartRateFromAvg = (
+ depositedUsdAmount: BigNumberish,
+ depositedInstantUsdAmount: BigNumberish,
+ tokenOutRate: BigNumberish,
+ avgMTokenRate: BigNumberish,
+): bigint => {
+ depositedUsdAmount = BigInt(depositedUsdAmount.toString());
+ depositedInstantUsdAmount = BigInt(depositedInstantUsdAmount.toString());
+ tokenOutRate = BigInt(tokenOutRate.toString());
+ avgMTokenRate = BigInt(avgMTokenRate.toString());
+
+ if (
+ avgMTokenRate === 0n ||
+ tokenOutRate === 0n ||
+ depositedInstantUsdAmount === 0n
+ ) {
+ return 0n;
+ }
+
+ const targetTotalMTokenValue =
+ ((depositedUsdAmount + depositedInstantUsdAmount) * 10n ** 18n) /
+ avgMTokenRate;
+ const instantPartMTokenValue =
+ (depositedInstantUsdAmount * 10n ** 18n) / tokenOutRate;
+
+ if (targetTotalMTokenValue <= instantPartMTokenValue) {
+ return 0n;
+ }
+
+ const holdbackPartValue = targetTotalMTokenValue - instantPartMTokenValue;
+
+ if (holdbackPartValue === 0n) {
+ return 0n;
+ }
+
+ return (depositedUsdAmount * 10n ** 18n) / holdbackPartValue;
};
export const safeBulkApproveRequestTest = async (
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed }: CommonParamsDeposit,
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate = false,
+ }: CommonParamsDeposit & {
+ isAvgRate?: boolean;
+ },
requests: { id: BigNumberish; expectedToExecute?: boolean }[],
newRate?: BigNumberish | 'request-rate',
opt?: OptionalCommonParams,
@@ -487,11 +681,11 @@ export const safeBulkApproveRequestTest = async (
const requestIds = requests.map(({ id }) => id);
- const callFn =
- newRate && newRate !== 'request-rate'
+ const callFn = isAvgRate
+ ? newRate && newRate !== 'request-rate'
? depositVault
.connect(sender)
- ['safeBulkApproveRequest(uint256[],uint256)'].bind(
+ ['safeBulkApproveRequestAvgRate(uint256[],uint256)'].bind(
this,
requestIds,
newRate,
@@ -502,29 +696,49 @@ export const safeBulkApproveRequestTest = async (
.safeBulkApproveRequestAtSavedRate.bind(this, requestIds)
: depositVault
.connect(sender)
- ['safeBulkApproveRequest(uint256[])'].bind(this, requestIds);
+ ['safeBulkApproveRequestAvgRate(uint256[])'].bind(this, requestIds)
+ : newRate && newRate !== 'request-rate'
+ ? depositVault
+ .connect(sender)
+ ['safeBulkApproveRequest(uint256[],uint256)'].bind(
+ this,
+ requestIds,
+ newRate,
+ )
+ : newRate === 'request-rate'
+ ? depositVault
+ .connect(sender)
+ .safeBulkApproveRequestAtSavedRate.bind(this, requestIds)
+ : depositVault
+ .connect(sender)
+ ['safeBulkApproveRequest(uint256[])'].bind(this, requestIds);
- if (opt?.revertMessage) {
- await expect(callFn()).revertedWith(opt?.revertMessage);
+ if (await handleRevert(callFn, depositVault, opt)) {
return;
}
+ await setNextBlockTimestamp((await getCurrentBlockTimestamp()) + 1);
+
const requestDatasBefore = await Promise.all(
requestIds.map((requestId) => depositVault.mintRequests(requestId)),
);
const balancesBefore = await Promise.all(
- requestDatasBefore.map(({ sender }) => balanceOfBase18(mTBILL, sender)),
+ requestDatasBefore.map(({ recipient }) =>
+ balanceOfBase18(mTBILL, recipient),
+ ),
);
const totalDepositedsBefore = await Promise.all(
- requestDatasBefore.map(({ sender }) => depositVault.totalMinted(sender)),
+ requestDatasBefore.map(({ recipient }) =>
+ depositVault.totalMinted(recipient),
+ ),
);
const feePercents = await Promise.all(
requestDatasBefore.map((requestData) =>
getFeePercent(
- requestData.sender,
+ requestData.recipient,
requestData.tokenIn,
depositVault,
false,
@@ -533,20 +747,75 @@ export const safeBulkApproveRequestTest = async (
);
const totalSupplyBefore = await mTBILL.totalSupply();
- const supplyCap = await depositVault.maxSupplyCap();
+ const supplyCap = await mTBILL.maxSupplyCap();
+
+ const upcomingSupplyBefore = await depositVault.upcomingSupply();
+
+ const nextExpectedRequestIdToProcessBefore =
+ await depositVault.nextExpectedRequestIdToProcess();
const txPromise = callFn();
await expect(txPromise).to.not.reverted;
+ const nextExpectedRequestIdToProcessAfter =
+ await depositVault.nextExpectedRequestIdToProcess();
+
+ const expectedToExecuteRequests = requests.filter(
+ (v) => v.expectedToExecute ?? true,
+ );
+
+ const expectedHighestProcessedRequestId = expectedToExecuteRequests.sort(
+ (a, b) => +b.id.toString() - +a.id.toString(),
+ )[0]?.id;
+
+ if (
+ expectedHighestProcessedRequestId !== undefined &&
+ nextExpectedRequestIdToProcessBefore.lte(expectedHighestProcessedRequestId)
+ ) {
+ expect(nextExpectedRequestIdToProcessAfter).eq(
+ BigNumber.from(expectedHighestProcessedRequestId).add(1),
+ );
+ } else {
+ expect(nextExpectedRequestIdToProcessAfter).eq(
+ nextExpectedRequestIdToProcessBefore,
+ );
+ }
+
+ const upcomingSupplyAfter = await depositVault.upcomingSupply();
+
const currentRate = await mTokenToUsdDataFeed.getDataInBase18();
- const newExpectedRate =
- newRate === 'request-rate' ? undefined : newRate ?? currentRate;
+
+ const newExpectedRate = (
+ requestData: (typeof requestDatasBefore)[number],
+ ) => {
+ let rate =
+ newRate === 'request-rate'
+ ? requestData.tokenOutRate
+ : newRate ?? currentRate;
+
+ if (isAvgRate) {
+ const holdbackRate = expectedDepositHoldbackPartRateFromAvg(
+ requestData.depositedUsdAmount,
+ requestData.depositedInstantUsdAmount,
+ requestData.tokenOutRate,
+ rate,
+ );
+ rate = holdbackRate === 0n ? rate : holdbackRate;
+ }
+ return BigNumber.from(rate);
+ };
const expectedMintAmounts = requestDatasBefore.map((requestData, i) =>
requestData.depositedUsdAmount
.sub(requestData.depositedUsdAmount.mul(feePercents[i]).div(10000))
.mul(constants.WeiPerEther)
- .div(newExpectedRate ?? requestData.tokenOutRate),
+ .div(newExpectedRate(requestData)),
+ );
+
+ const estimatedMintAmounts = requestDatasBefore.map((requestData, i) =>
+ requestData.usdAmountWithoutFees
+ .mul(constants.WeiPerEther)
+ .div(requestData.tokenOutRate),
);
const groupedDataBefore = requests.map(({ id, expectedToExecute }, index) => {
@@ -558,6 +827,7 @@ export const safeBulkApproveRequestTest = async (
expectedMintAmount: expectedMintAmounts[index],
balance: balancesBefore[index],
totalDeposited: totalDepositedsBefore[index],
+ estimatedMintAmount: estimatedMintAmounts[index],
};
});
@@ -566,7 +836,7 @@ export const safeBulkApproveRequestTest = async (
const parsedLogs = txReceipt.logs
.filter((v) => v.address === depositVault.address)
.map((log) => depositVault.interface.parseLog(log))
- .filter((v) => v.name === 'SafeApproveRequest')
+ .filter((v) => v.name === 'ApproveRequest')
.map((v) => v.args);
const requestDatasAfter = await Promise.all(
@@ -574,11 +844,15 @@ export const safeBulkApproveRequestTest = async (
);
const balancesAfter = await Promise.all(
- requestDatasAfter.map(({ sender }) => balanceOfBase18(mTBILL, sender)),
+ requestDatasAfter.map(({ recipient }) =>
+ balanceOfBase18(mTBILL, recipient),
+ ),
);
const totalDepositedsAfter = await Promise.all(
- requestDatasAfter.map(({ sender }) => depositVault.totalMinted(sender)),
+ requestDatasAfter.map(({ recipient }) =>
+ depositVault.totalMinted(recipient),
+ ),
);
const groupedDataAfter = requests.map(({ id }, index) => {
@@ -605,48 +879,70 @@ export const safeBulkApproveRequestTest = async (
const totalDepositedAfter = dataAfter.totalDeposited;
const totalDepositedBefore = dataBefore.totalDeposited;
- expect(requestDataAfter.sender).eq(requestDataBefore.sender);
+ expect(requestDataAfter.depositedInstantUsdAmount).eq(
+ requestDataBefore.depositedInstantUsdAmount,
+ );
+ expect(requestDataAfter.recipient).eq(requestDataBefore.recipient);
expect(requestDataAfter.tokenIn).eq(requestDataBefore.tokenIn);
expect(requestDataAfter.depositedUsdAmount).eq(
requestDataBefore.depositedUsdAmount,
);
+ expect(requestDataAfter.tokenOutRate).eq(requestDataBefore.tokenOutRate);
const logs = parsedLogs.filter((log) => log.requestId.eq(id));
const expectedMintedAggregatedByUser = groupedDataBefore
.filter(
(v) =>
- v.request.sender === requestDataBefore.sender && v.expectedToExecute,
+ v.request.recipient === requestDataBefore.recipient &&
+ v.expectedToExecute,
)
+ .reduce(
+ (prev, curr) => {
+ return {
+ mTokenAmount: prev.mTokenAmount.add(curr.expectedMintAmount),
+ mintAmount: prev.mintAmount.add(curr.expectedMintAmount),
+ };
+ },
+ { mTokenAmount: BigNumber.from(0), mintAmount: BigNumber.from(0) },
+ );
+
+ const upcomingSupplyExpectedDecrease = groupedDataBefore
+ .filter((v) => v.expectedToExecute)
.reduce((prev, curr) => {
- return prev.add(curr.expectedMintAmount);
+ return prev.add(curr.estimatedMintAmount);
}, BigNumber.from(0));
if (!expectedToExecute) {
expect(logs.length).eq(0);
- expect(requestDataAfter.tokenOutRate).eq(requestDataBefore.tokenOutRate);
expect(requestDataAfter.status).eq(0);
+ expect(requestDataAfter.approvedTokenOutRate).eq(0);
} else {
expect(logs.length).eq(1);
- expect(requestDataAfter.tokenOutRate).eq(
- newExpectedRate ?? requestDataBefore.tokenOutRate,
- );
+
expect(requestDataAfter.status).eq(1);
+
expect(totalDepositedAfter).eq(
- totalDepositedBefore.add(expectedMintedAggregatedByUser),
+ totalDepositedBefore.add(expectedMintedAggregatedByUser.mTokenAmount),
+ );
+ expect(requestDataAfter.approvedTokenOutRate).eq(
+ newExpectedRate(requestDataBefore),
);
const log = logs[0];
- expect(log.newOutRate).eq(
- newExpectedRate ?? requestDataBefore.tokenOutRate,
- );
+ expect(log.newOutRate).eq(newExpectedRate(requestDataBefore));
expect(log.requestId).eq(id);
}
expect(totalDepositedAfter).eq(
- totalDepositedBefore.add(expectedMintedAggregatedByUser),
+ totalDepositedBefore.add(expectedMintedAggregatedByUser.mTokenAmount),
);
- expect(balanceAfter).eq(balanceBefore.add(expectedMintedAggregatedByUser));
+ expect(upcomingSupplyAfter).eq(
+ upcomingSupplyBefore.sub(upcomingSupplyExpectedDecrease),
+ );
+ expect(balanceAfter).eq(
+ balanceBefore.add(expectedMintedAggregatedByUser.mintAmount),
+ );
}
};
@@ -657,10 +953,13 @@ export const rejectRequestTest = async (
) => {
const sender = opt?.from ?? owner;
- if (opt?.revertMessage) {
- await expect(
- depositVault.connect(sender).rejectRequest(requestId),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ depositVault.connect(sender).rejectRequest.bind(this, requestId),
+ depositVault,
+ opt,
+ )
+ ) {
return;
}
const balanceMtBillBeforeUser = await balanceOfBase18(mTBILL, sender.address);
@@ -668,13 +967,31 @@ export const rejectRequestTest = async (
const totalDepositedBefore = await depositVault.totalMinted(sender.address);
const requestData = await depositVault.mintRequests(requestId);
+ const nextExpectedRequestIdToProcessBefore =
+ await depositVault.nextExpectedRequestIdToProcess();
+ const upcomingSupplyBefore = await depositVault.upcomingSupply();
await expect(depositVault.connect(sender).rejectRequest(requestId))
.to.emit(
depositVault,
- depositVault.interface.events['RejectRequest(uint256,address)'].name,
+ depositVault.interface.events['RejectRequest(uint256)'].name,
)
- .withArgs(requestId, requestData.sender).to.not.reverted;
+ .withArgs(requestId).to.not.reverted;
+
+ const nextExpectedRequestIdToProcessAfter =
+ await depositVault.nextExpectedRequestIdToProcess();
+
+ if (nextExpectedRequestIdToProcessBefore.lte(requestId)) {
+ expect(nextExpectedRequestIdToProcessAfter).eq(
+ BigNumber.from(requestId).add(1),
+ );
+ } else {
+ expect(nextExpectedRequestIdToProcessAfter).eq(
+ nextExpectedRequestIdToProcessBefore,
+ );
+ }
+
+ const upcomingSupplyAfter = await depositVault.upcomingSupply();
const requestDataAfter = await depositVault.mintRequests(requestId);
@@ -682,9 +999,17 @@ export const rejectRequestTest = async (
const balanceMtBillAfterUser = await balanceOfBase18(mTBILL, sender.address);
+ const estimatedMintAmountRequest = requestData.usdAmountWithoutFees
+ .mul(constants.WeiPerEther)
+ .div(requestData.tokenOutRate);
+
+ expect(upcomingSupplyAfter).eq(
+ upcomingSupplyBefore.sub(estimatedMintAmountRequest),
+ );
+
expect(balanceMtBillAfterUser).eq(balanceMtBillBeforeUser);
expect(totalDepositedAfter).eq(totalDepositedBefore);
- expect(requestDataAfter.sender).eq(requestData.sender);
+ expect(requestDataAfter.recipient).eq(requestData.recipient);
expect(requestDataAfter.tokenIn).eq(requestData.tokenIn);
expect(requestDataAfter.tokenOutRate).eq(requestData.tokenOutRate);
expect(requestDataAfter.depositedUsdAmount).eq(
@@ -693,7 +1018,7 @@ export const rejectRequestTest = async (
expect(requestDataAfter.status).eq(2);
};
-export const setMaxSupplyCapTest = async (
+export const setMaxAmountPerRequestTest = async (
{
depositVault,
owner,
@@ -701,26 +1026,29 @@ export const setMaxSupplyCapTest = async (
depositVault: DepositVault | DepositVaultTest;
owner: SignerWithAddress;
},
- valueN: number,
+ value: number,
opt?: OptionalCommonParams,
) => {
- const value = parseUnits(valueN.toString());
-
- if (opt?.revertMessage) {
- await expect(
- depositVault.connect(opt?.from ?? owner).setMaxSupplyCap(value),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ depositVault
+ .connect(opt?.from ?? owner)
+ .setMaxAmountPerRequest.bind(this, value),
+ depositVault,
+ opt,
+ )
+ ) {
return;
}
await expect(
- depositVault.connect(opt?.from ?? owner).setMaxSupplyCap(value),
+ depositVault.connect(opt?.from ?? owner).setMaxAmountPerRequest(value),
).to.emit(
depositVault,
- depositVault.interface.events['SetMaxSupplyCap(address,uint256)'].name,
+ depositVault.interface.events['SetMaxAmountPerRequest(uint256)'].name,
).to.not.reverted;
- const newMax = await depositVault.maxSupplyCap();
+ const newMax = await depositVault.maxAmountPerRequest();
expect(newMax).eq(value);
};
@@ -769,15 +1097,17 @@ export const calcExpectedMintAmount = async (
tokenConfig.dataFeed,
sender,
);
- const currentTokenIn = tokenConfig.stable
+ const tokenInRate = tokenConfig.stable
? constants.WeiPerEther
: await dataFeedContract.getDataInBase18();
- if (currentTokenIn.isZero())
+ if (tokenInRate.isZero())
return {
mintAmount: constants.Zero,
amountInWithoutFee: constants.Zero,
actualAmountInUsd: constants.Zero,
fee: constants.Zero,
+ usdForMintConvertion: constants.Zero,
+ tokenInRate: constants.Zero,
};
const feePercent = await getFeePercent(
@@ -792,10 +1122,10 @@ export const calcExpectedMintAmount = async (
const amountInWithoutFee = amountIn.sub(fee);
- const feeInUsd = fee.mul(currentTokenIn).div(constants.WeiPerEther);
+ const feeInUsd = fee.mul(tokenInRate).div(constants.WeiPerEther);
const actualAmountInUsd = amountIn
- .mul(currentTokenIn)
+ .mul(tokenInRate)
.div(constants.WeiPerEther);
const usdForMintConvertion = actualAmountInUsd.sub(feeInUsd);
@@ -803,7 +1133,9 @@ export const calcExpectedMintAmount = async (
return {
mintAmount: usdForMintConvertion.mul(constants.WeiPerEther).div(mTokenRate),
actualAmountInUsd,
+ usdForMintConvertion,
amountInWithoutFee,
fee,
+ tokenInRate,
};
};
diff --git a/test/common/fast-evm.ts b/test/common/fast-evm.ts
new file mode 100644
index 00000000..d2e3ffac
--- /dev/null
+++ b/test/common/fast-evm.ts
@@ -0,0 +1,64 @@
+/**
+ * Test speed-up: skip the implicit `eth_estimateGas` that ethers v5 runs before
+ * every transaction.
+ *
+ * Profiling this suite showed the per-test cost is NOT EVM execution (an
+ * evm_snapshot/revert is ~0.2ms and EDR execution is sub-ms) but the JSON-RPC
+ * round-trips ethers v5 makes per tx. The single most expensive one is
+ * `eth_estimateGas`, which re-executes the entire transaction in the EVM just
+ * to measure gas — roughly doubling the EVM work for every tx. Measured impact
+ * on a transaction-heavy file: ~2.5x faster (161s -> 64s), with all tests still
+ * passing and no change to any test body.
+ *
+ * Instead of estimating, we return a fixed high gas cap. Correctness is
+ * preserved: a reverting tx still reverts at send time, so `expect(...).to.be
+ * .reverted*` matchers keep working (verified). gasLimit only caps execution;
+ * the EVM still charges the real gas used, so behaviour is identical.
+ *
+ * Opt out with FAST_EVM=false. Disabled automatically under coverage / gas
+ * reporting, which need real gas accounting. Set FAST_EVM_DEBUG=true to print
+ * how many estimateGas calls were skipped.
+ *
+ * This module is imported for its side effect from test/common/fixtures.ts so it
+ * engages in both serial and parallel (`--parallel`) runs, in every worker,
+ * before any transaction is sent.
+ */
+import hre from 'hardhat';
+
+const disabled =
+ process.env.FAST_EVM === 'false' ||
+ process.env.COVERAGE === 'true' ||
+ process.env.REPORT_GAS === 'true';
+
+if (!disabled) {
+ // 250M, comfortably under the 300M test block gas limit (see config/networks).
+ const FIXED_GAS = '0x' + (250_000_000).toString(16);
+
+ const provider = hre.network.provider as unknown as {
+ send?: (method: string, params?: unknown[]) => Promise;
+ request?: (args: {
+ method: string;
+ params?: unknown[];
+ }) => Promise;
+ };
+
+ if (typeof provider.send === 'function') {
+ const originalSend = provider.send.bind(provider);
+ provider.send = (method: string, params?: unknown[]) => {
+ if (method === 'eth_estimateGas') {
+ return Promise.resolve(FIXED_GAS);
+ }
+ return originalSend(method, params);
+ };
+ }
+
+ if (typeof provider.request === 'function') {
+ const originalRequest = provider.request.bind(provider);
+ provider.request = (args: { method: string; params?: unknown[] }) => {
+ if (args?.method === 'eth_estimateGas') {
+ return Promise.resolve(FIXED_GAS);
+ }
+ return originalRequest(args);
+ };
+ }
+}
diff --git a/test/common/fixtures.ts b/test/common/fixtures.ts
index 70e0ab6e..61ae81df 100644
--- a/test/common/fixtures.ts
+++ b/test/common/fixtures.ts
@@ -1,31 +1,64 @@
+// Side-effect import: installs the eth_estimateGas interceptor before any test
+// sends a transaction (see ./fast-evm). Every test loads this module via the
+// fixtures, so it engages in both serial and parallel runs. Keep this first.
+import './fast-evm';
+
import { Options } from '@layerzerolabs/lz-v2-utilities';
-import { expect } from 'chai';
import { constants } from 'ethers';
import { parseUnits } from 'ethers/lib/utils';
import { ethers } from 'hardhat';
-import * as hre from 'hardhat';
-import { approve, approveBase18, mintToken } from './common.helpers';
+import { NO_DELAY } from './ac.helpers';
+import { approve, approveBase18, keccak256, mintToken } from './common.helpers';
import { deployProxyContract } from './deploy.helpers';
import {
addPaymentTokenTest,
- addWaivedFeeAccountTest,
setInstantFeeTest,
setMinAmountTest,
+ setWaivedFeeAccountTest,
} from './manageable-vault.helpers';
-import { postDeploymentTest } from './post-deploy.helpers';
-
+import {
+ initializeDv,
+ initializeDvWithAave,
+ initializeDvWithMToken,
+ initializeDvWithMorpho,
+ initializeDvWithUstb,
+ initializeMv,
+ initializeRv,
+ initializeRvWithAave,
+ initializeRvWithMToken,
+ initializeRvWithMorpho,
+ initializeRvWithUstb,
+} from './vault-initializer.helpers';
+
+export {
+ getInitializerParamsDv,
+ getInitializerParamsDvWithMToken,
+ getInitializerParamsDvWithUstb,
+ getInitializerParamsMv,
+ getInitializerParamsRv,
+ getInitializerParamsRvWithMToken,
+ getInitializerParamsRvWithUstb,
+} from './vault-initializer.helpers';
+export type {
+ InitializerParamsDv,
+ InitializerParamsDvWithMToken,
+ InitializerParamsDvWithUstb,
+ InitializerParamsMv,
+ InitializerParamsRv,
+ InitializerParamsRvWithMToken,
+ InitializerParamsRvWithUstb,
+} from './vault-initializer.helpers';
+
+import { mTokensMetadata } from '../../helpers/mtokens-metadata';
import { getAllRoles, getRolesForToken } from '../../helpers/roles';
import {
AggregatorV3Mock__factory,
BlacklistableTester__factory,
- DepositVaultTest__factory,
ERC20Mock__factory,
GreenlistableTester__factory,
MidasAccessControlTest__factory,
PausableTester__factory,
- RedemptionVaultTest__factory,
- MTBILLTest__factory,
WithMidasAccessControlTester__factory,
DataFeedTest__factory,
AggregatorV3DeprecatedMock__factory,
@@ -33,21 +66,10 @@ import {
CustomAggregatorV3CompatibleFeedTester__factory,
SanctionsListMock__factory,
WithSanctionsListTester__factory,
- RedemptionTest__factory,
USTBRedemptionMock__factory,
- RedemptionVaultWithBUIDLTest__factory,
- RedemptionVaultWithUSTBTest__factory,
- RedemptionVaultWithSwapperTest__factory,
- RedemptionVaultWithAaveTest__factory,
- RedemptionVaultWithMorphoTest__factory,
- RedemptionVaultWithMTokenTest__factory,
AaveV3PoolMock__factory,
MorphoVaultMock__factory,
CustomAggregatorV3CompatibleFeedAdjustedTester__factory,
- DepositVaultWithAaveTest__factory,
- DepositVaultWithMorphoTest__factory,
- DepositVaultWithMTokenTest__factory,
- DepositVaultWithUSTBTest__factory,
USTBMock__factory,
CustomAggregatorV3CompatibleFeedGrowthTester__factory,
AcreAdapter__factory,
@@ -56,10 +78,14 @@ import {
MidasLzOFT__factory,
MidasLzOFTAdapter__factory,
MidasLzVaultComposerSyncTester,
- MTokenPermissionedTest__factory,
AxelarInterchainTokenServiceMock__factory,
MidasAxelarVaultExecutableTester,
LzEndpointV2Mock__factory,
+ MTokenTest__factory,
+ RedemptionVaultTest,
+ MidasTimelockManagerTest__factory,
+ MidasAccessControlTimelockControllerTest__factory,
+ MidasPauseManagerTest__factory,
} from '../../typechain-types';
export const defaultDeploy = async () => {
@@ -67,19 +93,58 @@ export const defaultDeploy = async () => {
owner,
customRecipient,
tokensReceiver,
- feeReceiver,
requestRedeemer,
liquidityProvider,
+ loanLp,
+ loanRepaymentAddress,
+ clawbackReceiver,
+ councilMember1,
+ councilMember2,
+ councilMember3,
+ councilMember4,
+ councilMember5,
...regularAccounts
] = await ethers.getSigners();
+ const councilMembers = [
+ councilMember1,
+ councilMember2,
+ councilMember3,
+ councilMember4,
+ councilMember5,
+ ];
+
const allRoles = getAllRoles();
// main contracts
const accessControl = await new MidasAccessControlTest__factory(
owner,
).deploy();
- await accessControl.initialize();
+ await accessControl.initialize(0, []);
+
+ const timelockManager = await new MidasTimelockManagerTest__factory(
+ owner,
+ ).deploy();
+
+ await timelockManager.initialize(
+ accessControl.address,
+ 100,
+ councilMembers.map((v) => v.address),
+ );
+
+ const timelock = await new MidasAccessControlTimelockControllerTest__factory(
+ owner,
+ ).deploy();
+ await timelock.initialize(timelockManager.address);
+
+ const pauseManager = await new MidasPauseManagerTest__factory(owner).deploy();
+ await pauseManager.initialize(accessControl.address, NO_DELAY, 86400);
+
+ await accessControl.initializeRelationships(
+ timelockManager.address,
+ pauseManager.address,
+ );
+ await timelockManager.initializeTimelock(timelock.address);
const mockedSanctionsList = await new SanctionsListMock__factory(
owner,
@@ -94,13 +159,41 @@ export const defaultDeploy = async () => {
mockedSanctionsList.address,
);
- const mTBILL = await new MTBILLTest__factory(owner).deploy();
- await expect(mTBILL.initialize(ethers.constants.AddressZero)).to.be.reverted;
- await mTBILL.initialize(accessControl.address);
+ const mTBILL = await new MTokenTest__factory(owner).deploy(
+ allRoles.tokenRoles.mTBILL.tokenManager,
+ allRoles.tokenRoles.mTBILL.minter,
+ allRoles.tokenRoles.mTBILL.burner,
+ allRoles.tokenRoles.mTBILL.greenlisted,
+ allRoles.tokenRoles.mTBILL.minBalanceExempt,
+ );
+
+ await mTBILL.initialize(
+ accessControl.address,
+ clawbackReceiver.address,
+ constants.MaxUint256,
+ false,
+ false,
+ mTokensMetadata.mTBILL.name,
+ mTokensMetadata.mTBILL.symbol,
+ );
+
+ const mTokenLoan = await new MTokenTest__factory(owner).deploy(
+ keccak256('M_TOKEN_MANAGER_ROLE'),
+ keccak256('M_TOKEN_TEST_MINT_OPERATOR_ROLE'),
+ keccak256('M_TOKEN_TEST_BURN_OPERATOR_ROLE'),
+ keccak256('M_TOKEN_TEST_GREENLISTED_ROLE'),
+ keccak256('M_TOKEN_TEST_MIN_BALANCE_EXEMPT_ROLE'),
+ );
- // separate mTBILL instance for swapper testing
- const mBASIS = await new MTBILLTest__factory(owner).deploy();
- await mBASIS.initialize(accessControl.address);
+ await mTokenLoan.initialize(
+ accessControl.address,
+ clawbackReceiver.address,
+ constants.MaxUint256,
+ false,
+ false,
+ 'mTokenLoan',
+ 'mTokenLoan',
+ );
const excludedRoles = [
allRoles.common.blacklisted,
@@ -114,12 +207,9 @@ export const defaultDeploy = async () => {
.flat(2)
.filter((v) => v !== '-' && !!v && !excludedRoles.includes(v)) as string[];
- await expect(
- accessControl.grantRoleMult(
- rolesFlat,
- rolesFlat.map((_) => owner.address),
- ),
- ).not.reverted;
+ await accessControl.grantRoleMult(
+ rolesFlat.map((role) => ({ role, account: owner.address, delay: 0 })),
+ );
const mockedAggregator = await new AggregatorV3Mock__factory(owner).deploy();
const mockedAggregatorDecimals = await mockedAggregator.decimals();
@@ -128,6 +218,10 @@ export const defaultDeploy = async () => {
owner,
).deploy();
+ const mockedAggregatorMTokenLoan = await new AggregatorV3Mock__factory(
+ owner,
+ ).deploy();
+
const mockedAggregatorMBasis = await new AggregatorV3Mock__factory(
owner,
).deploy();
@@ -143,6 +237,10 @@ export const defaultDeploy = async () => {
parseUnits('5', mockedAggregatorMTokenDecimals),
);
+ await mockedAggregatorMTokenLoan.setRoundData(
+ parseUnits('1', await mockedAggregatorMTokenLoan.decimals()),
+ );
+
await mockedAggregatorMBasis.setRoundData(
parseUnits('3', await mockedAggregatorMBasis.decimals()),
);
@@ -165,6 +263,17 @@ export const defaultDeploy = async () => {
parseUnits('10000', mockedAggregatorMTokenDecimals),
);
+ const mTokenLoanToUsdDataFeed = await new DataFeedTest__factory(
+ owner,
+ ).deploy();
+ await mTokenLoanToUsdDataFeed.initialize(
+ accessControl.address,
+ mockedAggregatorMTokenLoan.address,
+ 3 * 24 * 3600,
+ parseUnits('0.1', await mockedAggregatorMTokenLoan.decimals()),
+ parseUnits('10000', await mockedAggregatorMTokenLoan.decimals()),
+ );
+
const mBasisToUsdDataFeed = await new DataFeedTest__factory(owner).deploy();
await mBasisToUsdDataFeed.initialize(
accessControl.address,
@@ -186,68 +295,72 @@ export const defaultDeploy = async () => {
parseUnits('10000'),
);
- const depositVault = await new DepositVaultTest__factory(owner).deploy();
+ const commonVaultParams = {
+ accessControl,
+ mockedSanctionsList,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ tokensReceiver,
+ };
- await depositVault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
+ const depositVault = await initializeDv(commonVaultParams, undefined, {
+ from: owner,
+ });
+
+ await accessControl['grantRole(bytes32,address)'](
+ mTBILL.minterRole(),
+ depositVault.address,
+ );
+
+ const redemptionVaultLoanSwapper = await initializeRv(
{
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
+ accessControl,
+ mockedSanctionsList,
+ mTBILL: mTokenLoan,
+ mTokenToUsdDataFeed: mTokenLoanToUsdDataFeed,
+ tokensReceiver,
+ requestRedeemer,
},
+ undefined,
+ { from: owner },
+ );
+
+ const redemptionVault = await initializeRv(
{
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
+ ...commonVaultParams,
+ requestRedeemer,
+ loanLp,
+ loanRepaymentAddress,
+ redemptionVaultLoanSwapper,
},
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- 0,
- constants.MaxUint256,
+ undefined,
+ { from: owner },
);
- await accessControl.grantRole(
- mTBILL.M_TBILL_MINT_OPERATOR_ROLE(),
- depositVault.address,
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenLoan.burnerRole(),
+ redemptionVaultLoanSwapper.address,
);
- const redemptionVault = await new RedemptionVaultTest__factory(
- owner,
- ).deploy();
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenLoan.minterRole(),
+ owner.address,
+ );
- await redemptionVault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- 1000,
- {
- fiatAdditionalFee: 100,
- fiatFlatFee: parseUnits('1'),
- minFiatRedeemAmount: 1000,
- },
- requestRedeemer.address,
+ await redemptionVaultLoanSwapper.setWaivedFeeAccount(
+ redemptionVault.address,
+ true,
);
- await accessControl.grantRole(
- mTBILL.M_TBILL_BURN_OPERATOR_ROLE(),
+ await accessControl['grantRole(bytes32,address)'](
+ mTBILL.burnerRole(),
redemptionVault.address,
);
+ const manageableVault = await initializeMv(commonVaultParams, undefined, {
+ from: owner,
+ });
+
const stableCoins = {
usdc: await new ERC20Mock__factory(owner).deploy(8),
usdt: await new ERC20Mock__factory(owner).deploy(18),
@@ -259,86 +372,21 @@ export const defaultDeploy = async () => {
wbtc: await new ERC20Mock__factory(owner).deploy(8),
};
- /* Redemption Vault With BUIDL */
-
- const buidl = await new ERC20Mock__factory(owner).deploy(8);
- const buidlRedemption = await new RedemptionTest__factory(owner).deploy(
- buidl.address,
- stableCoins.usdc.address,
- );
- await stableCoins.usdc.mint(buidlRedemption.address, parseUnits('1000000'));
-
- 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: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- 1000,
- {
- fiatAdditionalFee: 100,
- fiatFlatFee: parseUnits('1'),
- minFiatRedeemAmount: 1000,
- },
- requestRedeemer.address,
- buidlRedemption.address,
- parseUnits('250000', 6),
- parseUnits('250000', 6),
- );
- await accessControl.grantRole(
- mTBILL.M_TBILL_BURN_OPERATOR_ROLE(),
- redemptionVaultWithBUIDL.address,
- );
-
const ustbToken = await new USTBMock__factory(owner).deploy();
/* Deposit Vault With USTB */
- const depositVaultWithUSTB = await new DepositVaultWithUSTBTest__factory(
- owner,
- ).deploy();
-
- await depositVaultWithUSTB[
- 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,uint256,uint256,address)'
- ](
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
+ const depositVaultWithUSTB = await initializeDvWithUstb(
{
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
+ ...commonVaultParams,
+ ustbToken,
},
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- 0,
- constants.MaxUint256,
- ustbToken.address,
+ undefined,
+ { from: owner },
);
- await accessControl.grantRole(
- mTBILL.M_TBILL_MINT_OPERATOR_ROLE(),
+ await accessControl['grantRole(bytes32,address)'](
+ mTBILL.minterRole(),
depositVaultWithUSTB.address,
);
@@ -350,40 +398,26 @@ export const defaultDeploy = async () => {
);
await stableCoins.usdc.mint(ustbRedemption.address, parseUnits('1000000'));
- 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: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- 1000,
+ const redemptionVaultWithUSTB = await initializeRvWithUstb(
{
- fiatAdditionalFee: 100,
- fiatFlatFee: parseUnits('1'),
- minFiatRedeemAmount: 1000,
+ ...commonVaultParams,
+ requestRedeemer,
+ loanLp,
+ loanRepaymentAddress,
+ redemptionVaultLoanSwapper,
+ ustbRedemption,
},
- requestRedeemer.address,
- ustbRedemption.address,
+ undefined,
+ { from: owner },
);
- await accessControl.grantRole(
- mTBILL.M_TBILL_BURN_OPERATOR_ROLE(),
+ await accessControl['grantRole(bytes32,address)'](
+ mTBILL.burnerRole(),
redemptionVaultWithUSTB.address,
);
+ await redemptionVaultLoanSwapper.setWaivedFeeAccount(
+ redemptionVaultWithUSTB.address,
+ true,
+ );
/* Redemption Vault With Aave */
@@ -392,42 +426,29 @@ export const defaultDeploy = async () => {
await aavePoolMock.setReserveAToken(stableCoins.usdc.address, aUSDC.address);
await stableCoins.usdc.mint(aavePoolMock.address, parseUnits('1000000'));
- const redemptionVaultWithAave =
- await new RedemptionVaultWithAaveTest__factory(owner).deploy();
-
- await redemptionVaultWithAave.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- 1000,
+ const redemptionVaultWithAave = await initializeRvWithAave(
{
- fiatAdditionalFee: 100,
- fiatFlatFee: parseUnits('1'),
- minFiatRedeemAmount: 1000,
+ ...commonVaultParams,
+ requestRedeemer,
+ loanLp,
+ loanRepaymentAddress,
+ redemptionVaultLoanSwapper,
},
- requestRedeemer.address,
+ undefined,
+ { from: owner },
);
await redemptionVaultWithAave.setAavePool(
stableCoins.usdc.address,
aavePoolMock.address,
);
- await accessControl.grantRole(
- mTBILL.M_TBILL_BURN_OPERATOR_ROLE(),
+ await accessControl['grantRole(bytes32,address)'](
+ mTBILL.burnerRole(),
redemptionVaultWithAave.address,
);
-
+ await redemptionVaultLoanSwapper.setWaivedFeeAccount(
+ redemptionVaultWithAave.address,
+ true,
+ );
/* Redemption Vault With Morpho */
const morphoVaultMock = await new MorphoVaultMock__factory(owner).deploy(
@@ -435,192 +456,79 @@ export const defaultDeploy = async () => {
);
await stableCoins.usdc.mint(morphoVaultMock.address, parseUnits('1000000'));
- const redemptionVaultWithMorpho =
- await new RedemptionVaultWithMorphoTest__factory(owner).deploy();
-
- await redemptionVaultWithMorpho.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
+ const redemptionVaultWithMorpho = await initializeRvWithMorpho(
{
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
+ ...commonVaultParams,
+ requestRedeemer,
+ loanLp,
+ loanRepaymentAddress,
+ redemptionVaultLoanSwapper,
},
- mockedSanctionsList.address,
- 1,
- 1000,
- {
- fiatAdditionalFee: 100,
- fiatFlatFee: parseUnits('1'),
- minFiatRedeemAmount: 1000,
- },
- requestRedeemer.address,
+ undefined,
+ { from: owner },
);
await redemptionVaultWithMorpho.setMorphoVault(
stableCoins.usdc.address,
morphoVaultMock.address,
);
- await accessControl.grantRole(
- mTBILL.M_TBILL_BURN_OPERATOR_ROLE(),
+ await accessControl['grantRole(bytes32,address)'](
+ mTBILL.burnerRole(),
redemptionVaultWithMorpho.address,
);
-
+ await redemptionVaultLoanSwapper.setWaivedFeeAccount(
+ redemptionVaultWithMorpho.address,
+ true,
+ );
/* Deposit Vault With Aave */
- const depositVaultWithAave = await new DepositVaultWithAaveTest__factory(
- owner,
- ).deploy();
-
- await depositVaultWithAave.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- 0,
- constants.MaxUint256,
+ const depositVaultWithAave = await initializeDvWithAave(
+ commonVaultParams,
+ undefined,
+ { from: owner },
);
await depositVaultWithAave.setAavePool(
stableCoins.usdc.address,
aavePoolMock.address,
);
- await accessControl.grantRole(
- mTBILL.M_TBILL_MINT_OPERATOR_ROLE(),
+ await accessControl['grantRole(bytes32,address)'](
+ mTBILL.minterRole(),
depositVaultWithAave.address,
);
/* Deposit Vault With Morpho */
- const depositVaultWithMorpho = await new DepositVaultWithMorphoTest__factory(
- owner,
- ).deploy();
-
- await depositVaultWithMorpho.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- 0,
- constants.MaxUint256,
+ const depositVaultWithMorpho = await initializeDvWithMorpho(
+ commonVaultParams,
+ undefined,
+ { from: owner },
);
- await accessControl.grantRole(
- mTBILL.M_TBILL_MINT_OPERATOR_ROLE(),
+ await accessControl['grantRole(bytes32,address)'](
+ mTBILL.minterRole(),
depositVaultWithMorpho.address,
);
/* Deposit Vault With MToken (deposits into mTBILL DV) */
- const depositVaultWithMToken = await new DepositVaultWithMTokenTest__factory(
- owner,
- ).deploy();
-
- await depositVaultWithMToken[
- 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,uint256,uint256,address)'
- ](
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
+ const depositVaultWithMToken = await initializeDvWithMToken(
{
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
+ ...commonVaultParams,
+ depositVault,
},
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- 0,
- constants.MaxUint256,
- depositVault.address,
+ undefined,
+ { from: owner },
);
- await accessControl.grantRole(
- mTBILL.M_TBILL_MINT_OPERATOR_ROLE(),
+ await accessControl['grantRole(bytes32,address)'](
+ mTBILL.minterRole(),
depositVaultWithMToken.address,
);
- await depositVault.addWaivedFeeAccount(depositVaultWithMToken.address);
-
- /* Redemption Vault With Swapper */
-
- 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: mBASIS.address,
- mTokenDataFeed: mBasisToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- 1000,
- {
- fiatAdditionalFee: 100,
- fiatFlatFee: parseUnits('1'),
- minFiatRedeemAmount: 1000,
- },
- requestRedeemer.address,
- redemptionVault.address,
- liquidityProvider.address,
- );
-
- await redemptionVault.addWaivedFeeAccount(redemptionVaultWithSwapper.address);
- await accessControl.grantRole(
- mTBILL.M_TBILL_BURN_OPERATOR_ROLE(),
- redemptionVaultWithSwapper.address,
- );
+ await depositVault.setWaivedFeeAccount(depositVaultWithMToken.address, true);
/* Redemption Vault With MToken (mFONE -> mTBILL) */
- const mFONE = await new MTBILLTest__factory(owner).deploy();
- await mFONE.initialize(accessControl.address);
-
const mockedAggregatorMFone = await new AggregatorV3Mock__factory(
owner,
).deploy();
@@ -636,44 +544,29 @@ export const defaultDeploy = async () => {
parseUnits('10000', mockedAggregatorDecimals),
);
- 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: mFONE.address,
- mTokenDataFeed: mFoneToUsdDataFeed.address,
- },
+ const redemptionVaultWithMToken = await initializeRvWithMToken(
{
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
+ ...commonVaultParams,
+ requestRedeemer,
+ loanLp,
+ loanRepaymentAddress,
+ redemptionVaultLoanSwapper,
+ redemptionVault: redemptionVaultLoanSwapper.address,
},
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- 1000,
- {
- fiatAdditionalFee: 100,
- fiatFlatFee: parseUnits('1'),
- minFiatRedeemAmount: 1000,
- },
- requestRedeemer.address,
- redemptionVault.address,
+ undefined,
+ { from: owner },
);
- await accessControl.grantRole(
- mFONE.M_TBILL_BURN_OPERATOR_ROLE(),
+ await redemptionVaultLoanSwapper.setWaivedFeeAccount(
redemptionVaultWithMToken.address,
+ true,
);
- await redemptionVault.addWaivedFeeAccount(redemptionVaultWithMToken.address);
- await accessControl.grantRole(
- mTBILL.M_TBILL_BURN_OPERATOR_ROLE(),
+ await accessControl['grantRole(bytes32,address)'](
+ mTBILL.burnerRole(),
+ redemptionVaultWithMToken.address,
+ );
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenLoan.burnerRole(),
redemptionVaultWithMToken.address,
);
@@ -720,14 +613,11 @@ export const defaultDeploy = async () => {
).deploy(customFeed.address, parseUnits('10', 8));
// role granting testers
- await accessControl.grantRole(
- await customFeed.CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE(),
+ await accessControl['grantRole(bytes32,address)'](
+ await customFeed.contractAdminRole(),
owner.address,
);
- const manualFulfillmentToken =
- await redemptionVault.MANUAL_FULLFILMENT_TOKEN();
-
// testers
const wAccessControlTester = await new WithMidasAccessControlTester__factory(
owner,
@@ -750,85 +640,87 @@ export const defaultDeploy = async () => {
const offChainUsdToken = constants.AddressZero;
// role granting testers
- await accessControl.grantRole(
+ await accessControl['grantRole(bytes32,address)'](
allRoles.common.blacklistedOperator,
blackListableTester.address,
);
- await accessControl.grantRole(
+ await accessControl['grantRole(bytes32,address)'](
allRoles.common.greenlistedOperator,
greenListableTester.address,
);
- const greenlistToggler = await greenListableTester.greenlistTogglerRole();
- await accessControl.grantRole(greenlistToggler, owner.address);
-
- await postDeploymentTest(hre, {
- accessControl,
- aggregator: mockedAggregator,
- dataFeed,
- depositVault,
- owner,
- redemptionVault,
- aggregatorMToken: mockedAggregatorMToken,
- dataFeedMToken: mTokenToUsdDataFeed,
- mTBILL,
- minMTokenAmountForFirstDeposit: '0',
- minAmount: parseUnits('100'),
- tokensReceiver: tokensReceiver.address,
- });
-
- const mockedDeprecatedAggregator =
- await new AggregatorV3DeprecatedMock__factory(owner).deploy();
- const mockedDeprecatedAggregatorDecimals =
- await mockedDeprecatedAggregator.decimals();
-
- await mockedDeprecatedAggregator.setRoundData(
- parseUnits('5', mockedAggregatorDecimals),
- );
-
- await mockedDeprecatedAggregator.setRoundData(
- parseUnits('1.07778', mockedAggregatorMTokenDecimals),
- );
- const dataFeedDeprecated = await new DataFeedTest__factory(owner).deploy();
- await dataFeedDeprecated.initialize(
- accessControl.address,
- mockedDeprecatedAggregator.address,
- 3 * 24 * 3600,
- parseUnits('0.1', mockedDeprecatedAggregatorDecimals),
- parseUnits('10000', mockedDeprecatedAggregatorDecimals),
+ const greenlistAdmin = await greenListableTester.greenlistAdminRole();
+ await accessControl['grantRole(bytes32,address)'](
+ greenlistAdmin,
+ owner.address,
);
- const mockedUnhealthyAggregator =
- await new AggregatorV3UnhealthyMock__factory(owner).deploy();
- const mockedUnhealthyAggregatorDecimals =
- await mockedUnhealthyAggregator.decimals();
-
- await mockedUnhealthyAggregator.setRoundData(
- parseUnits('5', mockedAggregatorDecimals),
- );
+ const deployDeprecatedFeed = async () => {
+ const mockedDeprecatedAggregator =
+ await new AggregatorV3DeprecatedMock__factory(owner).deploy();
+ const mockedDeprecatedAggregatorDecimals =
+ await mockedDeprecatedAggregator.decimals();
+
+ await mockedDeprecatedAggregator.setRoundData(
+ parseUnits('5', mockedAggregatorDecimals),
+ );
+
+ await mockedDeprecatedAggregator.setRoundData(
+ parseUnits('1.07778', mockedAggregatorMTokenDecimals),
+ );
+ const dataFeedDeprecated = await new DataFeedTest__factory(owner).deploy();
+ await dataFeedDeprecated.initialize(
+ accessControl.address,
+ mockedDeprecatedAggregator.address,
+ 3 * 24 * 3600,
+ parseUnits('0.1', mockedDeprecatedAggregatorDecimals),
+ parseUnits('10000', mockedDeprecatedAggregatorDecimals),
+ );
+
+ return {
+ dataFeedDeprecated,
+ mockedDeprecatedAggregator,
+ mockedDeprecatedAggregatorDecimals,
+ };
+ };
- await mockedUnhealthyAggregator.setRoundData(
- parseUnits('1.07778', mockedAggregatorMTokenDecimals),
- );
- const dataFeedUnhealthy = await new DataFeedTest__factory(owner).deploy();
- await dataFeedUnhealthy.initialize(
- accessControl.address,
- mockedUnhealthyAggregator.address,
- 3 * 24 * 3600,
- parseUnits('0.1', mockedUnhealthyAggregatorDecimals),
- parseUnits('10000', mockedUnhealthyAggregatorDecimals),
- );
+ const deployUnhealthyFeed = async () => {
+ const mockedUnhealthyAggregator =
+ await new AggregatorV3UnhealthyMock__factory(owner).deploy();
+ const mockedUnhealthyAggregatorDecimals =
+ await mockedUnhealthyAggregator.decimals();
+
+ await mockedUnhealthyAggregator.setRoundData(
+ parseUnits('5', mockedAggregatorDecimals),
+ );
+
+ await mockedUnhealthyAggregator.setRoundData(
+ parseUnits('1.07778', mockedAggregatorMTokenDecimals),
+ );
+ const dataFeedUnhealthy = await new DataFeedTest__factory(owner).deploy();
+ await dataFeedUnhealthy.initialize(
+ accessControl.address,
+ mockedUnhealthyAggregator.address,
+ 3 * 24 * 3600,
+ parseUnits('0.1', mockedUnhealthyAggregatorDecimals),
+ parseUnits('10000', mockedUnhealthyAggregatorDecimals),
+ );
+
+ return {
+ dataFeedUnhealthy,
+ mockedUnhealthyAggregator,
+ mockedUnhealthyAggregatorDecimals,
+ };
+ };
return {
customFeed,
customFeedAdjusted,
customFeedGrowth,
mTBILL,
- mBASIS,
- redemptionVaultWithSwapper,
mBasisToUsdDataFeed,
accessControl,
wAccessControlTester,
- roles: { ...allRoles, greenlistToggler },
+ roles: { ...allRoles, greenlistAdmin },
owner,
regularAccounts,
blackListableTester,
@@ -840,22 +732,15 @@ export const defaultDeploy = async () => {
depositVault,
redemptionVault,
stableCoins,
- manualFulfillmentToken,
mTokenToUsdDataFeed,
mockedAggregatorMToken,
mockedAggregatorMBasis,
offChainUsdToken,
mockedAggregatorMTokenDecimals,
tokensReceiver,
- feeReceiver,
- dataFeedDeprecated,
- dataFeedUnhealthy,
withSanctionsListTester,
mockedSanctionsList,
requestRedeemer,
- buidl,
- buidlRedemption,
- redemptionVaultWithBUIDL,
redemptionVaultWithUSTB,
redemptionVaultWithAave,
aavePoolMock,
@@ -863,7 +748,6 @@ export const defaultDeploy = async () => {
redemptionVaultWithMorpho,
morphoVaultMock,
liquidityProvider,
- mFONE,
mockedAggregatorMFone,
mFoneToUsdDataFeed,
redemptionVaultWithMToken,
@@ -877,11 +761,90 @@ export const defaultDeploy = async () => {
depositVaultWithMToken,
dataFeedGrowth,
compositeDataFeed,
+ loanLp,
+ loanRepaymentAddress,
+ redemptionVaultLoanSwapper,
+ mTokenLoan,
+ mTokenLoanToUsdDataFeed,
+ mockedAggregatorMTokenLoan,
+ timelock,
+ timelockManager,
+ pauseManager,
+ councilMembers,
+ clawbackReceiver,
+ manageableVault,
+ tokenContract: mTBILL,
+ deployDeprecatedFeed,
+ deployUnhealthyFeed,
};
};
+export type DefaultFixture = Omit<
+ Awaited>,
+ 'redemptionVault'
+> & {
+ redemptionVault: RedemptionVaultTest;
+};
+
+const M_TOKEN_TEST_MANAGER_ROLE = keccak256('M_TOKEN_TEST_MANAGER_ROLE');
+const M_TOKEN_TEST_MINT_OPERATOR_ROLE = keccak256(
+ 'M_TOKEN_TEST_MINT_OPERATOR_ROLE',
+);
+const M_TOKEN_TEST_BURN_OPERATOR_ROLE = keccak256(
+ 'M_TOKEN_TEST_BURN_OPERATOR_ROLE',
+);
+const M_TOKEN_TEST_GREENLISTED_ROLE = keccak256(
+ 'M_TOKEN_TEST_GREENLISTED_ROLE',
+);
+const M_TOKEN_TEST_MIN_BALANCE_EXEMPT_ROLE = keccak256(
+ 'M_TOKEN_TEST_MIN_BALANCE_EXEMPT_ROLE',
+);
+
+const deployFeatureFlaggedMToken = async (
+ owner: Awaited>['owner'],
+ accessControl: Awaited>['accessControl'],
+ clawbackReceiver: string,
+ isPermissioned: boolean,
+ isMinHoldingBalanceEnforced: boolean,
+ name: string,
+ symbol: string,
+) => {
+ const token = await new MTokenTest__factory(owner).deploy(
+ M_TOKEN_TEST_MANAGER_ROLE,
+ M_TOKEN_TEST_MINT_OPERATOR_ROLE,
+ M_TOKEN_TEST_BURN_OPERATOR_ROLE,
+ M_TOKEN_TEST_GREENLISTED_ROLE,
+ M_TOKEN_TEST_MIN_BALANCE_EXEMPT_ROLE,
+ );
+
+ await token.initialize(
+ accessControl.address,
+ clawbackReceiver,
+ constants.MaxUint256,
+ isPermissioned,
+ isMinHoldingBalanceEnforced,
+ name,
+ symbol,
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ M_TOKEN_TEST_MINT_OPERATOR_ROLE,
+ owner.address,
+ );
+ await accessControl['grantRole(bytes32,address)'](
+ M_TOKEN_TEST_BURN_OPERATOR_ROLE,
+ owner.address,
+ );
+ await accessControl['grantRole(bytes32,address)'](
+ M_TOKEN_TEST_MANAGER_ROLE,
+ owner.address,
+ );
+
+ return token;
+};
+
/**
- * mTokenPermissionedTest + dedicated deposit/redemption vaults (for integration-style tests).
+ * Permissioned mToken (feature flag) + dedicated deposit/redemption vaults.
*/
export const mTokenPermissionedFixture = async (
baseFixture?: Awaited>,
@@ -891,82 +854,59 @@ export const mTokenPermissionedFixture = async (
owner,
accessControl,
mockedSanctionsList,
- feeReceiver,
tokensReceiver,
requestRedeemer,
mTokenToUsdDataFeed,
} = fx;
- const mTokenPermissioned = await new MTokenPermissionedTest__factory(
+ const mTokenPermissioned = await deployFeatureFlaggedMToken(
owner,
- ).deploy();
- await mTokenPermissioned.initialize(accessControl.address);
+ accessControl,
+ fx.clawbackReceiver.address,
+ true,
+ false,
+ 'mTokenPermissioned',
+ 'mTokenPermissioned',
+ );
- const mintRole = await mTokenPermissioned.M_TOKEN_TEST_MINT_OPERATOR_ROLE();
- const burnRole = await mTokenPermissioned.M_TOKEN_TEST_BURN_OPERATOR_ROLE();
- const pauseRole = await mTokenPermissioned.M_TOKEN_TEST_PAUSE_OPERATOR_ROLE();
+ const mintRole = await mTokenPermissioned.minterRole();
+ const burnRole = await mTokenPermissioned.burnerRole();
+ const tokenManagerRole = await mTokenPermissioned.contractAdminRole();
const mTokenPermissionedGreenlistedRole =
- await mTokenPermissioned.M_TOKEN_TEST_GREENLISTED_ROLE();
+ await mTokenPermissioned.greenlistedRole();
- await accessControl.grantRole(mintRole, owner.address);
- await accessControl.grantRole(burnRole, owner.address);
- await accessControl.grantRole(pauseRole, owner.address);
-
- const mTokenPermissionedDepositVault = await new DepositVaultTest__factory(
- owner,
- ).deploy();
- await mTokenPermissionedDepositVault.initialize(
- accessControl.address,
- {
- mToken: mTokenPermissioned.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
+ const mTokenPermissionedDepositVault = await initializeDv(
{
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
+ accessControl,
+ mockedSanctionsList,
+ mTBILL: mTokenPermissioned,
+ mTokenToUsdDataFeed,
+ tokensReceiver,
},
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- 0,
- constants.MaxUint256,
+ undefined,
+ { from: owner },
);
- await accessControl.grantRole(
+ await accessControl['grantRole(bytes32,address)'](
mintRole,
mTokenPermissionedDepositVault.address,
);
- const mTokenPermissionedRedemptionVault =
- await new RedemptionVaultTest__factory(owner).deploy();
- await mTokenPermissionedRedemptionVault.initialize(
- accessControl.address,
- {
- mToken: mTokenPermissioned.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 0,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- 1000,
+ const mTokenPermissionedRedemptionVault = await initializeRv(
{
- fiatAdditionalFee: 100,
- fiatFlatFee: parseUnits('1'),
- minFiatRedeemAmount: 1000,
+ accessControl,
+ mockedSanctionsList,
+ mTBILL: mTokenPermissioned,
+ mTokenToUsdDataFeed,
+ tokensReceiver,
+ requestRedeemer,
},
- requestRedeemer.address,
+ undefined,
+ { from: owner },
);
- await accessControl.grantRole(
+
+ await mTokenPermissionedRedemptionVault.setMaxApproveRequestId(100);
+
+ await accessControl['grantRole(bytes32,address)'](
burnRole,
mTokenPermissionedRedemptionVault.address,
);
@@ -977,7 +917,7 @@ export const mTokenPermissionedFixture = async (
mTokenPermissionedRoles: {
mint: mintRole,
burn: burnRole,
- pause: pauseRole,
+ manager: tokenManagerRole,
greenlisted: mTokenPermissionedGreenlistedRole,
},
mTokenPermissionedDepositVault,
@@ -985,6 +925,70 @@ export const mTokenPermissionedFixture = async (
};
};
+/**
+ * mToken with min holding balance enforced (feature flag).
+ */
+export const mTokenMinBalanceFixture = async (
+ baseFixture?: Awaited>,
+) => {
+ const fx = baseFixture ?? (await defaultDeploy());
+ const { owner, accessControl } = fx;
+
+ const mTokenMinBalance = await deployFeatureFlaggedMToken(
+ owner,
+ accessControl,
+ fx.clawbackReceiver.address,
+ false,
+ true,
+ 'mTokenMinBalance',
+ 'mTokenMinBalance',
+ );
+
+ return {
+ ...fx,
+ mTokenMinBalance,
+ mTokenMinBalanceRoles: {
+ mint: await mTokenMinBalance.minterRole(),
+ burn: await mTokenMinBalance.burnerRole(),
+ manager: await mTokenMinBalance.contractAdminRole(),
+ minBalanceExempt: await mTokenMinBalance.minBalanceExemptRole(),
+ },
+ };
+};
+
+/**
+ * Permissioned mToken with min holding balance enforced (feature flags).
+ */
+export const mTokenPermissionedMinBalanceFixture = async (
+ baseFixture?: Awaited>,
+) => {
+ const fx = baseFixture ?? (await defaultDeploy());
+ const { owner, accessControl } = fx;
+
+ const mTokenPermissionedMinBalance = await deployFeatureFlaggedMToken(
+ owner,
+ accessControl,
+ fx.clawbackReceiver.address,
+ true,
+ true,
+ 'mTokenPermissionedMinBalance',
+ 'mTokenPermissionedMinBalance',
+ );
+
+ return {
+ ...fx,
+ mTokenPermissionedMinBalance,
+ mTokenPermissionedMinBalanceRoles: {
+ mint: await mTokenPermissionedMinBalance.minterRole(),
+ burn: await mTokenPermissionedMinBalance.burnerRole(),
+ manager: await mTokenPermissionedMinBalance.contractAdminRole(),
+ greenlisted: await mTokenPermissionedMinBalance.greenlistedRole(),
+ minBalanceExempt:
+ await mTokenPermissionedMinBalance.minBalanceExemptRole(),
+ },
+ };
+};
+
export const acreAdapterFixture = async () => {
const defaultFixture = await defaultDeploy();
@@ -1015,9 +1019,10 @@ export const acreAdapterFixture = async () => {
0,
);
- await addWaivedFeeAccountTest(
+ await setWaivedFeeAccountTest(
{ vault: defaultFixture.redemptionVault, owner: defaultFixture.owner },
acreUsdcMTbillAdapter.address,
+ true,
);
await addPaymentTokenTest(
@@ -1109,15 +1114,12 @@ export const layerZeroFixture = async () => {
},
]);
- await accessControl.grantRoleMult(
- [roles.minter, roles.burner, roles.minter, roles.burner],
- [
- oftAdapterA.address,
- oftAdapterA.address,
- oftAdapterB.address,
- oftAdapterB.address,
- ],
- );
+ await accessControl.grantRoleMult([
+ { role: roles.minter, account: oftAdapterA.address, delay: 0 },
+ { role: roles.burner, account: oftAdapterA.address, delay: 0 },
+ { role: roles.minter, account: oftAdapterB.address, delay: 0 },
+ { role: roles.burner, account: oftAdapterB.address, delay: 0 },
+ ]);
await mockEndpointA.setDestLzEndpoint(
oftAdapterB.address,
@@ -1313,15 +1315,12 @@ export const axelarFixture = async () => {
const roles = getRolesForToken('mTBILL');
- await accessControl.grantRoleMult(
- [roles.minter, roles.burner, roles.minter, roles.burner],
- [
- axelarItsA.address,
- axelarItsA.address,
- axelarItsB.address,
- axelarItsB.address,
- ],
- );
+ await accessControl.grantRoleMult([
+ { role: roles.minter, account: axelarItsA.address, delay: 0 },
+ { role: roles.burner, account: axelarItsA.address, delay: 0 },
+ { role: roles.minter, account: axelarItsB.address, delay: 0 },
+ { role: roles.burner, account: axelarItsB.address, delay: 0 },
+ ]);
const executor = await deployProxyContract(
'MidasAxelarVaultExecutableTester',
diff --git a/test/common/greenlist.helpers.ts b/test/common/greenlist.helpers.ts
index 3bc5ac27..e7171e36 100644
--- a/test/common/greenlist.helpers.ts
+++ b/test/common/greenlist.helpers.ts
@@ -1,7 +1,7 @@
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { expect } from 'chai';
-import { OptionalCommonParams } from './common.helpers';
+import { handleRevert, OptionalCommonParams } from './common.helpers';
import { Greenlistable } from '../../typechain-types';
@@ -15,10 +15,15 @@ export const greenListEnable = async (
enable: boolean,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
- greenlistable.connect(opt?.from ?? owner).setGreenlistEnable(enable),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ greenlistable
+ .connect(opt?.from ?? owner)
+ .setGreenlistEnable.bind(this, enable),
+ greenlistable,
+ opt,
+ )
+ ) {
return;
}
@@ -26,7 +31,7 @@ export const greenListEnable = async (
greenlistable.connect(opt?.from ?? owner).setGreenlistEnable(enable),
).to.emit(
greenlistable,
- greenlistable.interface.events['SetGreenlistEnable(address,bool)'].name,
+ greenlistable.interface.events['SetGreenlistEnable(bool)'].name,
).to.not.reverted;
expect(await greenlistable.greenlistEnabled()).eq(enable);
diff --git a/test/common/layerzero.helpers.ts b/test/common/layerzero.helpers.ts
index 98e3f8db..d364160c 100644
--- a/test/common/layerzero.helpers.ts
+++ b/test/common/layerzero.helpers.ts
@@ -4,13 +4,16 @@ import {
BigNumber,
BigNumberish,
constants,
- Contract,
ContractTransaction,
} from 'ethers';
import { parseUnits } from 'ethers/lib/utils';
import { ethers } from 'hardhat';
-import { OptionalCommonParams, tokenAmountToBase18 } from './common.helpers';
+import {
+ handleRevert,
+ OptionalCommonParams,
+ tokenAmountToBase18,
+} from './common.helpers';
import { calcExpectedMintAmount } from './deposit-vault.helpers';
import { layerZeroFixture } from './fixtures';
import { calcExpectedTokenOutAmount } from './redemption-vault.helpers';
@@ -60,10 +63,11 @@ export const setRateLimitConfig = async (
) => {
const from = opt?.from ?? owner;
- if (opt?.revertMessage) {
- await expect(
- oftAdapter.connect(from).setRateLimits([{ dstEid, limit, window }]),
- ).revertedWith(opt?.revertMessage);
+ const callFn = oftAdapter
+ .connect(from)
+ .setRateLimits.bind(this, [{ dstEid, limit, window }]);
+
+ if (await handleRevert(callFn, oftAdapter, opt)) {
return;
}
@@ -84,10 +88,6 @@ export const sendOft = async (
},
opt?: {
revertOnDst?: boolean;
- revertWithCustomError?: {
- contract: Contract;
- error: string;
- };
} & OptionalCommonParams,
) => {
const from = opt?.from ?? owner;
@@ -130,20 +130,9 @@ export const sendOft = async (
{ value: nativeFee },
] as const;
- if (opt?.revertMessage && !opt?.revertOnDst) {
- await expect(
- oftFrom.connect(opt?.from ?? owner).send(...params),
- ).revertedWith(opt?.revertMessage);
- return;
- }
+ const callFn = oftFrom.connect(opt?.from ?? owner).send.bind(this, ...params);
- if (opt?.revertWithCustomError && !opt?.revertOnDst) {
- await expect(
- oftFrom.connect(opt?.from ?? owner).send(...params),
- ).revertedWithCustomError(
- opt?.revertWithCustomError.contract,
- opt?.revertWithCustomError.error,
- );
+ if (!opt?.revertOnDst && (await handleRevert(callFn, oftFrom, opt))) {
return;
}
@@ -155,7 +144,7 @@ export const sendOft = async (
oftFrom,
oftFrom.interface.events['OFTSent(bytes32,uint32,address,uint256,uint256)']
.name,
- ).not.reverted;
+ );
const totalSupplyAfter = await mTBILL.totalSupply();
const balanceFromAfter = await mTBILL.balanceOf(from.address);
@@ -203,10 +192,6 @@ export const sendOftLockBox = async (
},
opt?: {
revertOnDst?: boolean;
- revertWithCustomError?: {
- contract: Contract;
- error: string;
- };
} & OptionalCommonParams,
) => {
const from = opt?.from ?? owner;
@@ -249,20 +234,9 @@ export const sendOftLockBox = async (
{ value: nativeFee },
] as const;
- if (opt?.revertMessage && !opt?.revertOnDst) {
- await expect(
- oftFrom.connect(opt?.from ?? owner).send(...params),
- ).revertedWith(opt?.revertMessage);
- return;
- }
+ const callFn = oftFrom.connect(opt?.from ?? owner).send.bind(this, ...params);
- if (opt?.revertWithCustomError && !opt?.revertOnDst) {
- await expect(
- oftFrom.connect(opt?.from ?? owner).send(...params),
- ).revertedWithCustomError(
- opt?.revertWithCustomError.contract,
- opt?.revertWithCustomError.error,
- );
+ if (!opt?.revertOnDst && (await handleRevert(callFn, oftFrom, opt))) {
return;
}
@@ -277,7 +251,7 @@ export const sendOftLockBox = async (
oftFrom,
oftFrom.interface.events['OFTSent(bytes32,uint32,address,uint256,uint256)']
.name,
- ).not.reverted;
+ );
const totalSupplyAfter = await pToken.totalSupply();
const totalSupplyOftAfter = await pTokenLzOft.totalSupply();
@@ -333,10 +307,6 @@ export const depositAndSend = async (
opt?: {
revertOnDst?: boolean;
refundOnDst?: boolean;
- revertWithCustomError?: {
- contract: Contract;
- error: string;
- };
overrideValue?: BigNumberish;
expectedMintAmountWoDust?: BigNumberish;
} & OptionalCommonParams,
@@ -491,16 +461,9 @@ export const depositAndSend = async (
txFn = composer.connect(from).depositAndSend.bind(this, ...params);
}
- if (opt?.revertMessage && !opt?.revertOnDst) {
- await expect(txFn()).revertedWith(opt?.revertMessage);
- return;
- }
+ const callFn = composer.connect(from).depositAndSend.bind(this, ...params);
- if (opt?.revertWithCustomError && !opt?.revertOnDst) {
- await expect(txFn()).revertedWithCustomError(
- opt?.revertWithCustomError.contract,
- opt?.revertWithCustomError.error,
- );
+ if (!opt?.revertOnDst && (await handleRevert(callFn, composer, opt))) {
return;
}
@@ -515,31 +478,12 @@ export const depositAndSend = async (
composer.interface.events['Refunded(bytes32)'].name,
);
} else {
- await expect(txFn())
- .to.emit(
- composer,
- composer.interface.events[
- 'Deposited(bytes32,bytes32,uint32,uint256,uint256)'
- ].name,
- )
- .to.emit(
- depositVault,
- depositVault.interface.events[
- 'DepositInstantWithCustomRecipient(address,address,address,uint256,uint256,uint256,uint256,bytes32)'
- ].name,
- )
- .withArgs(
- composer.address,
- await composer.paymentTokenErc20(),
- direction === 'A_TO_A' || direction === 'B_TO_A'
- ? recipient
- : composer.address,
- actualAmountInUsd,
- amountParsed,
- fee,
- mintAmount,
- referrerId,
- );
+ await expect(txFn()).to.emit(
+ composer,
+ composer.interface.events[
+ 'Deposited(bytes32,bytes32,uint32,uint256,uint256)'
+ ].name,
+ );
}
const totalSupplyAfter = await mTBILL.totalSupply();
@@ -605,10 +549,7 @@ export const redeemAndSend = async (
refundOnDst?: boolean;
expectedReceiveAmountWoDust?: BigNumberish;
revertOnDst?: boolean;
- revertWithCustomError?: {
- contract: Contract;
- error: string;
- };
+
overrideValue?: BigNumberish;
} & OptionalCommonParams,
) => {
@@ -749,16 +690,9 @@ export const redeemAndSend = async (
txFn = composer.connect(from).redeemAndSend.bind(this, ...params);
}
- if (opt?.revertMessage && !opt?.revertOnDst) {
- await expect(txFn()).revertedWith(opt?.revertMessage);
- return;
- }
+ const callFn = composer.connect(from).redeemAndSend.bind(this, ...params);
- if (opt?.revertWithCustomError && !opt?.revertOnDst) {
- await expect(txFn()).revertedWithCustomError(
- opt?.revertWithCustomError.contract,
- opt?.revertWithCustomError.error,
- );
+ if (!opt?.revertOnDst && (await handleRevert(callFn, composer, opt))) {
return;
}
@@ -788,22 +722,6 @@ export const redeemAndSend = async (
direction === 'A_TO_A' || direction === 'B_TO_A' ? eidA : eidB,
amountParsed,
amountOut,
- )
- .to.emit(
- redemptionVault,
- redemptionVault.interface.events[
- 'RedeemInstantWithCustomRecipient(address,address,address,uint256,uint256,uint256)'
- ].name,
- )
- .withArgs(
- composer.address,
- await composer.paymentTokenErc20(),
- direction === 'A_TO_A' || direction === 'B_TO_A'
- ? recipient
- : composer.address,
- amountParsed,
- fee,
- amountOut,
);
}
diff --git a/test/common/mTBILL.helpers.ts b/test/common/mTBILL.helpers.ts
deleted file mode 100644
index 8613a8b3..00000000
--- a/test/common/mTBILL.helpers.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
-import { expect } from 'chai';
-import { BigNumberish } from 'ethers';
-import { defaultAbiCoder, solidityKeccak256 } from 'ethers/lib/utils';
-
-import { Account, OptionalCommonParams, getAccount } from './common.helpers';
-
-import { MTBILL, MToken, MTokenPermissioned } from '../../typechain-types';
-
-type CommonParams = {
- tokenContract: MToken | MTBILL | MTokenPermissioned;
- owner: SignerWithAddress;
-};
-
-export const setMetadataTest = async (
- { tokenContract, owner }: CommonParams,
- key: string,
- value: string,
- opt?: OptionalCommonParams,
-) => {
- const keyBytes32 = solidityKeccak256(['string'], [key]);
- const valueBytes = defaultAbiCoder.encode(['string'], [value]);
-
- if (opt?.revertMessage) {
- await expect(
- tokenContract
- .connect(opt?.from ?? owner)
- .setMetadata(keyBytes32, valueBytes),
- ).revertedWith(opt?.revertMessage);
- return;
- }
-
- await expect(
- tokenContract
- .connect(opt?.from ?? owner)
- .setMetadata(keyBytes32, valueBytes),
- ).not.reverted;
-
- expect(await tokenContract.metadata(keyBytes32)).eq(valueBytes);
-};
-
-export const mint = async (
- { tokenContract, owner }: CommonParams,
- to: Account,
- amount: BigNumberish,
- opt?: OptionalCommonParams,
-) => {
- to = getAccount(to);
-
- if (opt?.revertMessage) {
- await expect(
- tokenContract.connect(opt?.from ?? owner).mint(to, amount),
- ).revertedWith(opt?.revertMessage);
- return;
- }
-
- const balanceBefore = await tokenContract.balanceOf(to);
-
- await expect(tokenContract.connect(owner).mint(to, amount)).to.emit(
- tokenContract,
- tokenContract.interface.events['Transfer(address,address,uint256)'].name,
- ).to.not.reverted;
-
- const balanceAfter = await tokenContract.balanceOf(to);
-
- expect(balanceAfter.sub(balanceBefore)).eq(amount);
-};
-
-export const burn = async (
- { tokenContract, owner }: CommonParams,
- from: Account,
- amount: BigNumberish,
- opt?: OptionalCommonParams,
-) => {
- from = getAccount(from);
-
- if (opt?.revertMessage) {
- await expect(
- tokenContract.connect(opt?.from ?? owner).burn(from, amount),
- ).revertedWith(opt?.revertMessage);
- return;
- }
-
- const balanceBefore = await tokenContract.balanceOf(from);
-
- await expect(tokenContract.connect(owner).burn(from, amount)).to.emit(
- tokenContract,
- tokenContract.interface.events['Transfer(address,address,uint256)'].name,
- ).to.not.reverted;
-
- const balanceAfter = await tokenContract.balanceOf(from);
-
- expect(balanceBefore.sub(balanceAfter)).eq(amount);
-};
diff --git a/test/common/manageable-vault.helpers.ts b/test/common/manageable-vault.helpers.ts
index 2ac6ba78..4167a841 100644
--- a/test/common/manageable-vault.helpers.ts
+++ b/test/common/manageable-vault.helpers.ts
@@ -1,9 +1,15 @@
+import { days } from '@nomicfoundation/hardhat-network-helpers/dist/src/helpers/time/duration';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { expect } from 'chai';
import { BigNumber, BigNumberish, constants } from 'ethers';
import { parseUnits } from 'ethers/lib/utils';
-import { Account, OptionalCommonParams, getAccount } from './common.helpers';
+import {
+ getAccount,
+ getCurrentBlockTimestamp,
+ handleRevert,
+ OptionalCommonParams,
+} from './common.helpers';
import { defaultDeploy } from './fixtures';
import {
@@ -15,12 +21,11 @@ import {
ERC20,
ERC20__factory,
IERC20,
+ ManageableVault,
RedemptionVault,
- RedemptionVaultWIthBUIDL,
RedemptionVaultWithAave,
RedemptionVaultWithMorpho,
RedemptionVaultWithMToken,
- RedemptionVaultWithSwapper,
RedemptionVaultWithUSTB,
} from '../../typechain-types';
@@ -32,12 +37,11 @@ type CommonParamsChangePaymentToken = {
| DepositVaultWithMToken
| DepositVaultWithUSTB
| RedemptionVault
- | RedemptionVaultWIthBUIDL
| RedemptionVaultWithAave
| RedemptionVaultWithMorpho
| RedemptionVaultWithMToken
- | RedemptionVaultWithSwapper
- | RedemptionVaultWithUSTB;
+ | RedemptionVaultWithUSTB
+ | ManageableVault;
owner: SignerWithAddress;
};
type CommonParams = {
@@ -49,75 +53,151 @@ type CommonParams = {
| DepositVaultWithUSTB;
} & Pick>, 'owner'>;
+export type WindowRateLimitCapacityParams = {
+ /** stored `amountInFlight` (not a pre-decayed view) */
+ amountInFlight: BigNumberish;
+ lastUpdated: BigNumberish;
+ limit: BigNumberish;
+ window: BigNumberish;
+ /** current timestamp (`block.timestamp`) */
+ now: BigNumberish;
+};
+
+export type WindowRateLimitCapacity = {
+ inFlight: BigNumber;
+ remaining: BigNumber;
+};
+
+/** `Math.mulDiv(a, b, c, Down)` — matches OpenZeppelin / `RateLimitLibrary`. */
+export const mulDiv = (
+ a: BigNumberish,
+ b: BigNumberish,
+ c: BigNumberish,
+): BigNumber => {
+ const denominator = BigNumber.from(c);
+ if (denominator.isZero()) {
+ return BigNumber.from(0);
+ }
+ return BigNumber.from(
+ (BigInt(BigNumber.from(a).toString()) *
+ BigInt(BigNumber.from(b).toString())) /
+ BigInt(denominator.toString()),
+ );
+};
+
+/**
+ * Mirrors `RateLimitLibrary._availableCapacity` (decayed in-flight + headroom).
+ */
+export const calculateWindowRateLimitCapacity = ({
+ amountInFlight,
+ lastUpdated,
+ limit,
+ window,
+ now,
+}: WindowRateLimitCapacityParams): WindowRateLimitCapacity => {
+ const elapsed = BigNumber.from(now).sub(lastUpdated);
+ const windowBn = BigNumber.from(window);
+ const divisor = windowBn.isZero() ? BigNumber.from(1) : windowBn;
+
+ const decay = mulDiv(limit, elapsed, divisor);
+
+ const amountInFlightBn = BigNumber.from(amountInFlight);
+ const inFlight = amountInFlightBn.lte(decay)
+ ? BigNumber.from(0)
+ : amountInFlightBn.sub(decay);
+
+ const limitBn = BigNumber.from(limit);
+ const remaining = limitBn.lte(inFlight)
+ ? BigNumber.from(0)
+ : limitBn.sub(inFlight);
+
+ return { inFlight, remaining };
+};
+
export const setInstantFeeTest = async (
{ vault, owner }: CommonParamsChangePaymentToken,
newFee: BigNumberish,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
- vault.connect(opt?.from ?? owner).setInstantFee(newFee),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ vault.connect(opt?.from ?? owner).setInstantFee.bind(this, newFee),
+ vault,
+ opt,
+ )
+ ) {
return;
}
await expect(vault.connect(opt?.from ?? owner).setInstantFee(newFee))
- .to.emit(
- vault,
- vault.interface.events['SetInstantFee(address,uint256)'].name,
- )
- .withArgs((opt?.from ?? owner).address, newFee).to.not.reverted;
+ .to.emit(vault, vault.interface.events['SetInstantFee(uint256)'].name)
+ .withArgs(newFee);
const fee = await vault.instantFee();
expect(fee).eq(newFee);
};
-export const setVariabilityToleranceTest = async (
+export const setMinMaxInstantFeeTest = async (
{ vault, owner }: CommonParamsChangePaymentToken,
- newTolerance: BigNumberish,
+ newMinInstantFee: BigNumberish,
+ newMaxInstantFee: BigNumberish,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
- vault.connect(opt?.from ?? owner).setVariationTolerance(newTolerance),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ vault
+ .connect(opt?.from ?? owner)
+ .setMinMaxInstantFee.bind(this, newMinInstantFee, newMaxInstantFee),
+ vault,
+ opt,
+ )
+ ) {
return;
}
await expect(
- vault.connect(opt?.from ?? owner).setVariationTolerance(newTolerance),
+ vault
+ .connect(opt?.from ?? owner)
+ .setMinMaxInstantFee(newMinInstantFee, newMaxInstantFee),
)
.to.emit(
vault,
- vault.interface.events['SetVariationTolerance(address,uint256)'].name,
+ vault.interface.events['SetMinMaxInstantFee(uint256,uint256)'].name,
)
- .withArgs((opt?.from ?? owner).address, newTolerance).to.not.reverted;
+ .withArgs(newMinInstantFee, newMaxInstantFee);
- const tolerance = await vault.variationTolerance();
- expect(tolerance).eq(newTolerance);
+ expect(await vault.minInstantFee()).eq(newMinInstantFee);
+ expect(await vault.maxInstantFee()).eq(newMaxInstantFee);
};
-export const addWaivedFeeAccountTest = async (
+export const setVariabilityToleranceTest = async (
{ vault, owner }: CommonParamsChangePaymentToken,
- account: string,
+ newTolerance: BigNumberish,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
- vault.connect(opt?.from ?? owner).addWaivedFeeAccount(account),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ vault
+ .connect(opt?.from ?? owner)
+ .setVariationTolerance.bind(this, newTolerance),
+ vault,
+ opt,
+ )
+ ) {
return;
}
- await expect(vault.connect(opt?.from ?? owner).addWaivedFeeAccount(account))
+ await expect(
+ vault.connect(opt?.from ?? owner).setVariationTolerance(newTolerance),
+ )
.to.emit(
vault,
- vault.interface.events['AddWaivedFeeAccount(address,address)'].name,
+ vault.interface.events['SetVariationTolerance(uint256)'].name,
)
- .withArgs((opt?.from ?? owner).address, account).to.not.reverted;
+ .withArgs(newTolerance);
- const isWaivedFee = await vault.waivedFeeRestriction(account);
- expect(isWaivedFee).eq(true);
+ const tolerance = await vault.variationTolerance();
+ expect(tolerance).eq(newTolerance);
};
export const changeTokenAllowanceTest = async (
@@ -126,12 +206,15 @@ export const changeTokenAllowanceTest = async (
newAllowance: BigNumberish,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
+ if (
+ await handleRevert(
vault
.connect(opt?.from ?? owner)
- .changeTokenAllowance(token, newAllowance),
- ).revertedWith(opt?.revertMessage);
+ .changeTokenAllowance.bind(this, token, newAllowance),
+ vault,
+ opt,
+ )
+ ) {
return;
}
@@ -140,10 +223,9 @@ export const changeTokenAllowanceTest = async (
)
.to.emit(
vault,
- vault.interface.events['ChangeTokenAllowance(address,address,uint256)']
- .name,
+ vault.interface.events['ChangeTokenAllowance(address,uint256)'].name,
)
- .withArgs((opt?.from ?? owner).address, token).to.not.reverted;
+ .withArgs(token, newAllowance);
const allowance = (await vault.tokensConfig(token)).allowance;
expect(allowance).eq(newAllowance);
@@ -155,93 +237,217 @@ export const changeTokenFeeTest = async (
newFee: BigNumberish,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
- vault.connect(opt?.from ?? owner).changeTokenFee(token, newFee),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ vault
+ .connect(opt?.from ?? owner)
+ .changeTokenFee.bind(this, token, newFee),
+ vault,
+ opt,
+ )
+ ) {
return;
}
await expect(vault.connect(opt?.from ?? owner).changeTokenFee(token, newFee))
.to.emit(
vault,
- vault.interface.events['ChangeTokenFee(address,address,uint256)'].name,
+ vault.interface.events['ChangeTokenFee(address,uint256)'].name,
)
- .withArgs((opt?.from ?? owner).address, token, newFee).to.not.reverted;
+ .withArgs(token, newFee);
const fee = (await vault.tokensConfig(token)).fee;
expect(fee).eq(newFee);
};
-export const removeWaivedFeeAccountTest = async (
+export const setWaivedFeeAccountTest = async (
{ vault, owner }: CommonParamsChangePaymentToken,
account: string,
+ enable: boolean,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
- vault.connect(opt?.from ?? owner).removeWaivedFeeAccount(account),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ vault
+ .connect(opt?.from ?? owner)
+ .setWaivedFeeAccount.bind(this, account, enable),
+ vault,
+ opt,
+ )
+ ) {
return;
}
await expect(
- vault.connect(opt?.from ?? owner).removeWaivedFeeAccount(account),
+ vault.connect(opt?.from ?? owner).setWaivedFeeAccount(account, enable),
)
.to.emit(
vault,
- vault.interface.events['RemoveWaivedFeeAccount(address,address)'].name,
+ vault.interface.events['SetWaivedFeeAccount(address,bool)'].name,
)
- .withArgs((opt?.from ?? owner).address, account).to.not.reverted;
+ .withArgs(account, enable);
const isWaivedFee = await vault.waivedFeeRestriction(account);
- expect(isWaivedFee).eq(false);
+ expect(isWaivedFee).eq(enable);
};
-export const setInstantDailyLimitTest = async (
+export const setInstantLimitConfigTest = async (
{ vault, owner }: CommonParamsChangePaymentToken,
- newLimit: BigNumberish,
+ newLimit: BigNumberish | { window: BigNumberish; limit: BigNumberish },
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
- vault.connect(opt?.from ?? owner).setInstantDailyLimit(newLimit),
- ).revertedWith(opt?.revertMessage);
+ const { window, limit: newLimitValue } =
+ typeof newLimit === 'object' && 'window' in newLimit
+ ? { window: newLimit.window, limit: newLimit.limit }
+ : { window: days(1), limit: newLimit };
+
+ if (
+ await handleRevert(
+ vault
+ .connect(opt?.from ?? owner)
+ .setInstantLimitConfig.bind(this, window, newLimitValue),
+ vault,
+ opt,
+ )
+ ) {
return;
}
- await expect(vault.connect(opt?.from ?? owner).setInstantDailyLimit(newLimit))
- .to.emit(
+ const limitConfigsBefore = await vault.getInstantLimitStatuses();
+
+ await expect(
+ vault
+ .connect(opt?.from ?? owner)
+ .setInstantLimitConfig(window, newLimitValue),
+ )
+ .to.emit(vault, 'WindowLimitSet')
+ .withArgs(window, newLimitValue);
+
+ const limitConfigsAfter = await vault.getInstantLimitStatuses();
+
+ const configBefore = limitConfigsBefore.filter((w) =>
+ w.window.eq(window),
+ )?.[0];
+
+ const configAfter = limitConfigsAfter.filter((w) => w.window.eq(window))?.[0];
+
+ const currentTimestamp = await getCurrentBlockTimestamp();
+
+ if (configBefore) {
+ const { inFlight, remaining } = calculateWindowRateLimitCapacity({
+ amountInFlight: configBefore.inFlight,
+ lastUpdated: configBefore.lastUpdated,
+ limit: newLimitValue,
+ window,
+ now: currentTimestamp,
+ });
+
+ expect(configAfter).not.eq(undefined);
+ expect(configBefore).not.eq(undefined);
+ expect(configAfter.limit).eq(newLimitValue);
+ expect(configAfter.lastUpdated).eq(currentTimestamp);
+ expect(configAfter.inFlight).eq(inFlight);
+ expect(configAfter.remaining).eq(remaining);
+ } else {
+ expect(configAfter).not.eq(undefined);
+ expect(configBefore).eq(undefined);
+ expect(configAfter.limit).eq(newLimitValue);
+ expect(configAfter.inFlight).eq(0);
+ expect(configAfter.lastUpdated).eq(currentTimestamp);
+ expect(configAfter.remaining).eq(newLimitValue);
+ }
+};
+
+export const removeInstantLimitConfigTest = async (
+ { vault, owner }: CommonParamsChangePaymentToken,
+ window: BigNumberish,
+ opt?: OptionalCommonParams,
+) => {
+ if (
+ await handleRevert(
+ vault
+ .connect(opt?.from ?? owner)
+ .removeInstantLimitConfig.bind(this, window),
vault,
- vault.interface.events['SetInstantDailyLimit(address,uint256)'].name,
+ opt,
)
- .withArgs((opt?.from ?? owner).address, newLimit).to.not.reverted;
+ ) {
+ return;
+ }
+
+ const limitConfigsBefore = await vault.getInstantLimitStatuses();
+ const indexBefore = limitConfigsBefore.findIndex((w) => w.window.eq(window));
+ expect(indexBefore).gte(
+ 0,
+ 'removeInstantLimitConfigTest: window must exist before removal',
+ );
+
+ await expect(
+ vault.connect(opt?.from ?? owner).removeInstantLimitConfig(window),
+ )
+ .to.emit(vault, 'WindowLimitRemoved')
+ .withArgs(window);
- const limit = await vault.instantDailyLimit();
- expect(limit).eq(newLimit);
+ const limitConfigsAfter = await vault.getInstantLimitStatuses();
+ expect(limitConfigsAfter.length).eq(limitConfigsBefore.length - 1);
+ expect(limitConfigsAfter.filter((w) => w.window.eq(window)).length).eq(0);
};
-export const setFeeReceiverTest = async (
+export const setMaxApproveRequestIdTest = async (
{ vault, owner }: CommonParamsChangePaymentToken,
- newReceiver: string,
+ maxApproveRequestId: BigNumberish,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
- vault.connect(opt?.from ?? owner).setFeeReceiver(newReceiver),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ vault
+ .connect(opt?.from ?? owner)
+ .setMaxApproveRequestId.bind(this, maxApproveRequestId),
+ vault,
+ opt,
+ )
+ ) {
return;
}
- await expect(vault.connect(opt?.from ?? owner).setFeeReceiver(newReceiver))
+ await expect(
+ vault
+ .connect(opt?.from ?? owner)
+ .setMaxApproveRequestId(maxApproveRequestId),
+ )
.to.emit(
vault,
- vault.interface.events['SetFeeReceiver(address,address)'].name,
+ vault.interface.events['SetMaxApproveRequestId(uint256)'].name,
+ )
+ .withArgs(maxApproveRequestId);
+
+ const newMaxApproveRequestId = await vault.maxApproveRequestId();
+ expect(newMaxApproveRequestId).eq(maxApproveRequestId);
+};
+
+export const setMaxInstantShareTest = async (
+ { vault, owner }: CommonParamsChangePaymentToken,
+ maxInstantShare: number,
+ opt?: OptionalCommonParams,
+) => {
+ if (
+ await handleRevert(
+ vault
+ .connect(opt?.from ?? owner)
+ .setMaxInstantShare.bind(this, maxInstantShare),
+ vault,
+ opt,
)
- .withArgs((opt?.from ?? owner).address, newReceiver).to.not.reverted;
+ ) {
+ return;
+ }
- const feeReceiver = await vault.feeReceiver();
- expect(feeReceiver).eq(newReceiver);
+ await expect(
+ vault.connect(opt?.from ?? owner).setMaxInstantShare(maxInstantShare),
+ ).to.emit(vault, vault.interface.events['SetMaxInstantShare(uint256)'].name);
+
+ const newMaxInstantShare = await vault.maxInstantShare();
+ expect(newMaxInstantShare).eq(maxInstantShare);
};
export const setTokensReceiverTest = async (
@@ -249,42 +455,56 @@ export const setTokensReceiverTest = async (
newReceiver: string,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
- vault.connect(opt?.from ?? owner).setTokensReceiver(newReceiver),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ vault
+ .connect(opt?.from ?? owner)
+ .setTokensReceiver.bind(this, newReceiver),
+ vault,
+ opt,
+ )
+ ) {
return;
}
await expect(vault.connect(opt?.from ?? owner).setTokensReceiver(newReceiver))
- .to.emit(
- vault,
- vault.interface.events['SetTokensReceiver(address,address)'].name,
- )
- .withArgs((opt?.from ?? owner).address, newReceiver).to.not.reverted;
+ .to.emit(vault, vault.interface.events['SetTokensReceiver(address)'].name)
+ .withArgs(newReceiver);
- const feeReceiver = await vault.tokensReceiver();
- expect(feeReceiver).eq(newReceiver);
+ const tokensReceiver = await vault.tokensReceiver();
+ expect(tokensReceiver).eq(newReceiver);
};
-export const addAccountWaivedFeeRestrictionTest = async (
+export const setSequentialRequestProcessingTest = async (
{ vault, owner }: CommonParamsChangePaymentToken,
- account: string,
+ value: boolean,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
- vault.connect(opt?.from ?? owner).addWaivedFeeAccount(account),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ vault
+ .connect(opt?.from ?? owner)
+ .setSequentialRequestProcessing.bind(this, value),
+ vault,
+ opt,
+ )
+ ) {
return;
}
- await expect(vault.connect(opt?.from ?? owner).addWaivedFeeAccount(account))
+ await expect(
+ vault.connect(opt?.from ?? owner).setSequentialRequestProcessing(value),
+ )
.to.emit(
vault,
- vault.interface.events['AddWaivedFeeAccount(address,address)'].name,
+ vault.interface.events['SetSequentialRequestProcessing(bool)'].name,
)
- .withArgs(account, (opt?.from ?? owner).address).to.not.reverted;
+ .withArgs(value);
+
+ const newSequentialRequestProcessing =
+ await vault.sequentialRequestProcessing();
+
+ expect(newSequentialRequestProcessing).eq(value);
};
export const setMinAmountToDepositTest = async (
@@ -294,12 +514,15 @@ export const setMinAmountToDepositTest = async (
) => {
const value = parseUnits(valueN.toString());
- if (opt?.revertMessage) {
- await expect(
+ if (
+ await handleRevert(
depositVault
.connect(opt?.from ?? owner)
- .setMinMTokenAmountForFirstDeposit(value),
- ).revertedWith(opt?.revertMessage);
+ .setMinMTokenAmountForFirstDeposit.bind(this, value),
+ depositVault,
+ opt,
+ )
+ ) {
return;
}
@@ -309,10 +532,9 @@ export const setMinAmountToDepositTest = async (
.setMinMTokenAmountForFirstDeposit(value),
).to.emit(
depositVault,
- depositVault.interface.events[
- 'SetMinMTokenAmountForFirstDeposit(address,uint256)'
- ].name,
- ).to.not.reverted;
+ depositVault.interface.events['SetMinMTokenAmountForFirstDeposit(uint256)']
+ .name,
+ );
const newMin = await depositVault.minMTokenAmountForFirstDeposit();
expect(newMin).eq(value);
@@ -325,17 +547,20 @@ export const setMinAmountTest = async (
) => {
const value = parseUnits(valueN.toString());
- if (opt?.revertMessage) {
- await expect(
- vault.connect(opt?.from ?? owner).setMinAmount(value),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ vault.connect(opt?.from ?? owner).setMinAmount.bind(this, value),
+ vault,
+ opt,
+ )
+ ) {
return;
}
await expect(vault.connect(opt?.from ?? owner).setMinAmount(value)).to.emit(
vault,
- vault.interface.events['SetMinAmount(address,uint256)'].name,
- ).to.not.reverted;
+ vault.interface.events['SetMinAmount(uint256)'].name,
+ );
const newMin = await vault.minAmount();
expect(newMin).eq(value);
@@ -352,12 +577,15 @@ export const addPaymentTokenTest = async (
) => {
token = (token as ERC20).address ?? (token as string);
- if (opt?.revertMessage) {
- await expect(
+ if (
+ await handleRevert(
vault
.connect(opt?.from ?? owner)
- .addPaymentToken(token, dataFeed, fee, allowance, isStable),
- ).revertedWith(opt?.revertMessage);
+ .addPaymentToken.bind(this, token, dataFeed, fee, allowance, isStable),
+ vault,
+ opt,
+ )
+ ) {
return;
}
@@ -368,9 +596,9 @@ export const addPaymentTokenTest = async (
).to.emit(
vault,
vault.interface.events[
- 'AddPaymentToken(address,address,address,uint256,uint256,bool)'
+ 'AddPaymentToken(address,address,uint256,uint256,bool)'
].name,
- ).to.not.reverted;
+ );
const paymentTokens = await vault.getPaymentTokens();
expect(paymentTokens.find((v) => v === token)).not.eq(undefined);
@@ -387,55 +615,55 @@ export const removePaymentTokenTest = async (
) => {
token = (token as ERC20).address ?? (token as string);
- if (opt?.revertMessage) {
- await expect(
- vault.connect(opt?.from ?? owner).removePaymentToken(token),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ vault.connect(opt?.from ?? owner).removePaymentToken.bind(this, token),
+ vault,
+ opt,
+ )
+ ) {
return;
}
await expect(
vault.connect(opt?.from ?? owner).removePaymentToken(token),
- ).to.emit(
- vault,
- vault.interface.events['RemovePaymentToken(address,address)'].name,
- ).to.not.reverted;
+ ).to.emit(vault, vault.interface.events['RemovePaymentToken(address)'].name);
const paymentTokens = await vault.getPaymentTokens();
expect(paymentTokens.find((v) => v === token)).eq(undefined);
};
export const withdrawTest = async (
- { vault, owner }: CommonParamsChangePaymentToken,
+ { vault, owner }: { vault: ManageableVault; owner: SignerWithAddress },
token: IERC20 | ERC20 | string,
amount: BigNumberish,
- withdrawTo: Account,
opt?: OptionalCommonParams,
) => {
- withdrawTo = getAccount(withdrawTo);
token = getAccount(token);
const tokenContract = ERC20__factory.connect(token, owner);
- if (opt?.revertMessage) {
- await expect(
- vault
- .connect(opt?.from ?? owner)
- .withdrawToken(token, amount, withdrawTo),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ vault.connect(opt?.from ?? owner).withdrawToken.bind(this, token, amount),
+ vault,
+ opt,
+ )
+ ) {
return;
}
+ const withdrawTo = await vault.tokensReceiver();
+
const balanceBeforeContract = await tokenContract.balanceOf(vault.address);
const balanceBeforeTo = await tokenContract.balanceOf(withdrawTo);
await expect(
- vault.connect(opt?.from ?? owner).withdrawToken(token, amount, withdrawTo),
+ vault.connect(opt?.from ?? owner).withdrawToken(token, amount),
).to.emit(
vault,
- vault.interface.events['WithdrawToken(address,address,address,uint256)']
- .name,
- ).to.not.reverted;
+ vault.interface.events['WithdrawToken(address,address,uint256)'].name,
+ );
const balanceAfterContract = await tokenContract.balanceOf(vault.address);
const balanceAfterTo = await tokenContract.balanceOf(withdrawTo);
@@ -443,29 +671,3 @@ export const withdrawTest = async (
expect(balanceAfterContract).eq(balanceBeforeContract.sub(amount));
expect(balanceAfterTo).eq(balanceBeforeTo.add(amount));
};
-
-export const setMinBuidlToRedeem = async (
- {
- vault,
- owner,
- }: { vault: RedemptionVaultWIthBUIDL; owner: SignerWithAddress },
- value: BigNumber,
- opt?: OptionalCommonParams,
-) => {
- if (opt?.revertMessage) {
- await expect(
- vault.connect(opt?.from ?? owner).setMinBuidlToRedeem(value),
- ).revertedWith(opt?.revertMessage);
- return;
- }
-
- await expect(
- vault.connect(opt?.from ?? owner).setMinBuidlToRedeem(value),
- ).to.emit(
- vault,
- vault.interface.events['SetMinBuidlToRedeem(uint256,address)'].name,
- ).to.not.reverted;
-
- const newMin = await vault.minBuidlToRedeem();
- expect(newMin).eq(value);
-};
diff --git a/test/common/misc/acre.helpers.ts b/test/common/misc/acre.helpers.ts
index 794c318f..89db7188 100644
--- a/test/common/misc/acre.helpers.ts
+++ b/test/common/misc/acre.helpers.ts
@@ -7,6 +7,7 @@ import {
AccountOrContract,
balanceOfBase18,
getAccount,
+ handleRevert,
OptionalCommonParams,
} from '../../common/common.helpers';
import { acreAdapterFixture } from '../../common/fixtures';
@@ -38,12 +39,11 @@ export const acreWrapperDepositTest = async (
const mTokenRate = await fixture.mTokenToUsdDataFeed.getDataInBase18();
- if (opt?.revertMessage) {
- await expect(
- fixture.acreUsdcMTbillAdapter
- .connect(from)
- .deposit(parseUnits(amountN.toString(), 8), receiverAddress),
- ).revertedWith(opt?.revertMessage);
+ const callFn = fixture.acreUsdcMTbillAdapter
+ .connect(from)
+ .deposit.bind(this, parseUnits(amountN.toString(), 8), receiverAddress);
+
+ if (await handleRevert(callFn, fixture.acreUsdcMTbillAdapter, opt)) {
return;
}
@@ -67,11 +67,7 @@ export const acreWrapperDepositTest = async (
.connect(from)
.callStatic.deposit(parseUnits(amountN.toString(), 8), receiverAddress);
- await expect(
- fixture.acreUsdcMTbillAdapter
- .connect(from)
- .deposit(parseUnits(amountN.toString(), 8), receiverAddress),
- )
+ await expect(callFn())
.emit(
fixture.acreUsdcMTbillAdapter,
fixture.acreUsdcMTbillAdapter.interface.events[
@@ -131,12 +127,11 @@ export const acreWrapperRequestRedeemTest = async (
const amountBase18 = parseUnits(amountN.toFixed(18).replace(/\.?0+$/, ''));
- if (opt?.revertMessage) {
- await expect(
- fixture.acreUsdcMTbillAdapter
- .connect(from)
- .requestRedeem(amountBase18, receiverAddress),
- ).revertedWith(opt?.revertMessage);
+ const callFn = fixture.acreUsdcMTbillAdapter
+ .connect(from)
+ .requestRedeem.bind(this, amountBase18, receiverAddress);
+
+ if (await handleRevert(callFn, fixture.acreUsdcMTbillAdapter, opt)) {
return;
}
@@ -159,11 +154,7 @@ export const acreWrapperRequestRedeemTest = async (
.callStatic.requestRedeem(amountBase18, receiverAddress);
const requestId = await fixture.redemptionVault.currentRequestId();
- await expect(
- fixture.acreUsdcMTbillAdapter
- .connect(from)
- .requestRedeem(amountBase18, receiverAddress),
- )
+ await expect(callFn())
.emit(
fixture.acreUsdcMTbillAdapter,
fixture.acreUsdcMTbillAdapter.interface.events[
@@ -199,7 +190,7 @@ export const acreWrapperRequestRedeemTest = async (
expect(requestIdReturned).eq(requestId);
expect(request.amountMToken).eq(amountBase18);
- expect(request.sender).eq(receiverAddress);
+ expect(request.recipient).eq(receiverAddress);
expect(balanceUserAfter).eq(balanceUserBefore);
expect(balanceContractAfter).eq(constants.Zero);
expect(balanceMTokenAfter).eq(balanceMTokenBefore.sub(amountBase18));
diff --git a/test/common/mtoken.helpers.ts b/test/common/mtoken.helpers.ts
new file mode 100644
index 00000000..41775a57
--- /dev/null
+++ b/test/common/mtoken.helpers.ts
@@ -0,0 +1,515 @@
+import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
+import { expect } from 'chai';
+import { BigNumberish, constants } from 'ethers';
+import { defaultAbiCoder, solidityKeccak256 } from 'ethers/lib/utils';
+
+import {
+ Account,
+ OptionalCommonParams,
+ getAccount,
+ getCurrentBlockTimestamp,
+ handleRevert,
+} from './common.helpers';
+import { calculateWindowRateLimitCapacity } from './manageable-vault.helpers';
+
+import { MToken } from '../../typechain-types';
+
+type CommonParams = {
+ tokenContract: MToken;
+ owner: SignerWithAddress;
+};
+
+export const setMetadataTest = async (
+ { tokenContract, owner }: CommonParams,
+ key: string,
+ value: string,
+ opt?: OptionalCommonParams,
+) => {
+ const keyBytes32 = solidityKeccak256(['string'], [key]);
+ const valueBytes = defaultAbiCoder.encode(['string'], [value]);
+
+ if (
+ await handleRevert(
+ tokenContract
+ .connect(opt?.from ?? owner)
+ .setMetadata.bind(this, keyBytes32, valueBytes),
+ tokenContract,
+ opt,
+ )
+ ) {
+ return;
+ }
+
+ await expect(
+ tokenContract
+ .connect(opt?.from ?? owner)
+ .setMetadata(keyBytes32, valueBytes),
+ )
+ .to.emit(
+ tokenContract,
+ tokenContract.interface.events['SetMetadata(bytes32,bytes)'].name,
+ )
+ .withArgs(keyBytes32, valueBytes);
+
+ expect(await tokenContract.metadata(keyBytes32)).eq(valueBytes);
+};
+
+export const setClawbackReceiverTest = async (
+ { tokenContract, owner }: CommonParams,
+ newReceiver: string,
+ opt?: OptionalCommonParams,
+) => {
+ const from = opt?.from ?? owner;
+
+ if (
+ await handleRevert(
+ tokenContract.connect(from).setClawbackReceiver.bind(this, newReceiver),
+ tokenContract,
+ opt,
+ )
+ ) {
+ return;
+ }
+
+ await expect(tokenContract.connect(from).setClawbackReceiver(newReceiver))
+ .to.emit(
+ tokenContract,
+ tokenContract.interface.events['ClawbackReceiverSet(address)'].name,
+ )
+ .withArgs(newReceiver);
+ expect(await tokenContract.clawbackReceiver()).eq(newReceiver);
+};
+
+export const setIsPermissionedTest = async (
+ { tokenContract, owner }: CommonParams,
+ isPermissioned: boolean,
+ opt?: OptionalCommonParams,
+) => {
+ const from = opt?.from ?? owner;
+ const before = await tokenContract.isPermissioned();
+
+ if (
+ await handleRevert(
+ tokenContract.connect(from).setIsPermissioned.bind(this, isPermissioned),
+ tokenContract,
+ opt,
+ )
+ ) {
+ return;
+ }
+
+ const assertion = expect(
+ tokenContract.connect(from).setIsPermissioned(isPermissioned),
+ );
+
+ if (before === isPermissioned) {
+ await assertion.to.not.emit(
+ tokenContract,
+ tokenContract.interface.events['SetIsPermissioned(bool)'].name,
+ );
+ } else {
+ await assertion.to
+ .emit(
+ tokenContract,
+ tokenContract.interface.events['SetIsPermissioned(bool)'].name,
+ )
+ .withArgs(isPermissioned);
+ }
+
+ expect(await tokenContract.isPermissioned()).eq(isPermissioned);
+};
+
+export const setMinHoldingBalanceEnforcedTest = async (
+ { tokenContract, owner }: CommonParams,
+ isMinHoldingBalanceEnforced: boolean,
+ opt?: OptionalCommonParams,
+) => {
+ const from = opt?.from ?? owner;
+ const before = await tokenContract.isMinHoldingBalanceEnforced();
+
+ if (
+ await handleRevert(
+ tokenContract
+ .connect(from)
+ .setMinHoldingBalanceEnforced.bind(this, isMinHoldingBalanceEnforced),
+ tokenContract,
+ opt,
+ )
+ ) {
+ return;
+ }
+
+ const assertion = expect(
+ tokenContract
+ .connect(from)
+ .setMinHoldingBalanceEnforced(isMinHoldingBalanceEnforced),
+ );
+
+ if (before === isMinHoldingBalanceEnforced) {
+ await assertion.to.not.emit(
+ tokenContract,
+ tokenContract.interface.events['SetIsMinHoldingBalanceEnforced(bool)']
+ .name,
+ );
+ } else {
+ await assertion.to
+ .emit(
+ tokenContract,
+ tokenContract.interface.events['SetIsMinHoldingBalanceEnforced(bool)']
+ .name,
+ )
+ .withArgs(isMinHoldingBalanceEnforced);
+ }
+
+ expect(await tokenContract.isMinHoldingBalanceEnforced()).eq(
+ isMinHoldingBalanceEnforced,
+ );
+};
+
+export const setMaxSupplyCapTest = async (
+ { tokenContract, owner }: CommonParams,
+ maxSupplyCap: BigNumberish,
+ opt?: OptionalCommonParams,
+) => {
+ const from = opt?.from ?? owner;
+
+ if (
+ await handleRevert(
+ tokenContract.connect(from).setMaxSupplyCap.bind(this, maxSupplyCap),
+ tokenContract,
+ opt,
+ )
+ ) {
+ return;
+ }
+
+ await expect(tokenContract.connect(from).setMaxSupplyCap(maxSupplyCap))
+ .to.emit(
+ tokenContract,
+ tokenContract.interface.events['SetMaxSupplyCap(uint256)'].name,
+ )
+ .withArgs(maxSupplyCap);
+
+ expect(await tokenContract.maxSupplyCap()).eq(maxSupplyCap);
+};
+
+export const clawbackTest = async (
+ { tokenContract, owner }: CommonParams,
+ amount: BigNumberish,
+ from: Account,
+ opt?: OptionalCommonParams,
+) => {
+ const fromAddr = getAccount(from);
+ const caller = opt?.from ?? owner;
+ const receiver = await tokenContract.clawbackReceiver();
+
+ if (
+ await handleRevert(
+ tokenContract.connect(caller).clawback.bind(this, amount, fromAddr),
+ tokenContract,
+ opt,
+ )
+ ) {
+ return;
+ }
+
+ const balanceFromBefore = await tokenContract.balanceOf(fromAddr);
+ const balanceReceiverBefore = await tokenContract.balanceOf(receiver);
+
+ await expect(tokenContract.connect(caller).clawback(amount, fromAddr))
+ .to.emit(
+ tokenContract,
+ tokenContract.interface.events['Transfer(address,address,uint256)'].name,
+ )
+ .withArgs(fromAddr, receiver, amount)
+ .and.to.emit(
+ tokenContract,
+ tokenContract.interface.events['Clawback(address,address,uint256)'].name,
+ )
+ .withArgs(fromAddr, receiver, amount);
+
+ expect(await tokenContract.balanceOf(fromAddr)).eq(
+ balanceFromBefore.sub(amount),
+ );
+ expect(await tokenContract.balanceOf(receiver)).eq(
+ balanceReceiverBefore.add(amount),
+ );
+};
+
+export const mint = async (
+ {
+ tokenContract,
+ owner,
+ isGoverned = false,
+ }: CommonParams & { isGoverned?: boolean },
+ to: Account,
+ amount: BigNumberish,
+ opt?: OptionalCommonParams,
+) => {
+ to = getAccount(to);
+
+ const caller = opt?.from ?? owner;
+
+ const callFn = isGoverned
+ ? tokenContract.connect(caller).mintGoverned.bind(this, to, amount)
+ : tokenContract.connect(caller).mint.bind(this, to, amount);
+
+ if (await handleRevert(callFn, tokenContract, opt)) {
+ return;
+ }
+
+ const balanceBefore = await tokenContract.balanceOf(to);
+
+ const rateLimitConfigsBefore = await tokenContract.getMintRateLimitStatuses();
+
+ const timetsampBefore = await getCurrentBlockTimestamp();
+
+ await expect(callFn()).to.emit(
+ tokenContract,
+ tokenContract.interface.events['Transfer(address,address,uint256)'].name,
+ );
+
+ const rateLimitConfigsAfter = await tokenContract.getMintRateLimitStatuses();
+ const timestampAfter = await getCurrentBlockTimestamp();
+
+ const expectedLimitsAfter = await Promise.all(
+ rateLimitConfigsBefore.map(async (limit) => {
+ const { remaining, inFlight } = calculateWindowRateLimitCapacity({
+ amountInFlight: limit.inFlight,
+ lastUpdated: timetsampBefore,
+ limit: limit.limit,
+ window: limit.window,
+ now: timestampAfter,
+ });
+
+ return {
+ ...limit,
+ remaining: remaining.gte(amount)
+ ? remaining.sub(amount)
+ : constants.Zero,
+ inFlight: inFlight.add(amount),
+ };
+ }),
+ );
+
+ const currentTimestamp = await getCurrentBlockTimestamp();
+
+ for (const [index, limit] of rateLimitConfigsBefore.entries()) {
+ expect(rateLimitConfigsAfter[index].inFlight).eq(
+ expectedLimitsAfter[index].inFlight,
+ );
+ expect(rateLimitConfigsAfter[index].remaining).eq(
+ expectedLimitsAfter[index].remaining,
+ );
+ expect(rateLimitConfigsAfter[index].lastUpdated).eq(currentTimestamp);
+ expect(rateLimitConfigsAfter[index].window).eq(limit.window);
+ expect(rateLimitConfigsAfter[index].limit).eq(limit.limit);
+ }
+
+ const balanceAfter = await tokenContract.balanceOf(to);
+
+ expect(balanceAfter.sub(balanceBefore)).eq(amount);
+};
+
+export const burn = async (
+ {
+ tokenContract,
+ owner,
+ isGoverned = false,
+ }: CommonParams & { isGoverned?: boolean },
+ from: Account,
+ amount: BigNumberish,
+ opt?: OptionalCommonParams,
+) => {
+ from = getAccount(from);
+
+ const caller = opt?.from ?? owner;
+
+ const callFn = isGoverned
+ ? tokenContract.connect(caller).burnGoverned.bind(this, from, amount)
+ : tokenContract.connect(caller).burn.bind(this, from, amount);
+
+ if (await handleRevert(callFn, tokenContract, opt)) {
+ return;
+ }
+
+ const balanceBefore = await tokenContract.balanceOf(from);
+
+ const rateLimitConfigsBefore = await tokenContract.getMintRateLimitStatuses();
+
+ await expect(callFn()).to.emit(
+ tokenContract,
+ tokenContract.interface.events['Transfer(address,address,uint256)'].name,
+ );
+
+ const rateLimitConfigsAfter = await tokenContract.getMintRateLimitStatuses();
+
+ for (const [index, limit] of rateLimitConfigsBefore.entries()) {
+ expect(rateLimitConfigsAfter[index].limit).eq(limit.limit);
+ expect(rateLimitConfigsAfter[index].inFlight).gte(limit.inFlight);
+ expect(rateLimitConfigsAfter[index].remaining).lte(limit.remaining);
+ expect(rateLimitConfigsAfter[index].lastUpdated).eq(limit.lastUpdated);
+ expect(rateLimitConfigsAfter[index].window).eq(limit.window);
+ }
+
+ const balanceAfter = await tokenContract.balanceOf(from);
+
+ expect(balanceBefore.sub(balanceAfter)).eq(amount);
+};
+
+export const increaseMintRateLimitTest = async (
+ { tokenContract, owner }: CommonParams,
+ window: number,
+ newLimit: BigNumberish,
+ opt?: OptionalCommonParams,
+) => {
+ if (
+ await handleRevert(
+ tokenContract
+ .connect(opt?.from ?? owner)
+ .increaseMintRateLimit.bind(this, window, newLimit),
+ tokenContract,
+ opt,
+ )
+ ) {
+ return;
+ }
+
+ const rateLimitConfigsBefore = await tokenContract.getMintRateLimitStatuses();
+
+ await expect(
+ tokenContract.connect(owner).increaseMintRateLimit(window, newLimit),
+ )
+ .to.emit(tokenContract, 'WindowLimitSet')
+ .withArgs(window, newLimit);
+ const currentTimestamp = await getCurrentBlockTimestamp();
+ const rateLimitConfigsAfter = await tokenContract.getMintRateLimitStatuses();
+
+ const configBefore = rateLimitConfigsBefore.filter((limit) =>
+ limit.window.eq(window),
+ )?.[0];
+ const configAfter = rateLimitConfigsAfter.filter((limit) =>
+ limit.window.eq(window),
+ )?.[0];
+
+ if (configBefore) {
+ const { inFlight, remaining } = calculateWindowRateLimitCapacity({
+ amountInFlight: configBefore.inFlight,
+ lastUpdated: configBefore.lastUpdated,
+ limit: newLimit,
+ window,
+ now: currentTimestamp,
+ });
+
+ expect(configAfter).not.eq(undefined);
+ expect(configBefore).not.eq(undefined);
+ expect(configAfter.limit).eq(newLimit);
+ expect(configAfter.lastUpdated).eq(currentTimestamp);
+ expect(configAfter.inFlight).eq(inFlight);
+ expect(configAfter.remaining).eq(remaining);
+ } else {
+ expect(configAfter).not.eq(undefined);
+ expect(configBefore).eq(undefined);
+ expect(configAfter.limit).eq(newLimit);
+ expect(configAfter.inFlight).eq(0);
+ expect(configAfter.lastUpdated).eq(currentTimestamp);
+ expect(configAfter.remaining).eq(newLimit);
+ }
+};
+
+export const removeMintRateLimitTest = async (
+ { tokenContract, owner }: CommonParams,
+ window: number,
+ opt?: OptionalCommonParams,
+) => {
+ if (
+ await handleRevert(
+ tokenContract
+ .connect(opt?.from ?? owner)
+ .removeMintRateLimitConfig.bind(this, window),
+ tokenContract,
+ opt,
+ )
+ ) {
+ return;
+ }
+
+ const rateLimitConfigsBefore = await tokenContract.getMintRateLimitStatuses();
+
+ await expect(tokenContract.connect(owner).removeMintRateLimitConfig(window))
+ .to.emit(tokenContract, 'WindowLimitRemoved')
+ .withArgs(window);
+ const rateLimitConfigsAfter = await tokenContract.getMintRateLimitStatuses();
+
+ const configBefore = rateLimitConfigsBefore.filter((limit) =>
+ limit.window.eq(window),
+ )?.[0];
+ const configAfter = rateLimitConfigsAfter.filter((limit) =>
+ limit.window.eq(window),
+ )?.[0];
+
+ expect(configBefore).not.eq(undefined);
+ expect(configAfter).eq(undefined);
+};
+
+export const decreaseMintRateLimitTest = async (
+ { tokenContract, owner }: CommonParams,
+ window: number,
+ newLimit: BigNumberish,
+ opt?: OptionalCommonParams,
+) => {
+ if (
+ await handleRevert(
+ tokenContract
+ .connect(opt?.from ?? owner)
+ .decreaseMintRateLimit.bind(this, window, newLimit),
+ tokenContract,
+ opt,
+ )
+ ) {
+ return;
+ }
+
+ const rateLimitConfigsBefore = await tokenContract.getMintRateLimitStatuses();
+
+ await expect(
+ tokenContract.connect(owner).decreaseMintRateLimit(window, newLimit),
+ )
+ .to.emit(tokenContract, 'WindowLimitSet')
+ .withArgs(window, newLimit);
+
+ const currentTimestamp = await getCurrentBlockTimestamp();
+ const rateLimitConfigsAfter = await tokenContract.getMintRateLimitStatuses();
+
+ const configBefore = rateLimitConfigsBefore.filter((limit) =>
+ limit.window.eq(window),
+ )?.[0];
+
+ const configAfter = rateLimitConfigsAfter.filter((limit) =>
+ limit.window.eq(window),
+ )?.[0];
+
+ if (configBefore) {
+ const { inFlight, remaining } = calculateWindowRateLimitCapacity({
+ amountInFlight: configBefore.inFlight,
+ lastUpdated: configBefore.lastUpdated,
+ limit: newLimit,
+ window,
+ now: currentTimestamp,
+ });
+
+ expect(configAfter).not.eq(undefined);
+ expect(configBefore).not.eq(undefined);
+ expect(configAfter.limit).eq(newLimit);
+ expect(configAfter.lastUpdated).eq(currentTimestamp);
+ expect(configAfter.inFlight).eq(inFlight);
+ expect(configAfter.remaining).eq(remaining);
+ } else {
+ expect(configAfter).not.eq(undefined);
+ expect(configBefore).eq(undefined);
+ expect(configAfter.limit).eq(newLimit);
+ expect(configAfter.inFlight).eq(0);
+ expect(configAfter.lastUpdated).eq(currentTimestamp);
+ expect(configAfter.remaining).eq(newLimit);
+ }
+};
diff --git a/test/common/post-deploy.helpers.ts b/test/common/post-deploy.helpers.ts
deleted file mode 100644
index a29fee16..00000000
--- a/test/common/post-deploy.helpers.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
-import { expect } from 'chai';
-import { BigNumberish } from 'ethers';
-import { HardhatRuntimeEnvironment } from 'hardhat/types';
-
-import { keccak256 } from './common.helpers';
-
-import { getAllRoles } from '../../helpers/roles';
-import {
- AggregatorV3Interface,
- DataFeed,
- DepositVault,
- MidasAccessControl,
- RedemptionVault,
- MTBILL,
-} from '../../typechain-types';
-
-type Params = {
- accessControl: MidasAccessControl;
- mTBILL: MTBILL;
- dataFeed: DataFeed;
- dataFeedMToken: DataFeed;
- aggregator: AggregatorV3Interface;
- depositVault: DepositVault;
- aggregatorMToken: AggregatorV3Interface;
- redemptionVault: RedemptionVault;
- owner: SignerWithAddress;
- tokensReceiver: string;
- minMTokenAmountForFirstDeposit: BigNumberish;
- minAmount: BigNumberish;
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- execute?: (role: string, address: string) => Promise;
-};
-
-export const postDeploymentTest = async (
- { ethers }: HardhatRuntimeEnvironment,
- {
- accessControl,
- depositVault,
- redemptionVault,
- mTBILL,
- dataFeedMToken,
- aggregatorMToken,
- owner,
- tokensReceiver,
- minMTokenAmountForFirstDeposit = '0',
- minAmount,
- }: Params,
-) => {
- const roles = getAllRoles();
-
- /** mTBILL tests start */
- expect(await mTBILL.name()).eq('Midas US Treasury Bill Token');
- expect(await mTBILL.symbol()).eq('mTBILL');
- expect(await mTBILL.paused()).eq(false);
-
- /** mTBILL tests end */
-
- /** DataFeed tests start */
-
- expect(await dataFeedMToken.aggregator()).eq(aggregatorMToken.address);
-
- /** DataFeed tests end */
-
- /** DepositVault tests start */
-
- expect(await depositVault.mToken()).eq(mTBILL.address);
-
- expect(await depositVault.tokensReceiver()).eq(tokensReceiver);
-
- expect(await depositVault.ONE_HUNDRED_PERCENT()).eq('10000');
-
- expect(await depositVault.minMTokenAmountForFirstDeposit()).eq(
- minMTokenAmountForFirstDeposit,
- );
- expect(await depositVault.minAmount()).eq(minAmount);
-
- expect(await depositVault.vaultRole()).eq(
- keccak256('DEPOSIT_VAULT_ADMIN_ROLE'),
- );
-
- expect(await depositVault.MANUAL_FULLFILMENT_TOKEN()).eq(
- ethers.constants.AddressZero,
- );
-
- /** DepositVault tests end */
-
- /** RedemptionVault tests start */
-
- expect(await redemptionVault.mToken()).eq(mTBILL.address);
-
- expect(await redemptionVault.tokensReceiver()).eq(tokensReceiver);
-
- expect(await redemptionVault.ONE_HUNDRED_PERCENT()).eq('10000');
-
- expect(await redemptionVault.vaultRole()).eq(
- keccak256('REDEMPTION_VAULT_ADMIN_ROLE'),
- );
-
- expect(await redemptionVault.MANUAL_FULLFILMENT_TOKEN()).eq(
- ethers.constants.AddressZero,
- );
-
- /** RedemptionVault tests end */
-
- /** Owners roles tests start */
-
- const { blacklisted: _, greenlisted: __, ...rolesToCheck } = roles.common;
-
- for (const role of Object.values(rolesToCheck)) {
- expect(await accessControl.hasRole(role, owner.address)).to.eq(true);
- }
-
- expect(await accessControl.getRoleAdmin(roles.common.blacklisted)).eq(
- roles.common.blacklistedOperator,
- );
-
- expect(await accessControl.getRoleAdmin(roles.common.greenlisted)).eq(
- roles.common.greenlistedOperator,
- );
-};
diff --git a/test/common/redemption-vault-aave.helpers.ts b/test/common/redemption-vault-aave.helpers.ts
index c09bdc2f..dc708fd9 100644
--- a/test/common/redemption-vault-aave.helpers.ts
+++ b/test/common/redemption-vault-aave.helpers.ts
@@ -1,15 +1,21 @@
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { expect } from 'chai';
import { BigNumber, BigNumberish } from 'ethers';
+import { parseUnits } from 'ethers/lib/utils';
-import { AccountOrContract, OptionalCommonParams } from './common.helpers';
+import {
+ AccountOrContract,
+ handleRevert,
+ OptionalCommonParams,
+ shouldRevert,
+} from './common.helpers';
import { redeemInstantTest } from './redemption-vault.helpers';
import {
IERC20,
RedemptionVaultWithAave,
- MTBILLTest,
DataFeedTest,
+ MToken,
} from '../../typechain-types';
type CommonParamsSetAavePool = {
@@ -20,7 +26,7 @@ type CommonParamsSetAavePool = {
type RedemptionWithAaveParams = {
redemptionVault: RedemptionVaultWithAave;
owner: SignerWithAddress;
- mTBILL: MTBILLTest;
+ mTBILL: MToken;
mTokenToUsdDataFeed: DataFeedTest;
usdc: IERC20;
aToken: IERC20;
@@ -37,10 +43,15 @@ export const setAavePoolTest = async (
pool: string,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
- redemptionVault.connect(opt?.from ?? owner).setAavePool(token, pool),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ redemptionVault
+ .connect(opt?.from ?? owner)
+ .setAavePool.bind(this, token, pool),
+ redemptionVault,
+ opt,
+ )
+ ) {
return;
}
@@ -48,8 +59,7 @@ export const setAavePoolTest = async (
redemptionVault.connect(opt?.from ?? owner).setAavePool(token, pool),
).to.emit(
redemptionVault,
- redemptionVault.interface.events['SetAavePool(address,address,address)']
- .name,
+ redemptionVault.interface.events['SetAavePool(address,address)'].name,
).to.not.reverted;
const poolAfter = await redemptionVault.aavePools(token);
@@ -61,10 +71,15 @@ export const removeAavePoolTest = async (
token: string,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
- redemptionVault.connect(opt?.from ?? owner).removeAavePool(token),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ redemptionVault
+ .connect(opt?.from ?? owner)
+ .removeAavePool.bind(this, token),
+ redemptionVault,
+ opt,
+ )
+ ) {
return;
}
@@ -72,7 +87,7 @@ export const removeAavePoolTest = async (
redemptionVault.connect(opt?.from ?? owner).removeAavePool(token),
).to.emit(
redemptionVault,
- redemptionVault.interface.events['RemoveAavePool(address,address)'].name,
+ redemptionVault.interface.events['RemoveAavePool(address)'].name,
).to.not.reverted;
const poolAfter = await redemptionVault.aavePools(token);
@@ -96,7 +111,7 @@ export const redeemInstantWithAaveTest = async (
customRecipient,
} = params;
- if (opt?.revertMessage) {
+ if (shouldRevert(opt)) {
await redeemInstantTest(
{
redemptionVault,
@@ -131,6 +146,13 @@ export const redeemInstantWithAaveTest = async (
waivedFee: params.waivedFee,
minAmount: params.minAmount,
customRecipient,
+ // aTokens redeem 1:1 for the underlying, so the vault's aToken balance is
+ // extra tokenOut liquidity available on top of its direct tokenOut balance.
+ additionalLiquidity: async () =>
+ aToken.balanceOf(redemptionVault.address),
+ // Real aTokens rebase, accruing a few wei of interest across the redeem
+ // block; tolerate that so the balance assertion isn't yield-flaky.
+ vaultBalanceTolerance: parseUnits('0.01', 6),
},
usdc,
amountTBillIn,
diff --git a/test/common/redemption-vault-morpho.helpers.ts b/test/common/redemption-vault-morpho.helpers.ts
index a065347e..53e89963 100644
--- a/test/common/redemption-vault-morpho.helpers.ts
+++ b/test/common/redemption-vault-morpho.helpers.ts
@@ -1,15 +1,22 @@
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { expect } from 'chai';
import { BigNumber, BigNumberish } from 'ethers';
+import { parseUnits } from 'ethers/lib/utils';
+import { ethers } from 'hardhat';
-import { AccountOrContract, OptionalCommonParams } from './common.helpers';
+import {
+ AccountOrContract,
+ handleRevert,
+ OptionalCommonParams,
+ shouldRevert,
+} from './common.helpers';
import { redeemInstantTest } from './redemption-vault.helpers';
import {
IERC20,
RedemptionVaultWithMorpho,
- MTBILLTest,
DataFeedTest,
+ MToken,
} from '../../typechain-types';
type CommonParamsSetMorphoVault = {
@@ -20,7 +27,7 @@ type CommonParamsSetMorphoVault = {
type RedemptionWithMorphoParams = {
redemptionVault: RedemptionVaultWithMorpho;
owner: SignerWithAddress;
- mTBILL: MTBILLTest;
+ mTBILL: MToken;
mTokenToUsdDataFeed: DataFeedTest;
usdc: IERC20;
morphoVault: IERC20;
@@ -37,10 +44,15 @@ export const setMorphoVaultTest = async (
vault: string,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
- redemptionVault.connect(opt?.from ?? owner).setMorphoVault(token, vault),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ redemptionVault
+ .connect(opt?.from ?? owner)
+ .setMorphoVault.bind(this, token, vault),
+ redemptionVault,
+ opt,
+ )
+ ) {
return;
}
@@ -48,8 +60,7 @@ export const setMorphoVaultTest = async (
redemptionVault.connect(opt?.from ?? owner).setMorphoVault(token, vault),
).to.emit(
redemptionVault,
- redemptionVault.interface.events['SetMorphoVault(address,address,address)']
- .name,
+ redemptionVault.interface.events['SetMorphoVault(address,address)'].name,
).to.not.reverted;
const vaultAfter = await redemptionVault.morphoVaults(token);
@@ -61,10 +72,15 @@ export const removeMorphoVaultTest = async (
token: string,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
- redemptionVault.connect(opt?.from ?? owner).removeMorphoVault(token),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ redemptionVault
+ .connect(opt?.from ?? owner)
+ .removeMorphoVault.bind(this, token),
+ redemptionVault,
+ opt,
+ )
+ ) {
return;
}
@@ -72,7 +88,7 @@ export const removeMorphoVaultTest = async (
redemptionVault.connect(opt?.from ?? owner).removeMorphoVault(token),
).to.emit(
redemptionVault,
- redemptionVault.interface.events['RemoveMorphoVault(address,address)'].name,
+ redemptionVault.interface.events['RemoveMorphoVault(address)'].name,
).to.not.reverted;
const vaultAfter = await redemptionVault.morphoVaults(token);
@@ -96,7 +112,7 @@ export const redeemInstantWithMorphoTest = async (
customRecipient,
} = params;
- if (opt?.revertMessage) {
+ if (shouldRevert(opt)) {
await redeemInstantTest(
{
redemptionVault,
@@ -122,6 +138,11 @@ export const redeemInstantWithMorphoTest = async (
usdc.balanceOf(sender.address),
]);
+ const morphoVaultErc4626 = await ethers.getContractAt(
+ 'IMorphoVault',
+ morphoVault.address,
+ );
+
await redeemInstantTest(
{
redemptionVault,
@@ -131,6 +152,14 @@ export const redeemInstantWithMorphoTest = async (
waivedFee: params.waivedFee,
minAmount: params.minAmount,
customRecipient,
+ // The vault's Morpho shares are extra tokenOut liquidity: value them in
+ // tokenOut units via the ERC-4626 preview.
+ additionalLiquidity: async () =>
+ morphoVaultErc4626.previewRedeem(
+ await morphoVault.balanceOf(redemptionVault.address),
+ ),
+ // Share price accrues across the redeem block; tolerate the drift.
+ vaultBalanceTolerance: parseUnits('0.01', 6),
},
usdc,
amountTBillIn,
diff --git a/test/common/redemption-vault-mtoken.helpers.ts b/test/common/redemption-vault-mtoken.helpers.ts
index cfe08def..397d98e6 100644
--- a/test/common/redemption-vault-mtoken.helpers.ts
+++ b/test/common/redemption-vault-mtoken.helpers.ts
@@ -7,12 +7,11 @@ import {
AccountOrContract,
OptionalCommonParams,
getAccount,
+ handleRevert,
+ shouldRevert,
} from './common.helpers';
import { defaultDeploy } from './fixtures';
-import {
- calcExpectedTokenOutAmount,
- redeemInstantTest,
-} from './redemption-vault.helpers';
+import { redeemInstantTest } from './redemption-vault.helpers';
import {
ERC20,
@@ -23,11 +22,11 @@ import {
type CommonParamsRedeem = Pick<
Awaited>,
| 'owner'
+ | 'mTokenLoan'
| 'mTBILL'
- | 'mFONE'
| 'redemptionVaultWithMToken'
+ | 'mTokenLoanToUsdDataFeed'
| 'mTokenToUsdDataFeed'
- | 'mFoneToUsdDataFeed'
>;
type CommonParamsSetVault = {
@@ -39,18 +38,20 @@ export const redeemInstantWithMTokenTest = async (
{
redemptionVaultWithMToken,
owner,
+ mTokenLoan,
mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
+ mTokenToUsdDataFeed,
useMTokenSleeve,
minAmount,
waivedFee,
customRecipient,
+ additionalLiquidity,
}: CommonParamsRedeem & {
useMTokenSleeve?: boolean;
waivedFee?: boolean;
minAmount?: BigNumberish;
customRecipient?: AccountOrContract;
+ additionalLiquidity?: () => Promise;
},
tokenOut: ERC20 | string,
amountMFoneIn: number,
@@ -64,16 +65,17 @@ export const redeemInstantWithMTokenTest = async (
const amountIn = parseUnits(amountMFoneIn.toString());
- if (opt?.revertMessage) {
+ if (shouldRevert(opt)) {
await redeemInstantTest(
{
redemptionVault: redemptionVaultWithMToken,
owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
+ mTBILL,
+ mTokenToUsdDataFeed,
waivedFee,
minAmount,
customRecipient,
+ additionalLiquidity,
},
tokenOut,
amountMFoneIn,
@@ -83,56 +85,46 @@ export const redeemInstantWithMTokenTest = async (
return;
}
- const balanceBeforeUserMFone = await mFONE.balanceOf(sender.address);
- const balanceBeforeVaultMTbill = await mTBILL.balanceOf(
+ const balanceBeforeUserMFone = await mTBILL.balanceOf(sender.address);
+ const balanceBeforeVaultMTbill = await mTokenLoan.balanceOf(
redemptionVaultWithMToken.address,
);
- const supplyBeforeMFone = await mFONE.totalSupply();
- const supplyBeforeMTbill = await mTBILL.totalSupply();
-
- const mFoneRate = await mFoneToUsdDataFeed.getDataInBase18();
-
- const { amountInWithoutFee } = await calcExpectedTokenOutAmount(
- sender,
- tokenContract,
- redemptionVaultWithMToken,
- mFoneRate,
- amountIn,
- true,
- );
+ const supplyBeforeMFone = await mTBILL.totalSupply();
+ const supplyBeforeMTbill = await mTokenLoan.totalSupply();
await redeemInstantTest(
{
redemptionVault: redemptionVaultWithMToken,
owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
+ mTBILL,
+ mTokenToUsdDataFeed,
waivedFee,
minAmount,
customRecipient,
+ additionalLiquidity,
},
tokenOut,
amountMFoneIn,
opt,
);
- const balanceAfterUserMFone = await mFONE.balanceOf(sender.address);
- const balanceAfterVaultMTbill = await mTBILL.balanceOf(
+ const balanceAfterUserMFone = await mTBILL.balanceOf(sender.address);
+ const balanceAfterVaultMTbill = await mTokenLoan.balanceOf(
redemptionVaultWithMToken.address,
);
- const supplyAfterMFone = await mFONE.totalSupply();
- const supplyAfterMTbill = await mTBILL.totalSupply();
+ const supplyAfterMFone = await mTBILL.totalSupply();
+ const supplyAfterMTbill = await mTokenLoan.totalSupply();
- // mFONE is always burned from user
+ // mTBILL is always burned from user
expect(balanceAfterUserMFone).eq(balanceBeforeUserMFone.sub(amountIn));
- expect(supplyAfterMFone).eq(supplyBeforeMFone.sub(amountInWithoutFee));
+ expect(supplyAfterMFone).eq(supplyBeforeMFone.sub(amountIn));
if (useMTokenSleeve) {
- // mTBILL was redeemed from the vault's holdings
+ // mTokenLoan was redeemed from the vault's holdings
expect(balanceAfterVaultMTbill).lt(balanceBeforeVaultMTbill);
expect(supplyAfterMTbill).lt(supplyBeforeMTbill);
} else {
- // Vault had enough tokenOut, mTBILL untouched
+ // Vault had enough tokenOut, mTokenLoan untouched
expect(balanceAfterVaultMTbill).eq(balanceBeforeVaultMTbill);
expect(supplyAfterMTbill).eq(supplyBeforeMTbill);
}
@@ -143,18 +135,18 @@ export const setRedemptionVaultTest = async (
newVault: string,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
- vault.connect(opt?.from ?? owner).setRedemptionVault(newVault),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ vault.connect(opt?.from ?? owner).setRedemptionVault.bind(this, newVault),
+ vault,
+ opt,
+ )
+ ) {
return;
}
await expect(vault.connect(opt?.from ?? owner).setRedemptionVault(newVault))
- .to.emit(
- vault,
- vault.interface.events['SetRedemptionVault(address,address)'].name,
- )
+ .to.emit(vault, vault.interface.events['SetRedemptionVault(address)'].name)
.withArgs((opt?.from ?? owner).address, newVault).to.not.reverted;
const provider = await vault.redemptionVault();
diff --git a/test/common/redemption-vault-swapper.helpers.ts b/test/common/redemption-vault-swapper.helpers.ts
deleted file mode 100644
index d749c379..00000000
--- a/test/common/redemption-vault-swapper.helpers.ts
+++ /dev/null
@@ -1,232 +0,0 @@
-import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
-import { expect } from 'chai';
-import { BigNumberish } from 'ethers';
-import { parseUnits } from 'ethers/lib/utils';
-
-import {
- AccountOrContract,
- OptionalCommonParams,
- getAccount,
-} from './common.helpers';
-import { defaultDeploy } from './fixtures';
-import {
- calcExpectedTokenOutAmount,
- redeemInstantTest,
-} from './redemption-vault.helpers';
-
-import {
- ERC20,
- ERC20__factory,
- RedemptionVaultWithSwapper,
-} from '../../typechain-types';
-
-type CommonParamsRedeem = Pick<
- Awaited>,
- | 'owner'
- | 'mTBILL'
- | 'mBASIS'
- | 'redemptionVaultWithSwapper'
- | 'mTokenToUsdDataFeed'
- | 'mBasisToUsdDataFeed'
->;
-type CommonParamsProvider = {
- vault: RedemptionVaultWithSwapper;
- owner: SignerWithAddress;
-};
-
-export const redeemInstantWithSwapperTest = async (
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mBASIS,
- mBasisToUsdDataFeed,
- mTokenToUsdDataFeed,
- swap,
- minAmount,
- waivedFee,
- customRecipient,
- }: CommonParamsRedeem & {
- swap?: boolean;
- waivedFee?: boolean;
- minAmount?: BigNumberish;
- customRecipient?: AccountOrContract;
- },
- tokenOut: ERC20 | string,
- amountTBillIn: number,
- opt?: OptionalCommonParams,
-) => {
- tokenOut = getAccount(tokenOut);
-
- const tokenContract = ERC20__factory.connect(tokenOut, owner);
-
- const sender = opt?.from ?? owner;
-
- const amountIn = parseUnits(amountTBillIn.toString());
- const tokensReceiver = await redemptionVaultWithSwapper.tokensReceiver();
- const feeReceiver = await redemptionVaultWithSwapper.feeReceiver();
- const liquidityProvider =
- await redemptionVaultWithSwapper.liquidityProvider();
-
- if (opt?.revertMessage) {
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithSwapper,
- owner,
- mTBILL: mBASIS,
- mTokenToUsdDataFeed: mBasisToUsdDataFeed,
- waivedFee,
- minAmount,
- customRecipient,
- checkSupply: !swap,
- },
- tokenOut,
- amountTBillIn,
- opt,
- );
-
- return;
- }
-
- const balanceBeforeUserMTBILL = await mTBILL.balanceOf(sender.address);
- const balanceBeforeUserMBASIS = await mBASIS.balanceOf(sender.address);
-
- const balanceBeforeContractMTBILL = await mTBILL.balanceOf(
- redemptionVaultWithSwapper.address,
- );
- const balanceBeforeContractMBASIS = await mBASIS.balanceOf(
- redemptionVaultWithSwapper.address,
- );
-
- const balanceBeforeProviderMTBILL = await mTBILL.balanceOf(liquidityProvider);
- const balanceBeforeProviderMBASIS = await mBASIS.balanceOf(liquidityProvider);
-
- const balanceBeforeReceiverMTBILL = await mTBILL.balanceOf(tokensReceiver);
-
- const balanceBeforeFeeReceiverMTBILL = await mTBILL.balanceOf(feeReceiver);
-
- const supplyBeforeMTBILL = await mTBILL.totalSupply();
- const supplyBeforeMBASIS = await mBASIS.totalSupply();
-
- const mBasisRate = await mBasisToUsdDataFeed.getDataInBase18();
- const mTokenRate = await mTokenToUsdDataFeed.getDataInBase18();
-
- const { amountInWithoutFee } = await calcExpectedTokenOutAmount(
- sender,
- tokenContract,
- redemptionVaultWithSwapper,
- mBasisRate,
- amountIn,
- true,
- );
-
- const expectedMToken = amountInWithoutFee.mul(mBasisRate).div(mTokenRate);
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithSwapper,
- owner,
- mTBILL: mBASIS,
- mTokenToUsdDataFeed: mBasisToUsdDataFeed,
- waivedFee,
- minAmount,
- customRecipient,
- checkSupply: !swap,
- },
- tokenOut,
- amountTBillIn,
- opt,
- );
-
- const balanceAfterUserMTBILL = await mTBILL.balanceOf(sender.address);
- const balanceAfterUserMBASIS = await mBASIS.balanceOf(sender.address);
-
- const balanceAfterContractMTBILL = await mTBILL.balanceOf(
- redemptionVaultWithSwapper.address,
- );
- const balanceAfterContractMBASIS = await mBASIS.balanceOf(
- redemptionVaultWithSwapper.address,
- );
-
- const balanceAfterProviderMTBILL = await mTBILL.balanceOf(liquidityProvider);
- const balanceAfterProviderMBASIS = await mBASIS.balanceOf(liquidityProvider);
-
- const balanceAfterReceiverMTBILL = await mTBILL.balanceOf(tokensReceiver);
-
- const balanceAfterFeeReceiverMTBILL = await mTBILL.balanceOf(feeReceiver);
-
- const supplyAfterMTBILL = await mTBILL.totalSupply();
- const supplyAfterMBASIS = await mBASIS.totalSupply();
-
- expect(balanceAfterUserMBASIS).eq(balanceBeforeUserMBASIS.sub(amountIn));
- expect(balanceAfterUserMTBILL).eq(balanceBeforeUserMTBILL);
-
- expect(balanceAfterReceiverMTBILL).eq(balanceBeforeReceiverMTBILL);
-
- expect(balanceAfterContractMTBILL).eq(balanceBeforeContractMTBILL);
- expect(balanceAfterContractMBASIS).eq(balanceBeforeContractMBASIS);
-
- expect(balanceAfterFeeReceiverMTBILL).eq(balanceBeforeFeeReceiverMTBILL);
-
- if (swap) {
- expect(supplyAfterMTBILL).eq(supplyBeforeMTBILL.sub(expectedMToken));
- expect(balanceAfterProviderMBASIS).eq(
- balanceBeforeProviderMBASIS.add(amountInWithoutFee),
- );
- expect(balanceAfterProviderMTBILL).eq(
- balanceBeforeProviderMTBILL.sub(expectedMToken),
- );
- } else {
- expect(supplyAfterMBASIS).eq(supplyBeforeMBASIS.sub(amountInWithoutFee));
- expect(balanceAfterProviderMBASIS).eq(balanceBeforeProviderMBASIS);
- expect(balanceAfterProviderMTBILL).eq(balanceBeforeProviderMTBILL);
- }
-};
-
-export const setLiquidityProviderTest = async (
- { vault, owner }: CommonParamsProvider,
- newProvider: string,
- opt?: OptionalCommonParams,
-) => {
- if (opt?.revertMessage) {
- await expect(
- vault.connect(opt?.from ?? owner).setLiquidityProvider(newProvider),
- ).revertedWith(opt?.revertMessage);
- return;
- }
-
- await expect(
- vault.connect(opt?.from ?? owner).setLiquidityProvider(newProvider),
- )
- .to.emit(
- vault,
- vault.interface.events['SetLiquidityProvider(address,address)'].name,
- )
- .withArgs((opt?.from ?? owner).address, newProvider).to.not.reverted;
-
- const provider = await vault.liquidityProvider();
- expect(provider).eq(newProvider);
-};
-
-export const setSwapperVaultTest = async (
- { vault, owner }: CommonParamsProvider,
- newVault: string,
- opt?: OptionalCommonParams,
-) => {
- if (opt?.revertMessage) {
- await expect(
- vault.connect(opt?.from ?? owner).setSwapperVault(newVault),
- ).revertedWith(opt?.revertMessage);
- return;
- }
-
- await expect(vault.connect(opt?.from ?? owner).setSwapperVault(newVault))
- .to.emit(
- vault,
- vault.interface.events['SetSwapperVault(address,address)'].name,
- )
- .withArgs((opt?.from ?? owner).address, newVault).to.not.reverted;
-
- const provider = await vault.mTbillRedemptionVault();
- expect(provider).eq(newVault);
-};
diff --git a/test/common/redemption-vault-ustb.helpers.ts b/test/common/redemption-vault-ustb.helpers.ts
index c58325e6..e12cbd51 100644
--- a/test/common/redemption-vault-ustb.helpers.ts
+++ b/test/common/redemption-vault-ustb.helpers.ts
@@ -1,21 +1,27 @@
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { expect } from 'chai';
import { BigNumber, BigNumberish } from 'ethers';
+import { parseUnits } from 'ethers/lib/utils';
+import { ethers } from 'hardhat';
-import { AccountOrContract, OptionalCommonParams } from './common.helpers';
+import {
+ AccountOrContract,
+ OptionalCommonParams,
+ shouldRevert,
+} from './common.helpers';
import { redeemInstantTest } from './redemption-vault.helpers';
import {
IERC20,
RedemptionVaultWithUSTB,
- MTBILLTest,
DataFeedTest,
+ MToken,
} from '../../typechain-types';
type RedemptionWithUSTBParams = {
redemptionVault: RedemptionVaultWithUSTB;
owner: SignerWithAddress;
- mTBILL: MTBILLTest;
+ mTBILL: MToken;
mTokenToUsdDataFeed: DataFeedTest;
usdc: IERC20;
ustbToken: IERC20;
@@ -43,7 +49,7 @@ export const redeemInstantWithUstbTest = async (
customRecipient,
} = params;
- if (opt?.revertMessage) {
+ if (shouldRevert(opt)) {
await redeemInstantTest(
{
redemptionVault,
@@ -68,6 +74,26 @@ export const redeemInstantWithUstbTest = async (
usdc.balanceOf(sender.address),
]);
+ const ustbRedemption = await ethers.getContractAt(
+ 'IUSTBRedemption',
+ await redemptionVault.ustbRedemption(),
+ );
+ // Value the vault's USTB in tokenOut (USDC) units using the redemption
+ // contract's own linear price, probed via calculateUstbIn. A large probe
+ // keeps the price ratio precise enough that valuing a whole USTB position
+ // doesn't accumulate meaningful rounding.
+ const usdcProbe = parseUnits('10000000', 6);
+ const valueUstbInUsdc = async () => {
+ const ustbBalance = await ustbToken.balanceOf(redemptionVault.address);
+ if (ustbBalance.isZero()) {
+ return BigNumber.from(0);
+ }
+ const [ustbInPerUsdcProbe] = await ustbRedemption.calculateUstbIn(
+ usdcProbe,
+ );
+ return ustbBalance.mul(usdcProbe).div(ustbInPerUsdcProbe);
+ };
+
await redeemInstantTest(
{
redemptionVault,
@@ -77,6 +103,10 @@ export const redeemInstantWithUstbTest = async (
waivedFee: params.waivedFee,
minAmount: params.minAmount,
customRecipient,
+ // The vault's USTB is extra tokenOut liquidity redeemable for USDC.
+ additionalLiquidity: valueUstbInUsdc,
+ // Absorb price-conversion rounding across the redeem block.
+ vaultBalanceTolerance: parseUnits('0.01', 6),
},
usdc,
amountTBillIn,
diff --git a/test/common/redemption-vault.helpers.ts b/test/common/redemption-vault.helpers.ts
index 083d8aa1..69522753 100644
--- a/test/common/redemption-vault.helpers.ts
+++ b/test/common/redemption-vault.helpers.ts
@@ -1,57 +1,87 @@
+import { setNextBlockTimestamp } from '@nomicfoundation/hardhat-network-helpers/dist/src/helpers/time';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { expect } from 'chai';
-import { BigNumber, BigNumberish, constants } from 'ethers';
-import { parseUnits } from 'ethers/lib/utils';
+import {
+ BigNumber,
+ BigNumberish,
+ constants,
+ ContractTransaction,
+} from 'ethers';
+import { formatUnits, parseUnits } from 'ethers/lib/utils';
+import { ethers } from 'hardhat';
import {
AccountOrContract,
OptionalCommonParams,
balanceOfBase18,
getAccount,
+ getCurrentBlockTimestamp,
+ handleRevert,
} from './common.helpers';
import { defaultDeploy } from './fixtures';
+import { calculateWindowRateLimitCapacity } from './manageable-vault.helpers';
import {
DataFeedTest__factory,
ERC20,
ERC20__factory,
IERC20,
- MTBILL,
MToken,
RedemptionVault,
- RedemptionVaultWIthBUIDL,
RedemptionVaultWithAave,
RedemptionVaultWithMorpho,
RedemptionVaultWithMToken,
- RedemptionVaultWithSwapper,
RedemptionVaultWithUSTB,
+ RedemptionVaultTest__factory,
} from '../../typechain-types';
+type RedemptionVaultType =
+ | RedemptionVault
+ | RedemptionVaultWithAave
+ | RedemptionVaultWithMorpho
+ | RedemptionVaultWithMToken
+ | RedemptionVaultWithUSTB;
+
type CommonParamsRedeem = {
- mTBILL: MToken | MTBILL;
+ mTBILL: MToken;
} & Pick<
Awaited>,
'owner' | 'mTokenToUsdDataFeed'
> & {
- redemptionVault:
- | RedemptionVault
- | RedemptionVaultWIthBUIDL
- | RedemptionVaultWithAave
- | RedemptionVaultWithMorpho
- | RedemptionVaultWithMToken
- | RedemptionVaultWithUSTB
- | RedemptionVaultWithSwapper;
+ redemptionVault: RedemptionVaultType;
};
type CommonParams = Pick>, 'owner'> & {
- redemptionVault:
- | RedemptionVault
- | RedemptionVaultWIthBUIDL
- | RedemptionVaultWithAave
- | RedemptionVaultWithMorpho
- | RedemptionVaultWithMToken
- | RedemptionVaultWithUSTB
- | RedemptionVaultWithSwapper;
+ redemptionVault: RedemptionVaultType;
+};
+
+const getTotalFromInstantShare = (
+ amountIn: BigNumber,
+ instantShare?: BigNumberish,
+) => {
+ if (instantShare === undefined) {
+ return amountIn;
+ }
+
+ if (BigNumber.from(instantShare).eq(constants.Zero)) {
+ return BigNumber.from(0);
+ }
+
+ return amountIn.mul(100_00).div(instantShare);
+};
+
+const expectEqWithOneWeiTolerance = (
+ actual: BigNumber,
+ expected: BigNumber,
+ field: string,
+) => {
+ const diff = actual.gte(expected)
+ ? actual.sub(expected)
+ : expected.sub(actual);
+ expect(
+ diff.lte(1),
+ `${field} differs by more than 1 wei: actual=${actual.toString()} expected=${expected.toString()}`,
+ ).eq(true);
};
export const redeemInstantTest = async (
@@ -65,12 +95,27 @@ export const redeemInstantTest = async (
customRecipient,
checkSupply = true,
expectedAmountOut,
+ additionalLiquidity,
+ vaultBalanceTolerance,
+ loanLiquidityExpectToFail,
+ holdback,
}: CommonParamsRedeem & {
waivedFee?: boolean;
minAmount?: BigNumberish;
customRecipient?: AccountOrContract;
checkSupply?: boolean;
expectedAmountOut?: BigNumberish;
+ additionalLiquidity?: () => Promise;
+ // Allowed absolute drift on the vault tokenOut balance assertion. Needed
+ // when `additionalLiquidity` points at a live yield-bearing source (e.g. a
+ // rebasing aToken on a mainnet fork) that accrues a few wei of interest
+ // across the redeem block. Defaults to exact equality.
+ vaultBalanceTolerance?: BigNumberish;
+ loanLiquidityExpectToFail?: boolean;
+ holdback?: {
+ callFunction: () => Promise;
+ instantShare: BigNumberish;
+ };
},
tokenOut: IERC20 | ERC20 | string,
amountTBillIn: number,
@@ -80,118 +125,237 @@ export const redeemInstantTest = async (
const tokenContract = ERC20__factory.connect(tokenOut, owner);
+ const loanSwapperVault = await redemptionVault.loanSwapperVault();
+ const loanSwapperVaultMToken =
+ loanSwapperVault !== constants.AddressZero
+ ? ERC20__factory.connect(
+ await RedemptionVaultTest__factory.connect(
+ loanSwapperVault,
+ owner,
+ ).mToken(),
+ owner,
+ )
+ : undefined;
+
const sender = opt?.from ?? owner;
const amountIn = parseUnits(amountTBillIn.toString());
const tokensReceiver = await redemptionVault.tokensReceiver();
- const feeReceiver = await redemptionVault.feeReceiver();
const withRecipient = customRecipient !== undefined;
const recipient = customRecipient
? getAccount(customRecipient)
: sender.address;
- const callFn = withRecipient
- ? redemptionVault
- .connect(sender)
- ['redeemInstant(address,uint256,uint256,address)'].bind(
- this,
- tokenOut,
- amountIn,
- minAmount ?? constants.Zero,
- recipient,
- )
- : redemptionVault
- .connect(sender)
- ['redeemInstant(address,uint256,uint256)'].bind(
- this,
- tokenOut,
- amountIn,
- minAmount ?? constants.Zero,
- );
-
- if (opt?.revertMessage) {
- await expect(callFn()).revertedWith(opt?.revertMessage);
+ const callFn =
+ holdback?.callFunction ??
+ (withRecipient
+ ? redemptionVault
+ .connect(sender)
+ ['redeemInstant(address,uint256,uint256,address)'].bind(
+ this,
+ tokenOut,
+ amountIn,
+ minAmount ?? constants.Zero,
+ recipient,
+ )
+ : redemptionVault
+ .connect(sender)
+ ['redeemInstant(address,uint256,uint256)'].bind(
+ this,
+ tokenOut,
+ amountIn,
+ minAmount ?? constants.Zero,
+ ));
+
+ if (await handleRevert(callFn, redemptionVault, opt)) {
return;
}
const balanceBeforeUser = await mTBILL.balanceOf(sender.address);
const balanceBeforeReceiver = await mTBILL.balanceOf(tokensReceiver);
- const balanceBeforeFeeReceiver = await mTBILL.balanceOf(feeReceiver);
-
+ const balanceBeforeLoanLp = loanSwapperVaultMToken
+ ? await loanSwapperVaultMToken.balanceOf(await redemptionVault.loanLp())
+ : constants.Zero;
+
+ const supplyBeforeLoanLp = loanSwapperVaultMToken
+ ? await loanSwapperVaultMToken.totalSupply()
+ : constants.Zero;
+ const balanceBeforeVault = (
+ await tokenContract.balanceOf(redemptionVault.address)
+ ).add((await additionalLiquidity?.()) ?? constants.Zero);
const balanceBeforeTokenOutRecipient = await tokenContract.balanceOf(
recipient,
);
const balanceBeforeTokenOut = await tokenContract.balanceOf(sender.address);
const supplyBefore = await mTBILL.totalSupply();
-
+ const lastLoanRequestIdBefore = await redemptionVault.currentLoanRequestId();
const mTokenRate = await mTokenToUsdDataFeed.getDataInBase18();
- const { fee, amountOut, amountInWithoutFee } =
- await calcExpectedTokenOutAmount(
- sender,
- tokenContract,
- redemptionVault,
- mTokenRate,
- amountIn,
- true,
- );
+ const {
+ fee,
+ amountOut,
+ amountOutWithoutFee,
+ amountOutWithoutFeeBase18,
+ feeBase18,
+ tokenOutRate,
+ } = await calcExpectedTokenOutAmount(
+ sender,
+ tokenContract,
+ redemptionVault,
+ mTokenRate,
+ amountIn,
+ true,
+ );
- await expect(callFn())
+ const {
+ toTransferFromVault,
+ toTransferFromLpBase18,
+ toTransferFromLpMToken,
+ lpFeePortionBase18,
+ vaultFeePortion,
+ } = await estimateSendTokensFromLiquidity(
+ redemptionVault,
+ tokenContract,
+ amountOutWithoutFeeBase18!,
+ feeBase18!,
+ tokenOutRate,
+ {
+ additionalLiquidity: await additionalLiquidity?.(),
+ loanLiquidityExpectToFail,
+ },
+ );
+
+ const instantLimitsBefore = await redemptionVault.getInstantLimitStatuses();
+ const timestampBefore = await getCurrentBlockTimestamp();
+
+ const callPromise = callFn();
+ await expect(callPromise)
.to.emit(
redemptionVault,
redemptionVault.interface.events[
- withRecipient
- ? 'RedeemInstantWithCustomRecipient(address,address,address,uint256,uint256,uint256)'
- : 'RedeemInstant(address,address,uint256,uint256,uint256)'
+ 'RedeemInstant(address,address,address,uint256,uint256,uint256,uint256,uint256)'
].name,
)
.withArgs(
- ...[
- sender,
- tokenOut,
- withRecipient ? recipient : undefined,
- amountTBillIn,
- fee,
- amountOut,
- ].filter((v) => v !== undefined),
+ sender,
+ tokenOut,
+ recipient,
+ amountTBillIn,
+ feeBase18,
+ amountOutWithoutFeeBase18,
+ mTokenRate,
+ tokenOutRate,
).to.not.reverted;
+ const instantLimitsAfter = await redemptionVault.getInstantLimitStatuses();
+ const timestampAfter = await getCurrentBlockTimestamp();
+
+ const expectedLimitsAfter = await Promise.all(
+ instantLimitsBefore.map(async (limit) => {
+ const { remaining, inFlight } = calculateWindowRateLimitCapacity({
+ amountInFlight: limit.inFlight,
+ lastUpdated: timestampBefore,
+ limit: limit.limit,
+ window: limit.window,
+ now: timestampAfter,
+ });
+
+ return {
+ ...limit,
+ remaining: remaining.gte(amountIn)
+ ? remaining.sub(amountIn)
+ : constants.Zero,
+ inFlight: inFlight.add(amountIn),
+ };
+ }),
+ );
+
const balanceAfterUser = await mTBILL.balanceOf(sender.address);
const balanceAfterReceiver = await mTBILL.balanceOf(tokensReceiver);
- const balanceAfterFeeReceiver = await mTBILL.balanceOf(feeReceiver);
+ const balanceAfterLoanLp = loanSwapperVaultMToken
+ ? await loanSwapperVaultMToken.balanceOf(await redemptionVault.loanLp())
+ : constants.Zero;
+ const supplyAfterLoanLp = loanSwapperVaultMToken
+ ? await loanSwapperVaultMToken.totalSupply()
+ : constants.Zero;
const balanceAfterTokenOutRecipient = await tokenContract.balanceOf(
recipient,
);
+ const balanceAfterVault = (
+ await tokenContract.balanceOf(redemptionVault.address)
+ ).add((await additionalLiquidity?.()) ?? constants.Zero);
const balanceAfterTokenOut = await tokenContract.balanceOf(sender.address);
const supplyAfter = await mTBILL.totalSupply();
+ const lastLoanRequestIdAfter = await redemptionVault.currentLoanRequestId();
if (checkSupply) {
- expect(supplyAfter).eq(supplyBefore.sub(amountInWithoutFee));
+ expect(supplyAfter).eq(supplyBefore.sub(amountIn));
}
- expect(balanceAfterReceiver).eq(
- balanceBeforeReceiver.add(
- tokensReceiver === feeReceiver ? fee : constants.Zero,
+ expect(balanceAfterReceiver).eq(balanceBeforeReceiver);
+ expect(balanceAfterUser).eq(
+ balanceBeforeUser.sub(
+ getTotalFromInstantShare(amountIn, holdback?.instantShare),
),
);
- expect(balanceAfterFeeReceiver).eq(balanceBeforeFeeReceiver.add(fee));
-
- expect(balanceAfterUser).eq(balanceBeforeUser.sub(amountIn));
-
- const expectedAmountToReceive = expectedAmountOut ?? amountOut;
+ const expectedBalanceAfterVault = balanceBeforeVault.sub(toTransferFromVault);
+ if (vaultBalanceTolerance !== undefined) {
+ expect(balanceAfterVault).closeTo(
+ expectedBalanceAfterVault,
+ vaultBalanceTolerance,
+ );
+ } else {
+ expect(balanceAfterVault).eq(expectedBalanceAfterVault);
+ }
+ const expectedAmountToReceive = expectedAmountOut ?? amountOutWithoutFee!;
expect(balanceAfterTokenOutRecipient).eq(
balanceBeforeTokenOutRecipient.add(expectedAmountToReceive),
);
if (recipient !== sender.address) {
expect(balanceAfterTokenOut).eq(balanceBeforeTokenOut);
}
- if (waivedFee) {
- expect(balanceAfterFeeReceiver).eq(balanceBeforeFeeReceiver);
+
+ if (toTransferFromLpBase18.gt(0)) {
+ expect(balanceAfterLoanLp).eq(
+ balanceBeforeLoanLp.sub(toTransferFromLpMToken),
+ );
+ expect(supplyAfterLoanLp).eq(
+ supplyBeforeLoanLp.sub(toTransferFromLpMToken),
+ );
+
+ const loanRequest = await redemptionVault.loanRequests(
+ lastLoanRequestIdAfter.sub(1),
+ );
+ expect(loanRequest.amountTokenOut).eq(toTransferFromLpBase18);
+ expect(loanRequest.amountFee).eq(lpFeePortionBase18);
+ expect(loanRequest.status).eq(0);
+ expect(loanRequest.tokenOut).eq(tokenOut);
+ expect(loanRequest.createdAt).eq(await getCurrentBlockTimestamp());
+ } else {
+ expect(lastLoanRequestIdAfter).eq(lastLoanRequestIdBefore);
+ }
+
+ for (const [index, limit] of instantLimitsBefore.entries()) {
+ expectEqWithOneWeiTolerance(
+ instantLimitsAfter[index].inFlight,
+ expectedLimitsAfter[index].inFlight,
+ `instantLimits[${index}].inFlight`,
+ );
+ expectEqWithOneWeiTolerance(
+ instantLimitsAfter[index].remaining,
+ expectedLimitsAfter[index].remaining,
+ `instantLimits[${index}].remaining`,
+ );
+ expect(instantLimitsAfter[index].lastUpdated).eq(timestampAfter);
+ expect(instantLimitsAfter[index].window).eq(limit.window);
+ expect(instantLimitsAfter[index].limit).eq(limit.limit);
}
+
+ return callPromise;
};
export const redeemRequestTest = async (
@@ -202,9 +366,15 @@ export const redeemRequestTest = async (
mTokenToUsdDataFeed,
waivedFee,
customRecipient,
+ customRecipientInstant,
+ instantShare,
+ minReceiveAmountInstantShare,
}: CommonParamsRedeem & {
waivedFee?: boolean;
customRecipient?: AccountOrContract;
+ instantShare?: BigNumberish;
+ minReceiveAmountInstantShare?: BigNumberish;
+ customRecipientInstant?: AccountOrContract;
},
tokenOut: ERC20 | string,
amountTBillIn: number,
@@ -218,35 +388,50 @@ export const redeemRequestTest = async (
const amountIn = parseUnits(amountTBillIn.toString());
const tokensReceiver = await redemptionVault.tokensReceiver();
- const feeReceiver = await redemptionVault.feeReceiver();
const withRecipient = customRecipient !== undefined;
- const recipient = customRecipient
+
+ const recipientRequest = customRecipient
? getAccount(customRecipient)
: sender.address;
+ const recipientInstant =
+ customRecipientInstant || customRecipient
+ ? getAccount(customRecipientInstant ?? customRecipient!)
+ : sender.address;
- const callFn = withRecipient
- ? redemptionVault
- .connect(sender)
- ['redeemRequest(address,uint256,address)'].bind(
- this,
- tokenOut,
- amountIn,
- recipient,
- )
- : redemptionVault
- .connect(sender)
- ['redeemRequest(address,uint256)'].bind(this, tokenOut, amountIn);
+ const callFn =
+ instantShare !== undefined || withRecipient
+ ? redemptionVault
+ .connect(sender)
+ [
+ 'redeemRequest(address,uint256,address,uint256,uint256,address)'
+ ].bind(
+ this,
+ tokenOut,
+ amountIn,
+ recipientRequest,
+ instantShare ?? constants.Zero,
+ minReceiveAmountInstantShare ?? constants.Zero,
+ recipientInstant,
+ )
+ : redemptionVault
+ .connect(sender)
+ ['redeemRequest(address,uint256)'].bind(this, tokenOut, amountIn);
- if (opt?.revertMessage) {
- await expect(callFn()).revertedWith(opt?.revertMessage);
+ if (await handleRevert(callFn, redemptionVault, opt)) {
return {};
}
const balanceBeforeUser = await mTBILL.balanceOf(sender.address);
const balanceBeforeContract = await mTBILL.balanceOf(redemptionVault.address);
const balanceBeforeReceiver = await mTBILL.balanceOf(tokensReceiver);
- const balanceBeforeFeeReceiver = await mTBILL.balanceOf(feeReceiver);
+ const balanceBeforeRequestRedeemer = await mTBILL.balanceOf(
+ await redemptionVault.requestRedeemer(),
+ );
+ const balanceBeforeContractPToken = await balanceOfBase18(
+ tokenContract,
+ redemptionVault.address,
+ );
const balanceBeforeTokenOut = await tokenContract.balanceOf(sender.address);
@@ -255,7 +440,7 @@ export const redeemRequestTest = async (
const latestRequestIdBefore = await redemptionVault.currentRequestId();
const mTokenRate = await mTokenToUsdDataFeed.getDataInBase18();
- const { fee, currentStableRate, amountInWithoutFee } =
+ const { currentStableRate, tokenOutRate, feePercent } =
await calcExpectedTokenOutAmount(
sender,
tokenContract,
@@ -265,346 +450,511 @@ export const redeemRequestTest = async (
false,
);
- await expect(callFn())
+ const amountMTokenInInstant = amountIn
+ .mul(instantShare ?? constants.Zero)
+ .div(100_00);
+ const amountMTokenInRequest = amountIn.sub(amountMTokenInInstant);
+
+ const nextExpectedRequestIdToProcessBefore =
+ await redemptionVault.nextExpectedRequestIdToProcess();
+
+ let callPromise: Awaited>;
+
+ if (amountMTokenInInstant.gt(0)) {
+ callPromise = await redeemInstantTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ waivedFee,
+ minAmount: minReceiveAmountInstantShare ?? constants.Zero,
+ customRecipient: recipientInstant,
+ holdback: {
+ callFunction: callFn,
+ instantShare: instantShare ?? constants.Zero,
+ },
+ },
+ tokenOut,
+ +formatUnits(amountMTokenInInstant, 18),
+ { from: sender },
+ );
+ }
+
+ await expect(callPromise ?? callFn())
.to.emit(
redemptionVault,
redemptionVault.interface.events[
- withRecipient
- ? 'RedeemRequestWithCustomRecipient(uint256,address,address,address,uint256,uint256)'
- : 'RedeemRequest(uint256,address,address,uint256,uint256)'
+ 'RedeemRequest(uint256,address,address,address,uint256,uint256,uint256,uint256,uint256)'
].name,
)
.withArgs(
- ...[
- latestRequestIdBefore.add(1),
- sender,
- tokenOut,
- withRecipient ? recipient : undefined,
- amountTBillIn,
- fee,
- ].filter((v) => v !== undefined),
- ).to.not.reverted;
+ latestRequestIdBefore,
+ sender.address,
+ tokenOut,
+ recipientRequest,
+ amountMTokenInRequest,
+ amountMTokenInInstant,
+ feePercent,
+ mTokenRate,
+ tokenOutRate,
+ );
const latestRequestIdAfter = await redemptionVault.currentRequestId();
const request = await redemptionVault.redeemRequests(latestRequestIdBefore);
-
- expect(request.sender).eq(recipient);
+ const nextExpectedRequestIdToProcessAfter =
+ await redemptionVault.nextExpectedRequestIdToProcess();
+ expect(request.recipient).eq(recipientRequest);
expect(request.tokenOut).eq(tokenOut);
- expect(request.amountMToken).eq(amountInWithoutFee);
+ expect(request.amountMToken).eq(amountMTokenInRequest);
expect(request.mTokenRate).eq(mTokenRate);
expect(request.tokenOutRate).eq(currentStableRate);
+ expect(nextExpectedRequestIdToProcessAfter).eq(
+ nextExpectedRequestIdToProcessBefore,
+ );
+
+ if (waivedFee) {
+ expect(request.feePercent).eq(feePercent).eq(constants.Zero);
+ } else {
+ expect(request.feePercent).eq(feePercent);
+ }
const balanceAfterUser = await mTBILL.balanceOf(sender.address);
const balanceAfterReceiver = await mTBILL.balanceOf(tokensReceiver);
- const balanceAfterFeeReceiver = await mTBILL.balanceOf(feeReceiver);
const balanceAfterContract = await mTBILL.balanceOf(redemptionVault.address);
+ const balanceAfterRequestRedeemer = await mTBILL.balanceOf(
+ await redemptionVault.requestRedeemer(),
+ );
+ const balanceAfterContractPToken = await balanceOfBase18(
+ tokenContract,
+ redemptionVault.address,
+ );
const balanceAfterTokenOut = await tokenContract.balanceOf(sender.address);
const supplyAfter = await mTBILL.totalSupply();
- expect(supplyAfter).eq(supplyBefore);
expect(latestRequestIdAfter).eq(latestRequestIdBefore.add(1));
expect(balanceAfterUser).eq(balanceBeforeUser.sub(amountIn));
- expect(balanceAfterContract).eq(
- balanceBeforeContract.add(amountInWithoutFee),
- );
+ expect(balanceAfterContract).eq(balanceBeforeContract);
expect(balanceAfterReceiver).eq(balanceBeforeReceiver);
- expect(balanceAfterFeeReceiver).eq(balanceBeforeFeeReceiver.add(fee));
- expect(balanceAfterTokenOut).eq(balanceBeforeTokenOut);
- if (waivedFee) {
- expect(balanceAfterFeeReceiver).eq(balanceBeforeFeeReceiver);
+
+ // those checks is already made in redeemInstantTest
+ if (amountMTokenInInstant.eq(0)) {
+ expect(supplyAfter).eq(supplyBefore);
+ expect(balanceAfterTokenOut).eq(balanceBeforeTokenOut);
+ expect(balanceAfterContractPToken).eq(balanceBeforeContractPToken);
}
+ expect(balanceAfterRequestRedeemer).eq(
+ balanceBeforeRequestRedeemer.add(amountMTokenInRequest),
+ );
+
return {
requestId: latestRequestIdBefore,
rate: mTokenRate,
};
};
-export const redeemFiatRequestTest = async (
+export const approveRedeemRequestTest = async (
{
redemptionVault,
owner,
mTBILL,
- mTokenToUsdDataFeed,
waivedFee,
- }: CommonParamsRedeem & { waivedFee?: boolean },
- amountTBillIn: number,
+ isAvgRate,
+ }: CommonParamsRedeem & {
+ waivedFee?: boolean;
+ isAvgRate?: boolean;
+ },
+ requestId: BigNumberish,
+ rate: BigNumber,
opt?: OptionalCommonParams,
) => {
const sender = opt?.from ?? owner;
- const amountIn = parseUnits(amountTBillIn.toString());
const tokensReceiver = await redemptionVault.tokensReceiver();
- const feeReceiver = await redemptionVault.feeReceiver();
- if (opt?.revertMessage) {
- await expect(
- redemptionVault.connect(sender).redeemFiatRequest(amountIn),
- ).revertedWith(opt?.revertMessage);
+ const callFn = redemptionVault
+ .connect(sender)
+ .approveRequest.bind(this, requestId, rate, isAvgRate ?? false);
+
+ if (await handleRevert(callFn, redemptionVault, opt)) {
return;
}
+ const requestDataBefore = await redemptionVault.redeemRequests(requestId);
+
+ let actualRate = !isAvgRate
+ ? rate
+ : BigNumber.from(
+ expectedHoldbackPartRateFromAvg(
+ requestDataBefore.amountMToken,
+ requestDataBefore.amountMTokenInstant,
+ requestDataBefore.mTokenRate,
+ rate,
+ ),
+ );
+
+ if (actualRate.eq(0)) {
+ actualRate = rate;
+ }
+
+ const tokenContract = ERC20__factory.connect(
+ requestDataBefore.tokenOut,
+ owner,
+ );
+
const balanceBeforeUser = await mTBILL.balanceOf(sender.address);
const balanceBeforeContract = await mTBILL.balanceOf(redemptionVault.address);
const balanceBeforeReceiver = await mTBILL.balanceOf(tokensReceiver);
- const balanceBeforeFeeReceiver = await mTBILL.balanceOf(feeReceiver);
-
- const supplyBefore = await mTBILL.totalSupply();
+ const balanceBeforeRequestRedeemerMToken = await mTBILL.balanceOf(
+ await redemptionVault.requestRedeemer(),
+ );
+ const balanceBeforeRequestRedeemerPToken = await balanceOfBase18(
+ requestDataBefore.tokenOut,
+ await redemptionVault.requestRedeemer(),
+ );
- const latestRequestIdBefore = await redemptionVault.currentRequestId();
- const manualToken = await redemptionVault.MANUAL_FULLFILMENT_TOKEN();
- const fiatAdditionalFee = await redemptionVault.fiatAdditionalFee();
- const hundredPercent = await redemptionVault.ONE_HUNDRED_PERCENT();
- const flatFee = await redemptionVault.fiatFlatFee();
+ const balanceBeforeVaultPToken = await balanceOfBase18(
+ requestDataBefore.tokenOut,
+ redemptionVault.address,
+ );
- const mTokenRate = await mTokenToUsdDataFeed.getDataInBase18();
+ const supplyBefore = await mTBILL.totalSupply();
- const feePercent = await getFeePercent(
+ const balanceUserTokenOutBefore = await balanceOfBase18(
+ tokenContract,
sender.address,
- manualToken,
- redemptionVault,
- false,
- fiatAdditionalFee,
);
- const fee = amountIn
- .mul(feePercent)
- .div(hundredPercent)
- .add(waivedFee ? 0 : flatFee);
- const amountInWithoutFee = amountIn.sub(fee);
- await expect(redemptionVault.connect(sender).redeemFiatRequest(amountIn))
+ const { amountOutWithoutFeeBase18, feeBase18 } =
+ await calcExpectedTokenOutAmount(
+ sender,
+ tokenContract,
+ redemptionVault,
+ actualRate,
+ requestDataBefore.amountMToken,
+ false,
+ );
+
+ const nextExpectedRequestIdToProcessBefore =
+ await redemptionVault.nextExpectedRequestIdToProcess();
+
+ await expect(callFn())
.to.emit(
redemptionVault,
redemptionVault.interface.events[
- 'RedeemRequest(uint256,address,address,uint256,uint256)'
+ 'ApproveRequest(uint256,uint256,bool,bool)'
].name,
)
- .withArgs(
- latestRequestIdBefore.add(1),
- sender,
- manualToken,
- amountTBillIn,
- fee,
- ).to.not.reverted;
+ .withArgs(requestId, actualRate, false, isAvgRate).to.not.reverted;
- const latestRequestIdAfter = await redemptionVault.currentRequestId();
- const request = await redemptionVault.redeemRequests(latestRequestIdBefore);
+ const nextExpectedRequestIdToProcessAfter =
+ await redemptionVault.nextExpectedRequestIdToProcess();
- expect(request.sender).eq(sender.address);
- expect(request.tokenOut).eq(manualToken);
- expect(request.amountMToken).eq(amountInWithoutFee);
- expect(request.mTokenRate).eq(mTokenRate);
- expect(request.tokenOutRate).eq(parseUnits('1'));
+ const requestDataAfter = await redemptionVault.redeemRequests(requestId);
+
+ expect(requestDataAfter.approvedMTokenRate).eq(actualRate);
+ expect(requestDataAfter.mTokenRate).eq(requestDataBefore.mTokenRate);
+
+ if (nextExpectedRequestIdToProcessBefore.lte(requestId)) {
+ expect(nextExpectedRequestIdToProcessAfter).eq(
+ BigNumber.from(requestId).add(1),
+ );
+ } else {
+ expect(nextExpectedRequestIdToProcessAfter).eq(
+ nextExpectedRequestIdToProcessBefore,
+ );
+ }
const balanceAfterUser = await mTBILL.balanceOf(sender.address);
const balanceAfterReceiver = await mTBILL.balanceOf(tokensReceiver);
- const balanceAfterFeeReceiver = await mTBILL.balanceOf(feeReceiver);
+ const balanceAfterRequestRedeemerMToken = await mTBILL.balanceOf(
+ await redemptionVault.requestRedeemer(),
+ );
+ const balanceAfterRequestRedeemerPToken = await balanceOfBase18(
+ requestDataBefore.tokenOut,
+ await redemptionVault.requestRedeemer(),
+ );
+
+ const balanceAfterVaultPToken = await balanceOfBase18(
+ requestDataBefore.tokenOut,
+ redemptionVault.address,
+ );
+
const balanceAfterContract = await mTBILL.balanceOf(redemptionVault.address);
+ const balanceUserTokenOutAfter = await balanceOfBase18(
+ tokenContract,
+ sender.address,
+ );
const supplyAfter = await mTBILL.totalSupply();
- expect(supplyAfter).eq(supplyBefore);
- expect(latestRequestIdAfter).eq(latestRequestIdBefore.add(1));
- expect(balanceAfterUser).eq(balanceBeforeUser.sub(amountIn));
- expect(balanceAfterContract).eq(
- balanceBeforeContract.add(amountInWithoutFee),
+ expect(requestDataAfter.amountTokenOut).eq(amountOutWithoutFeeBase18);
+ expect(balanceAfterRequestRedeemerMToken).eq(
+ balanceBeforeRequestRedeemerMToken.sub(requestDataBefore.amountMToken),
+ );
+ expect(balanceAfterRequestRedeemerPToken).eq(
+ balanceBeforeRequestRedeemerPToken.sub(
+ feeBase18!.add(amountOutWithoutFeeBase18!),
+ ),
);
+
+ expect(requestDataAfter.status).eq(1);
+
+ expect(balanceUserTokenOutAfter).eq(
+ balanceUserTokenOutBefore?.add(amountOutWithoutFeeBase18!.add(feeBase18!)),
+ );
+ expect(balanceAfterVaultPToken).eq(balanceBeforeVaultPToken);
+
+ expect(supplyAfter).eq(supplyBefore.sub(requestDataBefore.amountMToken));
+
+ expect(balanceAfterUser).eq(balanceBeforeUser);
+
+ expect(balanceAfterContract).eq(balanceBeforeContract);
+
expect(balanceAfterReceiver).eq(balanceBeforeReceiver);
- expect(balanceAfterFeeReceiver).eq(balanceBeforeFeeReceiver.add(fee));
- if (waivedFee) {
- expect(balanceAfterFeeReceiver).eq(balanceBeforeFeeReceiver);
- }
};
-export const approveRedeemRequestTest = async (
+export const truncateToTokenDecimals = (
+ amount: BigNumber,
+ decimals: number,
+) => {
+ const precision = BigNumber.from(10).pow(18 - decimals);
+
+ return amount.div(precision).mul(precision);
+};
+
+export const setLoanAprTest = async (
{
redemptionVault,
owner,
- mTBILL,
- waivedFee,
- }: CommonParamsRedeem & { waivedFee?: boolean },
- requestId: BigNumberish,
- newTokenRate: BigNumber,
+ }: {
+ redemptionVault: RedemptionVaultType;
+ owner: SignerWithAddress;
+ },
+ loanApr: BigNumberish,
opt?: OptionalCommonParams,
) => {
const sender = opt?.from ?? owner;
- const tokensReceiver = await redemptionVault.tokensReceiver();
- const feeReceiver = await redemptionVault.feeReceiver();
+ const callFn = redemptionVault.connect(sender).setLoanApr.bind(this, loanApr);
- if (opt?.revertMessage) {
- await expect(
- redemptionVault.connect(sender).approveRequest(requestId, newTokenRate),
- ).revertedWith(opt?.revertMessage);
+ if (await handleRevert(callFn, redemptionVault, opt)) {
return;
}
- const requestDataBefore = await redemptionVault.redeemRequests(requestId);
+ await expect(callFn())
+ .to.emit(
+ redemptionVault,
+ redemptionVault.interface.events['SetLoanApr(uint256)'].name,
+ )
+ .withArgs(loanApr).to.not.reverted;
+
+ const newLoanApr = await redemptionVault.loanApr();
+ expect(newLoanApr).eq(loanApr);
+};
+
+export const bulkRepayLpLoanRequestTest = async (
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ }: Omit,
+ requests: { id: BigNumberish }[],
+ opt?: OptionalCommonParams,
+) => {
+ const sender = opt?.from ?? owner;
- const manualToken = await redemptionVault.MANUAL_FULLFILMENT_TOKEN();
+ const requestIds = requests.map(({ id }) => id);
- let tokenContract;
- if (requestDataBefore.tokenOut !== manualToken) {
- tokenContract = ERC20__factory.connect(requestDataBefore.tokenOut, owner);
+ const callFn = redemptionVault
+ .connect(sender)
+ .bulkRepayLpLoanRequest.bind(this, requestIds);
+
+ if (await handleRevert(callFn, redemptionVault, opt)) {
+ return;
}
- const balanceBeforeUser = await mTBILL.balanceOf(sender.address);
- const balanceBeforeContract = await mTBILL.balanceOf(redemptionVault.address);
- const balanceBeforeReceiver = await mTBILL.balanceOf(tokensReceiver);
- const balanceBeforeFeeReceiver = await mTBILL.balanceOf(feeReceiver);
+ const loanRepaymentAddress = await redemptionVault.loanRepaymentAddress();
+ const loanLp = await redemptionVault.loanLp();
- const supplyBefore = await mTBILL.totalSupply();
+ const requestDatasBefore = await Promise.all(
+ requestIds.map((requestId) => redemptionVault.loanRequests(requestId)),
+ );
- const balanceUserTokenOutBefore =
- tokenContract && (await tokenContract.balanceOf(sender.address));
+ const balancesBefore = await Promise.all(
+ requestDatasBefore.map(({ tokenOut }) =>
+ balanceOfBase18(
+ ERC20__factory.connect(tokenOut, owner),
+ loanRepaymentAddress,
+ ),
+ ),
+ );
- await expect(
- redemptionVault.connect(sender).approveRequest(requestId, newTokenRate),
- )
- .to.emit(
- redemptionVault,
- redemptionVault.interface.events['ApproveRequest(uint256,uint256)'].name,
- )
- .withArgs(requestId, newTokenRate).to.not.reverted;
+ const balancesLpBefore = await Promise.all(
+ requestDatasBefore.map(({ tokenOut }) =>
+ balanceOfBase18(ERC20__factory.connect(tokenOut, owner), loanLp),
+ ),
+ );
- const requestDataAfter = await redemptionVault.redeemRequests(requestId);
+ const totalSupplyBefore = await mTBILL.totalSupply();
- expect(requestDataBefore.status).not.eq(requestDataAfter.status);
- expect(requestDataAfter.status).eq(1);
+ const expectedReceivedAmounts = await Promise.all(
+ requestDatasBefore.map(async (requestData) => {
+ return requestData.amountTokenOut;
+ }),
+ );
- const balanceAfterUser = await mTBILL.balanceOf(sender.address);
- const balanceAfterReceiver = await mTBILL.balanceOf(tokensReceiver);
- const balanceAfterFeeReceiver = await mTBILL.balanceOf(feeReceiver);
- const balanceAfterContract = await mTBILL.balanceOf(redemptionVault.address);
- const balanceUserTokenOutAfter =
- tokenContract && (await tokenContract.balanceOf(sender.address));
+ const groupedDataBefore = requests.map(({ id }, index) => {
+ return {
+ id,
+ request: requestDatasBefore[index],
+ expectedReceivedAmount: expectedReceivedAmounts[index],
+ expectedReceivedFeeAmount: BigNumber.from(0),
+ balance: balancesBefore[index],
+ balanceLp: balancesLpBefore[index],
+ };
+ });
+ const expectedTotalBurned = BigNumber.from(0);
- const supplyAfter = await mTBILL.totalSupply();
+ const txPromise = callFn();
+ await expect(txPromise).to.not.reverted;
- if (requestDataBefore.tokenOut !== manualToken) {
- const tokenDecimals = !tokenContract ? 18 : await tokenContract.decimals();
+ const txReceipt = await (await txPromise).wait();
+ const txBlock = await ethers.provider.getBlock(txReceipt.blockNumber);
+ const currentTimestamp = txBlock.timestamp;
- const amountOut = requestDataBefore.amountMToken
- .mul(newTokenRate)
- .div(requestDataBefore.tokenOutRate)
- .div(10 ** (18 - tokenDecimals));
+ const loanApr = await redemptionVault.loanApr();
- expect(balanceUserTokenOutAfter).eq(
- balanceUserTokenOutBefore?.add(amountOut),
- );
- }
- expect(supplyAfter).eq(supplyBefore.sub(requestDataBefore.amountMToken));
+ const feePercents = await Promise.all(
+ requestDatasBefore.map(async (requestData) => {
+ const duration = BigNumber.from(currentTimestamp).sub(
+ requestData.createdAt,
+ );
- expect(balanceAfterUser).eq(balanceBeforeUser);
+ const tokenDecimals = await ERC20__factory.connect(
+ requestData.tokenOut,
+ owner,
+ ).decimals();
- expect(balanceAfterContract).eq(
- balanceBeforeContract.sub(requestDataBefore.amountMToken),
- );
+ const accruedInterestRaw = requestData.amountTokenOut
+ .mul(loanApr)
+ .mul(duration)
+ .div(BigNumber.from(10_000).mul(365).mul(86400));
- expect(balanceAfterReceiver).eq(balanceBeforeReceiver);
- expect(balanceAfterFeeReceiver).eq(balanceBeforeFeeReceiver);
- if (waivedFee) {
- expect(balanceAfterFeeReceiver).eq(balanceBeforeFeeReceiver);
- }
-};
+ const accruedInterest = truncateToTokenDecimals(
+ accruedInterestRaw,
+ tokenDecimals,
+ );
-export const safeApproveRedeemRequestTest = async (
- {
- redemptionVault,
- owner,
- mTBILL,
- waivedFee,
- }: CommonParamsRedeem & { waivedFee?: boolean },
- requestId: BigNumberish,
- newTokenRate: BigNumber,
- opt?: OptionalCommonParams,
-) => {
- const sender = opt?.from ?? owner;
+ const amountFee = accruedInterest.gt(requestData.amountFee)
+ ? accruedInterest
+ : requestData.amountFee;
- const tokensReceiver = await redemptionVault.tokensReceiver();
- const feeReceiver = await redemptionVault.feeReceiver();
+ return amountFee;
+ }),
+ );
- if (opt?.revertMessage) {
- await expect(
- redemptionVault
- .connect(sender)
- .safeApproveRequest(requestId, newTokenRate),
- ).revertedWith(opt?.revertMessage);
- return;
+ for (const [index, feePercent] of feePercents.entries()) {
+ groupedDataBefore[index].expectedReceivedFeeAmount = feePercent;
}
- const requestDataBefore = await redemptionVault.redeemRequests(requestId);
+ const parsedLogs = txReceipt.logs
+ .filter((v) => v.address === redemptionVault.address)
+ .map((log) => redemptionVault.interface.parseLog(log))
+ .filter((v) => v.name === 'RepayLpLoanRequest')
+ .map((v) => v.args);
- const tokenContract = ERC20__factory.connect(
- requestDataBefore.tokenOut,
- owner,
+ const requestDatasAfter = await Promise.all(
+ requestIds.map((requestId) => redemptionVault.loanRequests(requestId)),
+ );
+
+ const balancesAfter = await Promise.all(
+ requestDatasAfter.map(({ tokenOut }) =>
+ balanceOfBase18(
+ ERC20__factory.connect(tokenOut, owner),
+ loanRepaymentAddress,
+ ),
+ ),
);
- const balanceBeforeUser = await mTBILL.balanceOf(requestDataBefore.sender);
- const balanceBeforeContract = await mTBILL.balanceOf(redemptionVault.address);
- const balanceBeforeReceiver = await mTBILL.balanceOf(tokensReceiver);
- const balanceBeforeFeeReceiver = await mTBILL.balanceOf(feeReceiver);
+ const balancesLpAfter = await Promise.all(
+ requestDatasAfter.map(({ tokenOut }) =>
+ balanceOfBase18(ERC20__factory.connect(tokenOut, owner), loanLp),
+ ),
+ );
- const supplyBefore = await mTBILL.totalSupply();
+ const totalSupplyAfter = await mTBILL.totalSupply();
- const balanceUserTokenOutBefore = await tokenContract.balanceOf(
- requestDataBefore.sender,
- );
+ const groupedDataAfter = requests.map(({ id }, index) => {
+ return {
+ id,
+ request: requestDatasAfter[index],
+ balance: balancesAfter[index],
+ balanceLp: balancesLpAfter[index],
+ };
+ });
- await expect(
- redemptionVault.connect(sender).safeApproveRequest(requestId, newTokenRate),
- )
- .to.emit(
- redemptionVault,
- redemptionVault.interface.events['SafeApproveRequest(uint256,uint256)']
- .name,
- )
- .withArgs(requestId, newTokenRate).to.not.reverted;
+ expect(totalSupplyAfter).eq(totalSupplyBefore.sub(expectedTotalBurned));
- const requestDataAfter = await redemptionVault.redeemRequests(requestId);
+ for (const [i, { id, ...dataBefore }] of groupedDataBefore.entries()) {
+ const dataAfter = groupedDataAfter[i];
- expect(requestDataBefore.status).not.eq(requestDataAfter.status);
- expect(requestDataAfter.status).eq(1);
+ const requestDataBefore = dataBefore.request;
+ const requestDataAfter = dataAfter.request;
- const balanceAfterUser = await mTBILL.balanceOf(sender.address);
- const balanceAfterReceiver = await mTBILL.balanceOf(tokensReceiver);
- const balanceAfterFeeReceiver = await mTBILL.balanceOf(feeReceiver);
- const balanceAfterContract = await mTBILL.balanceOf(redemptionVault.address);
- const balanceUserTokenOutAfter = await tokenContract.balanceOf(
- requestDataAfter.sender,
- );
+ const balanceAfter = dataAfter.balance;
+ const balanceLpAfter = dataAfter.balanceLp;
- const supplyAfter = await mTBILL.totalSupply();
+ const balanceBefore = dataBefore.balance;
+ const balanceLpBefore = dataBefore.balanceLp;
- const tokenDecimals = await tokenContract.decimals();
+ expect(requestDataAfter.amountFee).eq(dataBefore.expectedReceivedFeeAmount);
+ expect(requestDataAfter.tokenOut).eq(requestDataBefore.tokenOut);
+ expect(requestDataAfter.amountTokenOut).eq(
+ requestDataBefore.amountTokenOut,
+ );
- const amountOut = requestDataBefore.amountMToken
- .mul(newTokenRate)
- .div(requestDataBefore.tokenOutRate)
- .div(10 ** (18 - tokenDecimals));
+ const logs = parsedLogs.filter((log) => log.requestId.eq(id));
- expect(balanceUserTokenOutAfter).eq(
- balanceUserTokenOutBefore?.add(amountOut),
- );
- expect(supplyAfter).eq(supplyBefore.sub(requestDataBefore.amountMToken));
+ const expectedReceivedAggregatedByUser = groupedDataBefore
+ .filter((v) => v.request.tokenOut === requestDataBefore.tokenOut)
+ .reduce((prev, curr) => {
+ return prev
+ .add(curr.expectedReceivedAmount)
+ .add(curr.expectedReceivedFeeAmount);
+ }, BigNumber.from(0));
- expect(balanceAfterUser).eq(balanceBeforeUser);
+ expect(logs.length).eq(1);
+ expect(requestDataAfter.createdAt).eq(requestDataBefore.createdAt);
+ expect(requestDataAfter.status).eq(1);
+ expect(balanceAfter).eq(
+ balanceBefore.sub(expectedReceivedAggregatedByUser),
+ );
- expect(balanceAfterContract).eq(
- balanceBeforeContract.sub(requestDataBefore.amountMToken),
- );
+ expect(balanceLpAfter).eq(
+ balanceLpBefore.add(expectedReceivedAggregatedByUser),
+ );
+ const log = logs[0];
- expect(balanceAfterReceiver).eq(balanceBeforeReceiver);
- expect(balanceAfterFeeReceiver).eq(balanceBeforeFeeReceiver);
- if (waivedFee) {
- expect(balanceAfterFeeReceiver).eq(balanceBeforeFeeReceiver);
+ expect(log.requestId).eq(id);
}
};
export const safeBulkApproveRequestTest = async (
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed }: CommonParamsRedeem,
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate,
+ feedIsGrowth = false,
+ }: CommonParamsRedeem & {
+ isAvgRate?: boolean;
+ feedIsGrowth?: boolean;
+ },
requests: { id: BigNumberish; expectedToExecute?: boolean }[],
newRate?: BigNumberish | 'request-rate',
opt?: OptionalCommonParams,
@@ -617,37 +967,40 @@ export const safeBulkApproveRequestTest = async (
newRate && newRate !== 'request-rate'
? redemptionVault
.connect(sender)
- ['safeBulkApproveRequest(uint256[],uint256)'].bind(
- this,
- requestIds,
- newRate,
- )
+ [
+ `safeBulkApproveRequest${
+ isAvgRate ? 'AvgRate' : ''
+ }(uint256[],uint256)`
+ ].bind(this, requestIds, newRate)
: newRate === 'request-rate'
? redemptionVault
.connect(sender)
.safeBulkApproveRequestAtSavedRate.bind(this, requestIds)
: redemptionVault
.connect(sender)
- ['safeBulkApproveRequest(uint256[])'].bind(this, requestIds);
+ [
+ `safeBulkApproveRequest${isAvgRate ? 'AvgRate' : ''}(uint256[])`
+ ].bind(this, requestIds);
- if (opt?.revertMessage) {
- await expect(callFn()).revertedWith(opt?.revertMessage);
+ if (await handleRevert(callFn, redemptionVault, opt)) {
return;
}
+ await setNextBlockTimestamp((await getCurrentBlockTimestamp()) + 1);
+
const requestDatasBefore = await Promise.all(
requestIds.map((requestId) => redemptionVault.redeemRequests(requestId)),
);
const balancesBefore = await Promise.all(
- requestDatasBefore.map(({ tokenOut, sender }) =>
- balanceOfBase18(ERC20__factory.connect(tokenOut, owner), sender),
+ requestDatasBefore.map(({ tokenOut, recipient }) =>
+ balanceOfBase18(ERC20__factory.connect(tokenOut, owner), recipient),
),
);
const totalSupplyBefore = await mTBILL.totalSupply();
- const tokenDecimals = await Promise.all(
+ const _tokenDecimals = await Promise.all(
requestDatasBefore.map(({ tokenOut }) =>
ERC20__factory.connect(tokenOut, owner).decimals(),
),
@@ -656,7 +1009,7 @@ export const safeBulkApproveRequestTest = async (
const feePercents = await Promise.all(
requestDatasBefore.map((requestData) =>
getFeePercent(
- requestData.sender,
+ requestData.recipient,
requestData.tokenOut,
redemptionVault,
false,
@@ -664,16 +1017,73 @@ export const safeBulkApproveRequestTest = async (
),
);
+ const nextExpectedRequestIdToProcessBefore =
+ await redemptionVault.nextExpectedRequestIdToProcess();
+
+ const txPromise = callFn();
+ await expect(txPromise).to.not.reverted;
+
const currentRate = await mTokenToUsdDataFeed.getDataInBase18();
- const newExpectedRate =
- newRate === 'request-rate' ? undefined : newRate ?? currentRate;
- const expectedReceivedAmounts = requestDatasBefore.map((requestData, i) =>
- requestData.amountMToken
- .mul(newExpectedRate ?? requestData.mTokenRate)
- .div(requestData.tokenOutRate)
- .div(10 ** (18 - tokenDecimals[i]))
- .mul(10 ** (18 - tokenDecimals[i])),
+ const nextExpectedRequestIdToProcessAfter =
+ await redemptionVault.nextExpectedRequestIdToProcess();
+
+ const expectedToExecuteRequests = requests.filter(
+ (v) => v.expectedToExecute ?? true,
+ );
+
+ const expectedHighestProcessedRequestId = expectedToExecuteRequests.sort(
+ (a, b) => +b.id.toString() - +a.id.toString(),
+ )[0]?.id;
+
+ if (
+ expectedHighestProcessedRequestId !== undefined &&
+ nextExpectedRequestIdToProcessBefore.lte(expectedHighestProcessedRequestId)
+ ) {
+ expect(nextExpectedRequestIdToProcessAfter).eq(
+ BigNumber.from(expectedHighestProcessedRequestId).add(1),
+ );
+ } else {
+ expect(nextExpectedRequestIdToProcessAfter).eq(
+ nextExpectedRequestIdToProcessBefore,
+ );
+ }
+
+ const newExpectedRate = (
+ requestData: (typeof requestDatasBefore)[number],
+ ) => {
+ let rate =
+ newRate === 'request-rate'
+ ? requestData.mTokenRate
+ : newRate ?? currentRate;
+
+ if (isAvgRate) {
+ const holdbackRate = expectedHoldbackPartRateFromAvg(
+ requestData.amountMToken,
+ requestData.amountMTokenInstant,
+ requestData.mTokenRate,
+ rate,
+ );
+ rate = holdbackRate === 0n ? rate : holdbackRate;
+ }
+ return BigNumber.from(rate);
+ };
+
+ const expectedReceivedAmounts = await Promise.all(
+ requestDatasBefore.map(async (requestData) => {
+ const { amountOutWithoutFeeBase18 } = await calcExpectedTokenOutAmount(
+ sender,
+ ERC20__factory.connect(requestData.tokenOut, owner),
+ redemptionVault,
+ newExpectedRate(requestData),
+ requestData.amountMToken,
+ false,
+ requestData.feePercent,
+ requestData.tokenOutRate,
+ );
+
+ return amountOutWithoutFeeBase18!;
+ }),
);
const groupedDataBefore = requests.map(({ id, expectedToExecute }, index) => {
@@ -691,22 +1101,15 @@ export const safeBulkApproveRequestTest = async (
const expectedTotalBurned = groupedDataBefore
.filter((v) => v.expectedToExecute)
.reduce((prev, curr) => {
- return prev.add(
- curr.request.amountMToken.sub(
- curr.request.amountMToken.mul(curr.feePercent).div(hundredPercent),
- ),
- );
+ return prev.add(curr.request.amountMToken);
}, BigNumber.from(0));
- const txPromise = callFn();
- await expect(txPromise).to.not.reverted;
-
const txReceipt = await (await txPromise).wait();
const parsedLogs = txReceipt.logs
.filter((v) => v.address === redemptionVault.address)
.map((log) => redemptionVault.interface.parseLog(log))
- .filter((v) => v.name === 'SafeApproveRequest')
+ .filter((v) => v.name === 'ApproveRequest')
.map((v) => v.args);
const requestDatasAfter = await Promise.all(
@@ -714,8 +1117,8 @@ export const safeBulkApproveRequestTest = async (
);
const balancesAfter = await Promise.all(
- requestDatasAfter.map(({ tokenOut, sender }) =>
- balanceOfBase18(ERC20__factory.connect(tokenOut, owner), sender),
+ requestDatasAfter.map(({ tokenOut, recipient }) =>
+ balanceOfBase18(ERC20__factory.connect(tokenOut, owner), recipient),
),
);
@@ -744,7 +1147,7 @@ export const safeBulkApproveRequestTest = async (
const balanceAfter = dataAfter.balance;
const balanceBefore = dataBefore.balance;
- expect(requestDataAfter.sender).eq(requestDataBefore.sender);
+ expect(requestDataAfter.recipient).eq(requestDataBefore.recipient);
expect(requestDataAfter.tokenOut).eq(requestDataBefore.tokenOut);
expect(requestDataAfter.amountMToken).eq(requestDataBefore.amountMToken);
expect(requestDataAfter.tokenOutRate).eq(requestDataBefore.tokenOutRate);
@@ -754,7 +1157,7 @@ export const safeBulkApproveRequestTest = async (
const expectedReceivedAggregatedByUser = groupedDataBefore
.filter(
(v) =>
- v.request.sender === requestDataBefore.sender &&
+ v.request.recipient === requestDataBefore.recipient &&
v.request.tokenOut === requestDataBefore.tokenOut &&
v.expectedToExecute,
)
@@ -763,23 +1166,27 @@ export const safeBulkApproveRequestTest = async (
}, BigNumber.from(0));
if (expectedToExecute) {
+ const expectedRate = newExpectedRate(requestDataBefore);
expect(logs.length).eq(1);
- expect(requestDataAfter.mTokenRate).eq(
- newExpectedRate ?? requestDataBefore.mTokenRate,
- );
+ expect(requestDataAfter.approvedMTokenRate).eq(expectedRate);
+ expect(requestDataAfter.mTokenRate).eq(requestDataBefore.mTokenRate);
+
expect(requestDataAfter.status).eq(1);
+ expect(requestDataAfter.amountTokenOut).eq(
+ dataBefore.expectedReceivedAmount,
+ );
+
expect(balanceAfter).eq(
balanceBefore.add(expectedReceivedAggregatedByUser),
);
const log = logs[0];
- expect(log.newMTokenRate).eq(
- newExpectedRate ?? requestDataBefore.mTokenRate,
- );
+ expect(log.newMTokenRate).eq(expectedRate);
expect(log.requestId).eq(id);
} else {
expect(logs.length).eq(0);
expect(requestDataAfter.mTokenRate).eq(requestDataBefore.mTokenRate);
+ expect(requestDataAfter.approvedMTokenRate).eq(0);
expect(requestDataAfter.status).eq(0);
}
}
@@ -793,12 +1200,14 @@ export const rejectRedeemRequestTest = async (
const sender = opt?.from ?? owner;
const tokensReceiver = await redemptionVault.tokensReceiver();
- const feeReceiver = await redemptionVault.feeReceiver();
- if (opt?.revertMessage) {
- await expect(
- redemptionVault.connect(sender).rejectRequest(requestId),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ redemptionVault.connect(sender).rejectRequest.bind(this, requestId),
+ redemptionVault,
+ opt,
+ )
+ ) {
return;
}
@@ -807,17 +1216,45 @@ export const rejectRedeemRequestTest = async (
const balanceBeforeUser = await mTBILL.balanceOf(sender.address);
const balanceBeforeContract = await mTBILL.balanceOf(redemptionVault.address);
const balanceBeforeReceiver = await mTBILL.balanceOf(tokensReceiver);
- const balanceBeforeFeeReceiver = await mTBILL.balanceOf(feeReceiver);
+
+ const balanceVaultBefore = await balanceOfBase18(
+ requestDataBefore.tokenOut,
+ redemptionVault.address,
+ );
+ const balanceBeforeContractPToken = await balanceOfBase18(
+ requestDataBefore.tokenOut,
+ redemptionVault.address,
+ );
const supplyBefore = await mTBILL.totalSupply();
+ const nextExpectedRequestIdToProcessBefore =
+ await redemptionVault.nextExpectedRequestIdToProcess();
+
await expect(redemptionVault.connect(sender).rejectRequest(requestId))
.to.emit(
redemptionVault,
- redemptionVault.interface.events['RejectRequest(uint256,address)'].name,
+ redemptionVault.interface.events['RejectRequest(uint256)'].name,
)
- .withArgs(requestId, sender).to.not.reverted;
+ .withArgs(requestId).to.not.reverted;
+
+ const nextExpectedRequestIdToProcessAfter =
+ await redemptionVault.nextExpectedRequestIdToProcess();
+
+ if (nextExpectedRequestIdToProcessBefore.lte(requestId)) {
+ expect(nextExpectedRequestIdToProcessAfter).eq(
+ BigNumber.from(requestId).add(1),
+ );
+ } else {
+ expect(nextExpectedRequestIdToProcessAfter).eq(
+ nextExpectedRequestIdToProcessBefore,
+ );
+ }
+ const balanceVaultAfter = await balanceOfBase18(
+ requestDataBefore.tokenOut,
+ redemptionVault.address,
+ );
const requestDataAfter = await redemptionVault.redeemRequests(requestId);
expect(requestDataBefore.status).not.eq(requestDataAfter.status);
@@ -825,134 +1262,271 @@ export const rejectRedeemRequestTest = async (
const balanceAfterUser = await mTBILL.balanceOf(sender.address);
const balanceAfterReceiver = await mTBILL.balanceOf(tokensReceiver);
- const balanceAfterFeeReceiver = await mTBILL.balanceOf(feeReceiver);
const balanceAfterContract = await mTBILL.balanceOf(redemptionVault.address);
+ const balanceAfterContractPToken = await balanceOfBase18(
+ requestDataBefore.tokenOut,
+ redemptionVault.address,
+ );
const supplyAfter = await mTBILL.totalSupply();
+ expect(balanceVaultAfter).eq(balanceVaultBefore);
expect(supplyAfter).eq(supplyBefore);
expect(balanceAfterUser).eq(balanceBeforeUser);
expect(balanceAfterContract).eq(balanceBeforeContract);
expect(balanceAfterReceiver).eq(balanceBeforeReceiver);
- expect(balanceAfterFeeReceiver).eq(balanceBeforeFeeReceiver);
+ expect(balanceAfterContractPToken).eq(balanceBeforeContractPToken);
+};
+
+export const cancelLpLoanRequestTest = async (
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ }: Omit,
+ requestId: BigNumberish,
+ opt?: OptionalCommonParams,
+) => {
+ const sender = opt?.from ?? owner;
+
+ const loanLp = await redemptionVault.loanLp();
+ const loanRepaymentAddress = await redemptionVault.loanRepaymentAddress();
+
+ if (
+ await handleRevert(
+ redemptionVault.connect(sender).cancelLpLoanRequest.bind(this, requestId),
+ redemptionVault,
+ opt,
+ )
+ ) {
+ return;
+ }
+
+ const requestDataBefore = await redemptionVault.loanRequests(requestId);
+
+ const loanToken = ERC20__factory.connect(requestDataBefore.tokenOut, owner);
+
+ const balanceBeforeLpRepayment = await loanToken.balanceOf(
+ loanRepaymentAddress,
+ );
+ const balanceBeforeLp = await loanToken.balanceOf(loanLp);
+ const balanceBeforeSender = await loanToken.balanceOf(sender.address);
+
+ const supplyBefore = await mTBILL.totalSupply();
+
+ await expect(redemptionVault.connect(sender).cancelLpLoanRequest(requestId))
+ .to.emit(
+ redemptionVault,
+ redemptionVault.interface.events['CancelLpLoanRequest(uint256)'].name,
+ )
+ .withArgs(requestId).to.not.reverted;
+
+ const requestDataAfter = await redemptionVault.loanRequests(requestId);
+
+ const balanceAfterLpRepayment = await loanToken.balanceOf(
+ loanRepaymentAddress,
+ );
+ const balanceAfterLp = await loanToken.balanceOf(loanLp);
+ const balanceAfterSender = await loanToken.balanceOf(sender.address);
+
+ const supplyAfter = await mTBILL.totalSupply();
+
+ expect(requestDataAfter.amountFee).eq(requestDataAfter.amountFee);
+ expect(requestDataAfter.amountTokenOut).eq(requestDataAfter.amountTokenOut);
+ expect(requestDataAfter.status).eq(2);
+
+ expect(supplyAfter).eq(supplyBefore);
+ expect(balanceAfterLpRepayment).eq(balanceBeforeLpRepayment);
+ expect(balanceAfterLp).eq(balanceBeforeLp);
+ expect(balanceAfterSender).eq(balanceBeforeSender);
};
-export const setMinFiatRedeemAmountTest = async (
+export const setRequestRedeemerTest = async (
{ redemptionVault, owner }: CommonParams,
- valueN: number,
+ redeemer: string,
opt?: OptionalCommonParams,
) => {
- const value = parseUnits(valueN.toString());
+ if (
+ await handleRevert(
+ redemptionVault
+ .connect(opt?.from ?? owner)
+ .setRequestRedeemer.bind(this, redeemer),
+ redemptionVault,
+ opt,
+ )
+ ) {
+ return;
+ }
+
+ await expect(
+ redemptionVault.connect(opt?.from ?? owner).setRequestRedeemer(redeemer),
+ ).to.emit(
+ redemptionVault,
+ redemptionVault.interface.events['SetRequestRedeemer(address)'].name,
+ ).to.not.reverted;
+
+ const newRedeemer = await redemptionVault.requestRedeemer();
+ expect(newRedeemer).eq(redeemer);
+};
- if (opt?.revertMessage) {
- await expect(
- redemptionVault.connect(opt?.from ?? owner).setMinFiatRedeemAmount(value),
- ).revertedWith(opt?.revertMessage);
+export const setLoanLpTest = async (
+ { redemptionVault, owner }: CommonParams,
+ loanLp: string,
+ opt?: OptionalCommonParams,
+) => {
+ if (
+ await handleRevert(
+ redemptionVault.connect(opt?.from ?? owner).setLoanLp.bind(this, loanLp),
+ redemptionVault,
+ opt,
+ )
+ ) {
return;
}
await expect(
- redemptionVault.connect(opt?.from ?? owner).setMinFiatRedeemAmount(value),
+ redemptionVault.connect(opt?.from ?? owner).setLoanLp(loanLp),
).to.emit(
redemptionVault,
- redemptionVault.interface.events['SetMinFiatRedeemAmount(address,uint256)']
- .name,
+ redemptionVault.interface.events['SetLoanLp(address)'].name,
).to.not.reverted;
- const newMin = await redemptionVault.minFiatRedeemAmount();
- expect(newMin).eq(value);
+ const newLoanLp = await redemptionVault.loanLp();
+ expect(newLoanLp).eq(loanLp);
};
-export const setFiatAdditionalFeeTest = async (
+export const setLoanRepaymentAddressTest = async (
{ redemptionVault, owner }: CommonParams,
- valueN: number,
+ loanRepaymentAddress: string,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
- redemptionVault.connect(opt?.from ?? owner).setFiatAdditionalFee(valueN),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ redemptionVault
+ .connect(opt?.from ?? owner)
+ .setLoanRepaymentAddress.bind(this, loanRepaymentAddress),
+ redemptionVault,
+ opt,
+ )
+ ) {
return;
}
await expect(
- redemptionVault.connect(opt?.from ?? owner).setFiatAdditionalFee(valueN),
+ redemptionVault
+ .connect(opt?.from ?? owner)
+ .setLoanRepaymentAddress(loanRepaymentAddress),
).to.emit(
redemptionVault,
- redemptionVault.interface.events['SetFiatAdditionalFee(address,uint256)']
- .name,
+ redemptionVault.interface.events['SetLoanRepaymentAddress(address)'].name,
).to.not.reverted;
- const newfee = await redemptionVault.fiatAdditionalFee();
- expect(newfee).eq(valueN);
+ const newLoanRepaymentAddress = await redemptionVault.loanRepaymentAddress();
+ expect(newLoanRepaymentAddress).eq(loanRepaymentAddress);
};
-export const setFiatFlatFeeTest = async (
+export const setLoanSwapperVaultTest = async (
{ redemptionVault, owner }: CommonParams,
- valueN: number,
+ loanSwapperVault: string,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
- redemptionVault.connect(opt?.from ?? owner).setFiatFlatFee(valueN),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ redemptionVault
+ .connect(opt?.from ?? owner)
+ .setLoanSwapperVault.bind(this, loanSwapperVault),
+ redemptionVault,
+ opt,
+ )
+ ) {
return;
}
await expect(
- redemptionVault.connect(opt?.from ?? owner).setFiatFlatFee(valueN),
+ redemptionVault
+ .connect(opt?.from ?? owner)
+ .setLoanSwapperVault(loanSwapperVault),
).to.emit(
redemptionVault,
- redemptionVault.interface.events['SetFiatFlatFee(address,uint256)'].name,
+ redemptionVault.interface.events['SetLoanSwapperVault(address)'].name,
).to.not.reverted;
- const newfee = await redemptionVault.fiatFlatFee();
- expect(newfee).eq(valueN);
+ const newLoanSwapperVault = await redemptionVault.loanSwapperVault();
+ expect(newLoanSwapperVault).eq(loanSwapperVault);
};
-export const setRequestRedeemerTest = async (
+export const setMaxApproveRequestIdTest = async (
{ redemptionVault, owner }: CommonParams,
- redeemer: string,
+ maxApproveRequestId: number,
opt?: OptionalCommonParams,
) => {
- if (opt?.revertMessage) {
- await expect(
- redemptionVault.connect(opt?.from ?? owner).setRequestRedeemer(redeemer),
- ).revertedWith(opt?.revertMessage);
+ if (
+ await handleRevert(
+ redemptionVault
+ .connect(opt?.from ?? owner)
+ .setMaxApproveRequestId.bind(this, maxApproveRequestId),
+ redemptionVault,
+ opt,
+ )
+ ) {
return;
}
await expect(
- redemptionVault.connect(opt?.from ?? owner).setRequestRedeemer(redeemer),
+ redemptionVault
+ .connect(opt?.from ?? owner)
+ .setMaxApproveRequestId(maxApproveRequestId),
).to.emit(
redemptionVault,
- redemptionVault.interface.events['SetRequestRedeemer(address,address)']
- .name,
+ redemptionVault.interface.events['SetMaxApproveRequestId(uint256)'].name,
).to.not.reverted;
- const newRedeemer = await redemptionVault.requestRedeemer();
- expect(newRedeemer).eq(redeemer);
+ const newMaxApproveRequestId = await redemptionVault.maxApproveRequestId();
+ expect(newMaxApproveRequestId).eq(maxApproveRequestId);
+};
+
+export const setPreferLoanLiquidityTest = async (
+ { redemptionVault, owner }: CommonParams,
+ preferLoanLiquidity: boolean,
+ opt?: OptionalCommonParams,
+) => {
+ if (
+ await handleRevert(
+ redemptionVault
+ .connect(opt?.from ?? owner)
+ .setPreferLoanLiquidity.bind(this, preferLoanLiquidity),
+ redemptionVault,
+ opt,
+ )
+ ) {
+ return;
+ }
+
+ await expect(
+ redemptionVault
+ .connect(opt?.from ?? owner)
+ .setPreferLoanLiquidity(preferLoanLiquidity),
+ ).to.emit(
+ redemptionVault,
+ redemptionVault.interface.events['SetPreferLoanLiquidity(bool)'].name,
+ ).to.not.reverted;
+
+ const newPreferLoanLiquidity = await redemptionVault.preferLoanLiquidity();
+ expect(newPreferLoanLiquidity).eq(preferLoanLiquidity);
};
export const getFeePercent = async (
sender: string,
token: string,
- redemptionVault:
- | RedemptionVault
- | RedemptionVaultWIthBUIDL
- | RedemptionVaultWithAave
- | RedemptionVaultWithMorpho
- | RedemptionVaultWithMToken
- | RedemptionVaultWithSwapper
- | RedemptionVaultWithUSTB,
+ redemptionVault: RedemptionVaultType,
isInstant: boolean,
- additionalFee?: BigNumber,
+ overrideTokenFee?: BigNumber,
) => {
const tokenConfig = await redemptionVault.tokensConfig(token);
let feePercent = constants.Zero;
const isWaived = await redemptionVault.waivedFeeRestriction(sender);
if (!isWaived) {
- feePercent = additionalFee ?? tokenConfig.fee;
+ feePercent = overrideTokenFee ?? tokenConfig.fee;
if (isInstant) {
const instantFee = await redemptionVault.instantFee();
feePercent = feePercent.add(instantFee);
@@ -964,17 +1538,12 @@ export const getFeePercent = async (
export const calcExpectedTokenOutAmount = async (
sender: SignerWithAddress,
token: ERC20,
- redemptionVault:
- | RedemptionVault
- | RedemptionVaultWIthBUIDL
- | RedemptionVaultWithAave
- | RedemptionVaultWithMorpho
- | RedemptionVaultWithMToken
- | RedemptionVaultWithSwapper
- | RedemptionVaultWithUSTB,
+ redemptionVault: RedemptionVaultType,
mTokenRate: BigNumber,
amountIn: BigNumber,
isInstant: boolean,
+ overrideTokenFee?: BigNumber,
+ overrideTokenOutRate?: BigNumber,
) => {
const tokenConfig = await redemptionVault.tokensConfig(token.address);
@@ -982,40 +1551,280 @@ export const calcExpectedTokenOutAmount = async (
tokenConfig.dataFeed,
sender,
);
- const currentTokenInRate = tokenConfig.stable
- ? constants.WeiPerEther
- : await dataFeedContract.getDataInBase18();
- if (currentTokenInRate.isZero())
+ const currentTokenOutRate =
+ overrideTokenOutRate ??
+ (tokenConfig.stable
+ ? constants.WeiPerEther
+ : await dataFeedContract.getDataInBase18());
+ if (currentTokenOutRate.isZero())
return {
amountOut: constants.Zero,
amountInWithoutFee: constants.Zero,
fee: constants.Zero,
currentStableRate: constants.Zero,
+ tokenOutRate: constants.Zero,
+ feePercent: constants.Zero,
};
- const feePercent = await getFeePercent(
- sender.address,
- token.address,
- redemptionVault,
- isInstant,
- );
-
- const hundredPercent = await redemptionVault.ONE_HUNDRED_PERCENT();
- const fee = amountIn.mul(feePercent).div(hundredPercent);
-
- const amountInWithoutFee = amountIn.sub(fee);
-
const tokenDecimals = await token.decimals();
- const amountOut = amountInWithoutFee
+ const amountOut = amountIn
.mul(mTokenRate)
- .div(currentTokenInRate)
+ .div(currentTokenOutRate)
.div(10 ** (18 - tokenDecimals));
+ const feePercent =
+ overrideTokenFee ??
+ (await getFeePercent(
+ sender.address,
+ token.address,
+ redemptionVault,
+ isInstant,
+ ));
+
+ const hundredPercent = await redemptionVault.ONE_HUNDRED_PERCENT();
+ const fee = amountOut.mul(feePercent).div(hundredPercent);
+
+ const amountOutWithoutFee = amountOut.sub(fee);
+
return {
amountOut,
- amountInWithoutFee,
+ amountOutWithoutFee: amountOutWithoutFee,
fee,
- currentStableRate: currentTokenInRate,
+ feePercent,
+ currentStableRate: currentTokenOutRate,
+ amountOutBase18: amountOut.mul(10 ** (18 - tokenDecimals)),
+ amountOutWithoutFeeBase18: amountOutWithoutFee.mul(
+ 10 ** (18 - tokenDecimals),
+ ),
+ feeBase18: fee.mul(10 ** (18 - tokenDecimals)),
+ tokenOutRate: currentTokenOutRate,
+ };
+};
+
+export const estimateSendTokensFromLiquidity = async (
+ redemptionVault: RedemptionVaultType,
+ tokenOut: ERC20,
+ amountTokenOutWithoutFeeBase18: BigNumber,
+ feeAmountBase18: BigNumber,
+ tokenOutRate: BigNumber,
+ {
+ additionalLiquidity,
+ loanLiquidityExpectToFail,
+ }: {
+ additionalLiquidity?: BigNumberish;
+ loanLiquidityExpectToFail?: boolean;
+ },
+) => {
+ const decimals = await tokenOut.decimals();
+ const precision = BigNumber.from(10).pow(18 - decimals);
+ const balanceVaultBase18 = (await tokenOut.balanceOf(redemptionVault.address))
+ .add(additionalLiquidity ?? constants.Zero)
+ .mul(precision);
+
+ const totalAmountBase18 = amountTokenOutWithoutFeeBase18.add(feeAmountBase18);
+
+ if (totalAmountBase18.eq(0)) {
+ return {
+ toTransferFromVaultBase18: constants.Zero,
+ toTransferFromLpBase18: constants.Zero,
+ lpFeePortionBase18: constants.Zero,
+ vaultFeePortionBase18: constants.Zero,
+ toUseVaultLiquidityBase18: constants.Zero,
+ toUseLpLiquidityBase18: constants.Zero,
+ toTransferFromVault: constants.Zero,
+ toTransferFromLpMToken: constants.Zero,
+ lpFeePortion: constants.Zero,
+ vaultFeePortion: constants.Zero,
+ toUseVaultLiquidity: constants.Zero,
+ toUseLpLiquidity: constants.Zero,
+ };
+ }
+
+ const loanSwapperVaultAddress = await redemptionVault.loanSwapperVault();
+ const loanSwapperVault =
+ loanSwapperVaultAddress !== constants.AddressZero
+ ? RedemptionVaultTest__factory.connect(
+ loanSwapperVaultAddress,
+ redemptionVault.provider,
+ )
+ : undefined;
+ const loanSwapperVaultMTokenDataFeed = loanSwapperVault
+ ? DataFeedTest__factory.connect(
+ await loanSwapperVault.mTokenDataFeed(),
+ redemptionVault.provider,
+ )
+ : undefined;
+ const loanSwapperVaultMToken = loanSwapperVault
+ ? ERC20__factory.connect(
+ await loanSwapperVault.mToken(),
+ redemptionVault.provider,
+ )
+ : undefined;
+
+ const truncateToTokenDecimals = (amount: BigNumber) =>
+ amount.div(precision).mul(precision);
+
+ const estimateUseLoanLpLiquidity = async (
+ amountTokenOutBase18: BigNumber,
+ totalAmount: BigNumber,
+ totalFee: BigNumber,
+ ) => {
+ if (amountTokenOutBase18.eq(0)) {
+ return {
+ amountReceivedBase18: constants.Zero,
+ feePortionBase18: constants.Zero,
+ };
+ }
+
+ const loanLp = await redemptionVault.loanLp();
+ if (
+ !loanSwapperVaultMTokenDataFeed ||
+ !loanSwapperVaultMToken ||
+ loanLp === constants.AddressZero
+ ) {
+ return {
+ amountReceivedBase18: constants.Zero,
+ feePortionBase18: constants.Zero,
+ };
+ }
+
+ const mTokenARate = await loanSwapperVaultMTokenDataFeed.getDataInBase18();
+ if (mTokenARate.eq(0)) {
+ return {
+ amountReceivedBase18: constants.Zero,
+ feePortionBase18: constants.Zero,
+ };
+ }
+
+ let grossTokenOutAmount = (await loanSwapperVaultMToken.balanceOf(loanLp))
+ .mul(mTokenARate)
+ .div(tokenOutRate);
+
+ if (grossTokenOutAmount.gt(amountTokenOutBase18)) {
+ grossTokenOutAmount = amountTokenOutBase18;
+ }
+
+ if (grossTokenOutAmount.eq(0)) {
+ return {
+ amountReceivedBase18: constants.Zero,
+ feePortionBase18: constants.Zero,
+ };
+ }
+
+ const feePortionBase18 = truncateToTokenDecimals(
+ totalFee.mul(grossTokenOutAmount).div(totalAmount),
+ );
+
+ if (grossTokenOutAmount.eq(feePortionBase18)) {
+ return {
+ amountReceivedBase18: constants.Zero,
+ feePortionBase18,
+ };
+ }
+
+ return {
+ amountReceivedBase18: grossTokenOutAmount.sub(feePortionBase18),
+ feePortionBase18,
+ };
+ };
+
+ const preferLoanLiquidity = await redemptionVault.preferLoanLiquidity();
+ let usedLpLiquidityBase18 = constants.Zero;
+ let lpFeePortionBase18 = constants.Zero;
+
+ if (preferLoanLiquidity) {
+ ({
+ amountReceivedBase18: usedLpLiquidityBase18,
+ feePortionBase18: lpFeePortionBase18,
+ } = loanLiquidityExpectToFail
+ ? {
+ amountReceivedBase18: constants.Zero,
+ feePortionBase18: constants.Zero,
+ }
+ : await estimateUseLoanLpLiquidity(
+ totalAmountBase18,
+ totalAmountBase18,
+ feeAmountBase18,
+ ));
+ } else {
+ const obtainedVaultLiquidityBase18 = constants.Zero;
+ const newBalanceBase18 = balanceVaultBase18.add(
+ obtainedVaultLiquidityBase18,
+ );
+
+ if (newBalanceBase18.lt(totalAmountBase18)) {
+ ({
+ amountReceivedBase18: usedLpLiquidityBase18,
+ feePortionBase18: lpFeePortionBase18,
+ } = await estimateUseLoanLpLiquidity(
+ totalAmountBase18.sub(newBalanceBase18),
+ totalAmountBase18,
+ feeAmountBase18,
+ ));
+ }
+ }
+
+ const toTransferFromLpBase18 = usedLpLiquidityBase18;
+ const toTransferFromVaultBase18 = amountTokenOutWithoutFeeBase18.gte(
+ toTransferFromLpBase18,
+ )
+ ? amountTokenOutWithoutFeeBase18.sub(toTransferFromLpBase18)
+ : constants.Zero;
+ const vaultFeePortionBase18 = feeAmountBase18.sub(lpFeePortionBase18);
+ const toUseLpLiquidityBase18 = toTransferFromLpBase18.add(lpFeePortionBase18);
+ const toUseVaultLiquidityBase18 = totalAmountBase18.sub(
+ toUseLpLiquidityBase18,
+ );
+
+ const mTokenARate = loanSwapperVaultMTokenDataFeed
+ ? await loanSwapperVaultMTokenDataFeed.getDataInBase18()
+ : constants.Zero;
+ const mTokenAAmount =
+ toTransferFromLpBase18.eq(0) || mTokenARate.eq(0)
+ ? constants.Zero
+ : toTransferFromLpBase18
+ .mul(tokenOutRate)
+ .add(mTokenARate.sub(1))
+ .div(mTokenARate);
+
+ return {
+ toTransferFromVaultBase18,
+ toTransferFromLpBase18,
+ lpFeePortionBase18,
+ vaultFeePortionBase18,
+ toUseVaultLiquidityBase18,
+ toUseLpLiquidityBase18,
+ toTransferFromVault: toTransferFromVaultBase18.div(precision),
+ toTransferFromLpMToken: mTokenAAmount,
+ lpFeePortion: lpFeePortionBase18.div(precision),
+ vaultFeePortion: vaultFeePortionBase18.div(precision),
+ toUseVaultLiquidity: toUseVaultLiquidityBase18.div(precision),
+ toUseLpLiquidity: toUseLpLiquidityBase18.div(precision),
};
};
+
+export const expectedHoldbackPartRateFromAvg = (
+ amountMToken: BigNumberish,
+ amountMTokenInstant: BigNumberish,
+ mTokenRate: BigNumberish,
+ avgMTokenRate: BigNumberish,
+): BigNumberish => {
+ amountMToken = BigNumber.from(amountMToken).toBigInt();
+ amountMTokenInstant = BigNumber.from(amountMTokenInstant).toBigInt();
+ mTokenRate = BigNumber.from(mTokenRate).toBigInt();
+ avgMTokenRate = BigNumber.from(avgMTokenRate).toBigInt();
+
+ if (amountMTokenInstant === 0n) {
+ return 0n;
+ }
+
+ const targetTotalValue =
+ ((amountMToken + amountMTokenInstant) * avgMTokenRate) / 10n ** 18n;
+ const instantPartValue = (amountMTokenInstant * mTokenRate) / 10n ** 18n;
+ if (targetTotalValue <= instantPartValue) {
+ return 0n;
+ }
+ const holdbackPartValue = targetTotalValue - instantPartValue;
+ return (holdbackPartValue * 10n ** 18n) / amountMToken;
+};
diff --git a/test/common/timelock-manager.helpers.ts b/test/common/timelock-manager.helpers.ts
new file mode 100644
index 00000000..6684be0e
--- /dev/null
+++ b/test/common/timelock-manager.helpers.ts
@@ -0,0 +1,659 @@
+import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
+import { expect } from 'chai';
+import { BigNumber, BigNumberish, ethers } from 'ethers';
+
+import {
+ OptionalCommonParams,
+ asyncForEach,
+ getCurrentBlockTimestamp,
+ handleRevert,
+} from './common.helpers';
+
+import {
+ MidasAccessControl,
+ MidasAccessControlTimelockController,
+ MidasTimelockManager,
+} from '../../typechain-types';
+
+type CommonParamsTimelock = {
+ timelockManager: MidasTimelockManager;
+ accessControl: MidasAccessControl;
+ timelock: MidasAccessControlTimelockController;
+ owner: SignerWithAddress;
+};
+
+export const setMaxPendingOperationsPerProposerTester = async (
+ { timelockManager, owner }: CommonParamsTimelock,
+ maxPendingOperationsPerProposer: BigNumberish,
+ opt?: OptionalCommonParams,
+) => {
+ const from = opt?.from ?? owner;
+ const callFn = timelockManager
+ .connect(from)
+ .setMaxPendingOperationsPerProposer.bind(
+ this,
+ maxPendingOperationsPerProposer,
+ );
+
+ if (await handleRevert(callFn, timelockManager, opt)) {
+ return;
+ }
+
+ await expect(callFn())
+ .to.emit(
+ timelockManager,
+ timelockManager.interface.events[
+ 'SetMaxPendingOperationsPerProposer(uint256)'
+ ].name,
+ )
+ .withArgs(maxPendingOperationsPerProposer);
+
+ const actualMaxPendingOperationsPerProposer =
+ await timelockManager.maxPendingOperationsPerProposer();
+
+ expect(actualMaxPendingOperationsPerProposer).eq(
+ maxPendingOperationsPerProposer,
+ );
+};
+
+export const bulkScheduleTimelockOperationTester = async (
+ { timelockManager, timelock, owner }: CommonParamsTimelock,
+ target: string[],
+ data: string[],
+ { isSetCouncilOperation }: { isSetCouncilOperation?: boolean } = {},
+ opt?: OptionalCommonParams,
+) => {
+ const from = opt?.from ?? owner;
+ isSetCouncilOperation ??= false;
+
+ const params = target.map((target, index) => ({
+ target,
+ data: data[index],
+ }));
+
+ const callFn = timelockManager
+ .connect(from)
+ .bulkScheduleTimelockOperation.bind(this, params);
+
+ if (await handleRevert(callFn, timelockManager, opt)) {
+ return [];
+ }
+
+ const councilVersionBefore = await timelockManager.securityCouncilVersion();
+
+ const expectedOperationIds = await Promise.all(
+ target.map((operationTarget, index) =>
+ getExpectedOperationId(
+ timelockManager,
+ timelock,
+ operationTarget,
+ data[index],
+ ),
+ ),
+ );
+
+ const txPromise = callFn();
+ let bulkScheduleExpect = expect(txPromise);
+ for (const [index] of target.entries()) {
+ bulkScheduleExpect = bulkScheduleExpect.to
+ .emit(
+ timelockManager,
+ timelockManager.interface.events[
+ 'ScheduleTimelockOperation(address,bytes32)'
+ ].name,
+ )
+ .withArgs(from.address, expectedOperationIds[index]);
+ }
+ await bulkScheduleExpect;
+ const councilVersionAfter = await timelockManager.securityCouncilVersion();
+
+ expect(councilVersionAfter).to.be.equal(councilVersionBefore);
+
+ const operationIds: string[] = [];
+ await asyncForEach(target.entries(), async ([index, operationTarget]) => {
+ const operationData = data[index];
+ operationIds.push(
+ await validateOperationDetails({
+ timelockManager,
+ timelock,
+ from,
+ operationTarget,
+ operationData,
+ isSetCouncilOperation,
+ councilVersionBefore,
+ }),
+ );
+ });
+
+ return operationIds;
+};
+
+export const scheduleTimelockOperationTester = async (
+ { timelockManager, timelock, owner }: CommonParamsTimelock,
+ target: string,
+ data: string,
+ { isSetCouncilOperation }: { isSetCouncilOperation?: boolean } = {},
+ opt?: OptionalCommonParams,
+) => {
+ const from = opt?.from ?? owner;
+ isSetCouncilOperation ??= false;
+
+ const callFn = timelockManager
+ .connect(from)
+ .scheduleTimelockOperation.bind(this, { target, data });
+
+ if (await handleRevert(callFn, timelockManager, opt)) {
+ return [];
+ }
+
+ const councilVersionBefore = await timelockManager.securityCouncilVersion();
+
+ const expectedOperationId = await getExpectedOperationId(
+ timelockManager,
+ timelock,
+ target,
+ data,
+ );
+
+ const txPromise = callFn();
+ await expect(txPromise)
+ .to.emit(
+ timelockManager,
+ timelockManager.interface.events[
+ 'ScheduleTimelockOperation(address,bytes32)'
+ ].name,
+ )
+ .withArgs(from.address, expectedOperationId);
+ const councilVersionAfter = await timelockManager.securityCouncilVersion();
+
+ expect(councilVersionAfter).to.be.equal(councilVersionBefore);
+
+ return [
+ await validateOperationDetails({
+ timelockManager,
+ timelock,
+ from,
+ operationTarget: target,
+ operationData: data,
+ isSetCouncilOperation,
+ councilVersionBefore,
+ }),
+ ];
+};
+
+const getExpectedOperationId = async (
+ timelockManager: MidasTimelockManager,
+ timelock: MidasAccessControlTimelockController,
+ operationTarget: string,
+ operationData: string,
+) => {
+ const dataHash = getDataHash(operationTarget, operationData);
+ const dataHashIndex = await timelockManager.dataHashIndexes(dataHash);
+
+ return timelock.hashOperation(
+ operationTarget,
+ 0,
+ operationData,
+ ethers.constants.HashZero,
+ getTimelockSalt(dataHashIndex),
+ );
+};
+
+const validateOperationDetails = async ({
+ timelockManager,
+ timelock,
+ from,
+ operationTarget,
+ operationData,
+ isSetCouncilOperation,
+ councilVersionBefore,
+}: {
+ operationTarget: string;
+ operationData: string;
+ timelockManager: MidasTimelockManager;
+ timelock: MidasAccessControlTimelockController;
+ from: SignerWithAddress;
+ isSetCouncilOperation: boolean;
+ councilVersionBefore: BigNumber;
+}) => {
+ const blockTimestamp = await getCurrentBlockTimestamp();
+
+ const dataHash = getDataHash(operationTarget, operationData);
+
+ const dataHashIndex = await timelockManager.dataHashIndexes(dataHash);
+
+ const operationId = await timelock.hashOperation(
+ operationTarget,
+ 0,
+ operationData,
+ ethers.constants.HashZero,
+ getTimelockSalt(dataHashIndex),
+ );
+
+ expect(await timelock.isOperation(operationId)).to.be.true;
+ expect(await timelock.isOperationReady(operationId)).to.be.false;
+ expect(await timelock.isOperationDone(operationId)).to.be.false;
+ expect(await timelock.isOperationPending(operationId)).to.be.true;
+
+ const details = await timelockManager.getOperationDetails(operationId);
+ expect(details.status).to.be.equal(1);
+ expect(details.pauser).to.be.equal(ethers.constants.AddressZero);
+ expect(details.operationProposer).to.be.equal(from.address);
+ expect(details.dataHash).to.be.equal(dataHash);
+ expect(details.votesForExecution).to.be.equal(0);
+ expect(details.votesForVeto).to.be.equal(0);
+ expect(details.createdAt).to.be.equal(blockTimestamp);
+ expect(details.executionApprovedAt).to.be.equal(0);
+ expect(details.pausedAt).to.be.equal(0);
+ expect(details.pauseReasonCode).to.be.equal(0);
+ expect(details.councilVersion).to.be.equal(councilVersionBefore);
+ expect(details.isSetCouncilOperation).to.be.equal(isSetCouncilOperation);
+
+ return operationId;
+};
+
+export type ExecuteTimelockOperationOpt = OptionalCommonParams & {
+ revertOnFailure?: boolean;
+ expectExecutionSuccess?: boolean;
+};
+
+export const executeTimelockOperationTester = async (
+ { timelockManager, timelock, owner }: CommonParamsTimelock,
+ target: string,
+ data: string,
+ originalCaller: string,
+ opt?: ExecuteTimelockOperationOpt,
+) => {
+ const from = opt?.from ?? owner;
+ const revertOnFailure = opt?.revertOnFailure ?? true;
+ const expectExecutionSuccess = opt?.expectExecutionSuccess ?? true;
+
+ const callFn = timelockManager
+ .connect(from)
+ .executeTimelockOperation.bind(this, target, data, revertOnFailure);
+
+ if (await handleRevert(callFn, timelockManager, opt)) {
+ return;
+ }
+
+ const dataHash = getDataHash(target, data);
+
+ const dataHashIndexBefore = await timelockManager.dataHashIndexes(dataHash);
+
+ const operationId = await timelock.hashOperation(
+ target,
+ 0,
+ data,
+ ethers.constants.HashZero,
+ getTimelockSalt(dataHashIndexBefore),
+ );
+
+ const detailsBefore = await timelockManager.getOperationDetails(operationId);
+
+ const txPromise = callFn();
+ let txExpect = expect(txPromise)
+ .to.emit(
+ timelockManager,
+ timelockManager.interface.events[
+ 'ExecuteTimelockOperation(address,bytes32,bool)'
+ ].name,
+ )
+ .withArgs(from.address, operationId, expectExecutionSuccess);
+
+ if (!expectExecutionSuccess) {
+ txExpect = txExpect.to
+ .emit(timelock, timelock.interface.events['Cancelled(bytes32)'].name)
+ .withArgs(operationId);
+ }
+
+ await txExpect;
+
+ const dataHashIndexAfter = await timelockManager.dataHashIndexes(dataHash);
+
+ expect(dataHashIndexAfter).to.eq(dataHashIndexBefore.add(1));
+
+ if (expectExecutionSuccess) {
+ expect(await timelock.isOperation(operationId)).to.be.true;
+ expect(await timelock.isOperationReady(operationId)).to.be.false;
+ expect(await timelock.isOperationDone(operationId)).to.be.true;
+ expect(await timelock.isOperationPending(operationId)).to.be.false;
+ } else {
+ expect(await timelock.isOperation(operationId)).to.be.false;
+ expect(await timelock.isOperationReady(operationId)).to.be.false;
+ expect(await timelock.isOperationDone(operationId)).to.be.false;
+ expect(await timelock.isOperationPending(operationId)).to.be.false;
+ }
+
+ const detailsAfter = await timelockManager.getOperationDetails(operationId);
+
+ // Executed = 8, ExecutedWithFailure = 9
+ expect(detailsAfter.status).to.be.equal(expectExecutionSuccess ? 8 : 9);
+ expect(detailsAfter.pauser).to.be.equal(detailsBefore.pauser);
+ expect(detailsAfter.operationProposer).to.be.equal(
+ detailsBefore.operationProposer,
+ );
+ expect(detailsAfter.dataHash).to.be.equal(detailsBefore.dataHash);
+ expect(detailsAfter.votesForExecution).to.be.equal(
+ detailsBefore.votesForExecution,
+ );
+ expect(detailsAfter.votesForVeto).to.be.equal(detailsBefore.votesForVeto);
+ expect(detailsAfter.createdAt).to.be.equal(detailsBefore.createdAt);
+ expect(detailsAfter.executionApprovedAt).to.be.equal(
+ detailsBefore.executionApprovedAt,
+ );
+ expect(detailsAfter.pausedAt).to.be.equal(detailsBefore.pausedAt);
+ expect(detailsAfter.pauseReasonCode).to.be.equal(
+ detailsBefore.pauseReasonCode,
+ );
+ expect(detailsAfter.councilVersion).to.be.equal(detailsBefore.councilVersion);
+ expect(detailsAfter.isSetCouncilOperation).to.be.equal(
+ detailsBefore.isSetCouncilOperation,
+ );
+};
+
+export const setSecurityCouncilTest = async (
+ { timelockManager, owner }: CommonParamsTimelock,
+ members: string[],
+ opt?: OptionalCommonParams,
+) => {
+ const from = opt?.from ?? owner;
+ const callFn = timelockManager
+ .connect(from)
+ .setSecurityCouncil.bind(this, members);
+
+ if (await handleRevert(callFn, timelockManager, opt)) {
+ return;
+ }
+
+ const councilVersionBefore = await timelockManager.securityCouncilVersion();
+ const oldCouncilBefore = await timelockManager.getSecurityCouncilMembers(
+ councilVersionBefore,
+ );
+
+ await expect(callFn())
+ .to.emit(
+ timelockManager,
+ timelockManager.interface.events['SetSecurityCouncil(uint256,address[])']
+ .name,
+ )
+ .withArgs(councilVersionBefore.add(1), members);
+ const oldCouncilAfter = await timelockManager.getSecurityCouncilMembers(
+ councilVersionBefore,
+ );
+
+ const councilVersionAfter = await timelockManager.securityCouncilVersion();
+
+ expect(councilVersionAfter).to.be.equal(councilVersionBefore.add(1));
+ const currentCouncilAfter = await timelockManager.getSecurityCouncilMembers(
+ councilVersionAfter,
+ );
+ expect(currentCouncilAfter.length).to.be.equal(members.length);
+ for (const member of members) {
+ expect(currentCouncilAfter.includes(member)).to.be.true;
+ }
+
+ // check that old council is not changed
+ for (const member of oldCouncilBefore) {
+ expect(oldCouncilAfter.includes(member)).to.be.true;
+ }
+};
+
+export const pauseTimelockOperationTest = async (
+ { timelockManager, owner }: CommonParamsTimelock,
+ operationId: string,
+ pauseReasonCode: BigNumberish = 1,
+ opt?: OptionalCommonParams,
+) => {
+ const from = opt?.from ?? owner;
+ const callFn = timelockManager
+ .connect(from)
+ .pauseOperation.bind(this, operationId, pauseReasonCode);
+
+ if (await handleRevert(callFn, timelockManager, opt)) {
+ return;
+ }
+
+ const detailsBefore = await timelockManager.getOperationDetails(operationId);
+
+ await expect(callFn())
+ .to.emit(
+ timelockManager,
+ timelockManager.interface.events[
+ 'PauseTimelockOperation(address,bytes32,uint8,uint256)'
+ ].name,
+ )
+ .withArgs(
+ from.address,
+ operationId,
+ pauseReasonCode,
+ detailsBefore.councilVersion,
+ );
+
+ const details = await timelockManager.getOperationDetails(operationId);
+
+ expect(details.status).to.be.equal(2);
+ expect(details.pauser).to.be.equal(from.address);
+ expect(details.pauseReasonCode).to.be.equal(pauseReasonCode);
+ expect(details.pausedAt).to.be.equal(await getCurrentBlockTimestamp());
+ expect(details.councilVersion).to.be.equal(detailsBefore.councilVersion);
+ expect(details.operationProposer).to.be.equal(
+ detailsBefore.operationProposer,
+ );
+ expect(details.dataHash).to.be.equal(detailsBefore.dataHash);
+ expect(details.votesForExecution).to.be.equal(0);
+ expect(details.votesForVeto).to.be.equal(0);
+ expect(details.isSetCouncilOperation).to.be.equal(
+ detailsBefore.isSetCouncilOperation,
+ );
+ expect(details.createdAt).to.be.equal(detailsBefore.createdAt);
+ expect(details.executionApprovedAt).to.be.equal(0);
+};
+
+export const voteForVetoTest = async (
+ { timelockManager, owner }: CommonParamsTimelock,
+ operationId: string,
+ opt?: OptionalCommonParams,
+) => {
+ const from = opt?.from ?? owner;
+ const callFn = timelockManager
+ .connect(from)
+ .voteForVeto.bind(this, operationId);
+
+ if (await handleRevert(callFn, timelockManager, opt)) {
+ return;
+ }
+
+ const detailsBefore = await timelockManager.getOperationDetails(operationId);
+
+ await expect(callFn())
+ .to.emit(
+ timelockManager,
+ timelockManager.interface.events[
+ 'PausedProposalVoteCast(address,bytes32,bool)'
+ ].name,
+ )
+ .withArgs(from.address, operationId, false);
+
+ const quorum = await timelockManager.councilQuorum(
+ detailsBefore.councilVersion,
+ );
+
+ const details = await timelockManager.getOperationDetails(operationId);
+ expect(details.pauser).to.be.equal(detailsBefore.pauser);
+ expect(details.pauseReasonCode).to.be.equal(detailsBefore.pauseReasonCode);
+ expect(details.pausedAt).to.be.equal(detailsBefore.pausedAt);
+ expect(details.councilVersion).to.be.equal(detailsBefore.councilVersion);
+ expect(details.operationProposer).to.be.equal(
+ detailsBefore.operationProposer,
+ );
+ expect(details.dataHash).to.be.equal(detailsBefore.dataHash);
+ expect(details.votesForExecution).to.be.equal(
+ detailsBefore.votesForExecution,
+ );
+ expect(details.votesForVeto).to.be.equal(detailsBefore.votesForVeto + 1);
+
+ if (details.votesForVeto >= quorum) {
+ expect(details.status).to.be.equal(5);
+ } else {
+ expect(details.status).to.be.equal(detailsBefore.status);
+ }
+};
+
+export const voteForExecutionTest = async (
+ { timelockManager, owner }: CommonParamsTimelock,
+ operationId: string,
+ opt?: OptionalCommonParams,
+) => {
+ const from = opt?.from ?? owner;
+ const callFn = timelockManager
+ .connect(from)
+ .voteForExecution.bind(this, operationId);
+
+ if (await handleRevert(callFn, timelockManager, opt)) {
+ return;
+ }
+
+ const detailsBefore = await timelockManager.getOperationDetails(operationId);
+
+ await expect(callFn())
+ .to.emit(
+ timelockManager,
+ timelockManager.interface.events[
+ 'PausedProposalVoteCast(address,bytes32,bool)'
+ ].name,
+ )
+ .withArgs(from.address, operationId, true);
+
+ const quorum = await timelockManager.councilQuorum(
+ detailsBefore.councilVersion,
+ );
+
+ const details = await timelockManager.getOperationDetails(operationId);
+ expect(details.pauser).to.be.equal(detailsBefore.pauser);
+ expect(details.pauseReasonCode).to.be.equal(detailsBefore.pauseReasonCode);
+ expect(details.pausedAt).to.be.equal(detailsBefore.pausedAt);
+ expect(details.councilVersion).to.be.equal(detailsBefore.councilVersion);
+ expect(details.operationProposer).to.be.equal(
+ detailsBefore.operationProposer,
+ );
+ expect(details.dataHash).to.be.equal(detailsBefore.dataHash);
+ expect(details.votesForExecution).to.be.equal(
+ detailsBefore.votesForExecution + 1,
+ );
+ expect(details.votesForVeto).to.be.equal(detailsBefore.votesForVeto);
+
+ if (details.votesForExecution >= quorum) {
+ expect(details.status).to.be.equal(3);
+ expect(details.executionApprovedAt).to.be.equal(
+ await getCurrentBlockTimestamp(),
+ );
+ } else {
+ expect(details.status).to.be.equal(detailsBefore.status);
+ }
+};
+
+export const abortOperationTest = async (
+ { timelockManager, owner, timelock }: CommonParamsTimelock,
+ operationId: string,
+ opt?: OptionalCommonParams,
+) => {
+ const from = opt?.from ?? owner;
+ const callFn = timelockManager
+ .connect(from)
+ .abortOperation.bind(this, operationId);
+
+ if (await handleRevert(callFn, timelockManager, opt)) {
+ return;
+ }
+
+ const detailsBefore = await timelockManager.getOperationDetails(operationId);
+
+ const pendingSetCouncilOperationIdBefore =
+ await timelockManager.pendingSetCouncilOperationId();
+
+ const dataHashIndexBefore = await timelockManager.dataHashIndexes(
+ detailsBefore.dataHash,
+ );
+
+ const pendingOperationsBefore = await timelockManager.getPendingOperations();
+ const pendingOperationsPerProposerBefore =
+ await timelockManager.proposerPendingOperationsCount(
+ detailsBefore.operationProposer,
+ );
+ await expect(callFn())
+ .to.emit(
+ timelockManager,
+ timelockManager.interface.events[
+ 'AbortTimelockOperation(address,bytes32,uint8)'
+ ].name,
+ )
+ .withArgs(from.address, operationId, detailsBefore.status);
+
+ const pendingOperationsPerProposerAfter =
+ await timelockManager.proposerPendingOperationsCount(
+ detailsBefore.operationProposer,
+ );
+ const pendingOperationsAfter = await timelockManager.getPendingOperations();
+
+ expect(pendingOperationsAfter.length).to.be.equal(
+ pendingOperationsBefore.length - 1,
+ );
+ expect(pendingOperationsAfter.includes(operationId)).to.be.false;
+
+ expect(pendingOperationsPerProposerAfter).to.be.equal(
+ pendingOperationsPerProposerBefore.sub(1),
+ );
+
+ const dataHashIndexAfter = await timelockManager.dataHashIndexes(
+ detailsBefore.dataHash,
+ );
+
+ expect(dataHashIndexAfter).to.be.equal(dataHashIndexBefore.add(1));
+
+ const pendingSetCouncilOperationIdAfter =
+ await timelockManager.pendingSetCouncilOperationId();
+
+ const details = await timelockManager.getOperationDetails(operationId);
+
+ expect(details.status).to.be.equal(7);
+ expect(details.pauser).to.be.equal(detailsBefore.pauser);
+ expect(details.pauseReasonCode).to.be.equal(detailsBefore.pauseReasonCode);
+ expect(details.councilVersion).to.be.equal(detailsBefore.councilVersion);
+ expect(details.operationProposer).to.be.equal(
+ detailsBefore.operationProposer,
+ );
+ expect(details.dataHash).to.be.equal(detailsBefore.dataHash);
+ expect(details.votesForExecution).to.be.equal(
+ detailsBefore.votesForExecution,
+ );
+ expect(details.votesForVeto).to.be.equal(detailsBefore.votesForVeto);
+ expect(details.isSetCouncilOperation).to.be.equal(
+ detailsBefore.isSetCouncilOperation,
+ );
+ expect(details.createdAt).to.be.equal(detailsBefore.createdAt);
+ expect(details.executionApprovedAt).to.be.equal(
+ detailsBefore.executionApprovedAt,
+ );
+ expect(details.pausedAt).to.be.equal(detailsBefore.pausedAt);
+ expect(await timelock.isOperation(operationId)).to.be.false;
+
+ if (details.isSetCouncilOperation) {
+ expect(pendingSetCouncilOperationIdAfter).to.be.equal(
+ ethers.constants.HashZero,
+ );
+ } else {
+ expect(pendingSetCouncilOperationIdAfter).to.be.equal(
+ pendingSetCouncilOperationIdBefore,
+ );
+ }
+};
+
+const getTimelockSalt = (dataHashIndex: BigNumber) => {
+ return ethers.utils.hexZeroPad(dataHashIndex.toHexString(), 32);
+};
+const getDataHash = (target: string, data: string) => {
+ return ethers.utils.solidityKeccak256(
+ ['address', 'uint256', 'bytes'],
+ [target, 0, data],
+ );
+};
diff --git a/test/common/token.tests.ts b/test/common/token.tests.ts
deleted file mode 100644
index 5c9df789..00000000
--- a/test/common/token.tests.ts
+++ /dev/null
@@ -1,896 +0,0 @@
-import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
-import { expect } from 'chai';
-import { Contract } from 'ethers';
-import { parseUnits } from 'ethers/lib/utils';
-import { ethers } from 'hardhat';
-
-import { acErrors, blackList, unBlackList } from './ac.helpers';
-import { defaultDeploy } from './fixtures';
-import { burn, mint, setMetadataTest } from './mTBILL.helpers';
-
-import { MTokenName } from '../../config';
-import { getTokenContractNames } from '../../helpers/contracts';
-import { mTokensMetadata } from '../../helpers/mtokens-metadata';
-import {
- getAllRoles,
- getRolesForToken,
- getRolesNamesCommon,
- getRolesNamesForToken,
- tokenLevelGreenlistTokens,
-} from '../../helpers/roles';
-import {
- CustomAggregatorV3CompatibleFeed,
- CustomAggregatorV3CompatibleFeedGrowth,
- DataFeed,
- DepositVault,
- DepositVaultWithUSTB,
- MTBILL,
- RedemptionVault,
- RedemptionVaultWIthBUIDL,
- RedemptionVaultWithSwapper,
-} from '../../typechain-types';
-
-export const tokenContractsTests = (token: MTokenName) => {
- const contractNames = getTokenContractNames(token);
- const allRoles = getAllRoles();
- const allRoleNames = getRolesNamesCommon();
-
- const tokenRoles = getRolesForToken(token);
- const tokenRoleNames = getRolesNamesForToken(token);
-
- const isTac = token.startsWith('TAC');
- const contractNamesForTac = getTokenContractNames(
- token.replace('TAC', '') as MTokenName,
- );
-
- const getContractFactory = async (contract: string) => {
- return await ethers.getContractFactory(contract);
- };
-
- const deployProxyContract = async (
- contractKey: keyof Omit,
- initializer = 'initialize',
- ...initParams: unknown[]
- ) => {
- const shouldReplaceTacContracts =
- isTac &&
- (contractKey === 'customAggregator' || contractKey === 'dataFeed');
-
- const factory = await getContractFactory(
- shouldReplaceTacContracts
- ? contractNamesForTac[contractKey]!
- : contractNames[contractKey]!,
- );
-
- const impl = await factory.deploy();
-
- const proxy = await (
- await getContractFactory('ERC1967Proxy')
- ).deploy(
- impl.address,
- factory.interface.encodeFunctionData(initializer, initParams),
- );
-
- return factory.attach(proxy.address) as TContract;
- };
-
- const deployProxyContractIfExists = async <
- TContract extends Contract = Contract,
- >(
- contractKey: keyof Omit,
- initializer = 'initialize',
- ...initParams: unknown[]
- ) => {
- const shouldReplaceTacContracts =
- isTac &&
- (contractKey === 'customAggregator' || contractKey === 'dataFeed');
-
- const factory = await getContractFactory(
- shouldReplaceTacContracts
- ? contractNamesForTac[contractKey]!
- : contractNames[contractKey]!,
- ).catch((_) => {
- return null;
- });
-
- if (!factory) {
- return null;
- }
-
- const impl = await factory.deploy();
-
- const proxy = await (
- await getContractFactory('ERC1967Proxy')
- ).deploy(
- impl.address,
- factory.interface.encodeFunctionData(initializer, initParams),
- );
-
- return factory.attach(proxy.address) as TContract;
- };
-
- const deployMTokenWithFixture = async () => {
- const fixture = await loadFixture(defaultDeploy);
-
- const tokenContract = (await deployProxyContract(
- 'token',
- undefined,
- fixture.accessControl.address,
- )) as MTBILL;
-
- if (mTokensMetadata[token]?.isPermissioned) {
- const greenlistedRole = tokenRoles.greenlisted;
- for (const account of fixture.regularAccounts) {
- await fixture.accessControl
- .connect(fixture.owner)
- .grantRole(greenlistedRole, account.address);
- }
- }
-
- return { tokenContract, ...fixture };
- };
-
- const deployMTokenVaultsWithFixture = async () => {
- const { tokenContract, ...fixture } = await deployMTokenWithFixture();
- const customAggregatorFeed =
- await deployProxyContractIfExists(
- 'customAggregator',
- undefined,
- fixture.accessControl.address,
- 2,
- parseUnits('10000', 8),
- parseUnits('1', 8),
- 'Custom Data Feed',
- );
-
- const customAggregatorFeedGrowth =
- await deployProxyContractIfExists(
- 'customAggregatorGrowth',
- undefined,
- fixture.accessControl.address,
- 2,
- parseUnits('10000', 8),
- parseUnits('1', 8),
- parseUnits('0', 8),
- parseUnits('100', 8),
- false,
- 'Custom Data Feed',
- );
-
- await customAggregatorFeed?.setRoundData?.(parseUnits('1.01', 8));
- await customAggregatorFeedGrowth?.setRoundData?.(
- parseUnits('1.01', 8),
- await ethers.provider.getBlock('latest').then((block) => block.timestamp),
- 0,
- );
-
- const dataFeed = await deployProxyContract(
- 'dataFeed',
- undefined,
- fixture.accessControl.address,
- customAggregatorFeed?.address ?? customAggregatorFeedGrowth?.address,
- 3 * 24 * 3600,
- parseUnits('0.1', 8),
- parseUnits('10000', 8),
- );
-
- const depositVault = await deployProxyContractIfExists(
- 'dv',
- undefined,
- fixture.accessControl.address,
- {
- mToken: tokenContract.address,
- mTokenDataFeed: dataFeed.address,
- },
- {
- feeReceiver: fixture.feeReceiver.address,
- tokensReceiver: fixture.tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- fixture.mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- 0,
- 0,
- );
-
- const depositVaultUstb =
- await deployProxyContractIfExists(
- 'dvUstb',
- 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,uint256,uint256,address)',
- fixture.accessControl.address,
- {
- mToken: tokenContract.address,
- mTokenDataFeed: dataFeed.address,
- },
- {
- feeReceiver: fixture.feeReceiver.address,
- tokensReceiver: fixture.tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- fixture.mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- 0,
- 0,
- fixture.ustbToken.address,
- );
-
- const redemptionVault = await deployProxyContractIfExists(
- 'rv',
- undefined,
- fixture.accessControl.address,
- {
- mToken: tokenContract.address,
- mTokenDataFeed: dataFeed.address,
- },
- {
- feeReceiver: fixture.feeReceiver.address,
- tokensReceiver: fixture.tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- fixture.mockedSanctionsList.address,
- 1,
- 1000,
- {
- fiatAdditionalFee: 100,
- fiatFlatFee: parseUnits('1'),
- minFiatRedeemAmount: 1000,
- },
- fixture.requestRedeemer.address,
- );
-
- const redemptionVaultWithSwapper =
- await deployProxyContractIfExists(
- 'rvSwapper',
- 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,(uint256,uint256,uint256),address,address,address)',
- fixture.accessControl.address,
- {
- mToken: tokenContract.address,
- mTokenDataFeed: dataFeed.address,
- },
- {
- feeReceiver: fixture.feeReceiver.address,
- tokensReceiver: fixture.tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- fixture.mockedSanctionsList.address,
- 1,
- 1000,
- {
- fiatAdditionalFee: 100,
- fiatFlatFee: parseUnits('1'),
- minFiatRedeemAmount: 1000,
- },
- fixture.requestRedeemer.address,
- fixture.redemptionVault.address,
- fixture.liquidityProvider.address,
- );
-
- await redemptionVaultWithSwapper?.addWaivedFeeAccount(
- fixture.redemptionVault.address,
- );
-
- const redemptionVaultWithBuidl =
- await deployProxyContractIfExists(
- 'rvBuidl',
- 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,(uint256,uint256,uint256),address,address,uint256,uint256)',
- fixture.accessControl.address,
- {
- mToken: tokenContract.address,
- mTokenDataFeed: dataFeed.address,
- },
- {
- feeReceiver: fixture.feeReceiver.address,
- tokensReceiver: fixture.tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- fixture.mockedSanctionsList.address,
- 1,
- 1000,
- {
- fiatAdditionalFee: 100,
- fiatFlatFee: parseUnits('1'),
- minFiatRedeemAmount: 1000,
- },
- fixture.requestRedeemer.address,
- fixture.buidlRedemption.address,
- parseUnits('250000', 6),
- parseUnits('250000', 6),
- );
-
- return {
- ...fixture,
- tokenContract,
- tokenDataFeed: dataFeed,
- tokenCustomAggregatorFeed: customAggregatorFeed,
- tokenDepositVault: depositVault,
- tokenDepositVaultUstb: depositVaultUstb,
- tokenRedemptionVault: redemptionVault,
- tokenRedemptionVaultWithSwapper: redemptionVaultWithSwapper,
- tokenRedemptionVaultWithBuidl: redemptionVaultWithBuidl,
- tokenCustomAggregatorFeedGrowth: customAggregatorFeedGrowth,
- };
- };
-
- describe(`Token`, function () {
- it('deployment', async () => {
- const { tokenContract } = await deployMTokenWithFixture();
-
- const expected = mTokensMetadata[token];
- expect(await tokenContract.name()).eq(expected.name);
- expect(await tokenContract.symbol()).eq(expected.symbol);
-
- expect(await tokenContract.paused()).eq(false);
- });
-
- it('roles', async () => {
- const { tokenContract } = await deployMTokenWithFixture();
-
- const contract = tokenContract as Contract;
-
- expect(await contract[tokenRoleNames.burner]()).eq(tokenRoles.burner);
- expect(await contract[tokenRoleNames.minter]()).eq(tokenRoles.minter);
- expect(await contract[tokenRoleNames.pauser]()).eq(tokenRoles.pauser);
-
- expect(await contract[allRoleNames.defaultAdmin]()).eq(
- allRoles.common.defaultAdmin,
- );
- expect(await contract[allRoleNames.blacklistedOperator]()).eq(
- allRoles.common.blacklistedOperator,
- );
- expect(await contract[allRoleNames.greenlistedOperator]()).eq(
- allRoles.common.greenlistedOperator,
- );
- });
-
- it('initialize', async () => {
- const { tokenContract } = await deployMTokenWithFixture();
-
- await expect(
- tokenContract.initialize(ethers.constants.AddressZero),
- ).revertedWith('Initializable: contract is already initialized');
- });
-
- describe('pause()', () => {
- it('should fail: call from address without "token pauser" role', async () => {
- const { accessControl, regularAccounts, tokenContract } =
- await deployMTokenWithFixture();
-
- const caller = regularAccounts[0];
-
- await expect(tokenContract.connect(caller).pause()).revertedWith(
- acErrors.WMAC_HASNT_ROLE,
- );
- });
-
- it('should fail: call when already paused', async () => {
- const { accessControl, tokenContract, owner } =
- await deployMTokenWithFixture();
-
- await tokenContract.connect(owner).pause();
- await expect(tokenContract.connect(owner).pause()).revertedWith(
- `Pausable: paused`,
- );
- });
-
- it('call when unpaused', async () => {
- const { owner, tokenContract } = await deployMTokenWithFixture();
-
- expect(await tokenContract.paused()).eq(false);
- await expect(tokenContract.connect(owner).pause()).to.emit(
- tokenContract,
- tokenContract.interface.events['Paused(address)'].name,
- ).to.not.reverted;
- expect(await tokenContract.paused()).eq(true);
- });
- });
-
- describe('unpause()', () => {
- it('should fail: call from address without "token pauser" role', async () => {
- const { owner, tokenContract, regularAccounts } =
- await deployMTokenWithFixture();
-
- const caller = regularAccounts[0];
-
- await tokenContract.connect(owner).pause();
- await expect(tokenContract.connect(caller).unpause()).revertedWith(
- acErrors.WMAC_HASNT_ROLE,
- );
- });
-
- it('should fail: call when already paused', async () => {
- const { owner, tokenContract } = await deployMTokenWithFixture();
-
- await expect(tokenContract.connect(owner).unpause()).revertedWith(
- `Pausable: not paused`,
- );
- });
-
- it('call when paused', async () => {
- const { owner, tokenContract } = await deployMTokenWithFixture();
-
- expect(await tokenContract.paused()).eq(false);
- await tokenContract.connect(owner).pause();
- expect(await tokenContract.paused()).eq(true);
-
- await expect(tokenContract.connect(owner).unpause()).to.emit(
- tokenContract,
- tokenContract.interface.events['Unpaused(address)'].name,
- ).to.not.reverted;
-
- expect(await tokenContract.paused()).eq(false);
- });
- });
-
- describe('mint()', () => {
- it('should fail: call from address without "mint operator" role', async () => {
- const { owner, tokenContract, regularAccounts } =
- await deployMTokenWithFixture();
- const caller = regularAccounts[0];
-
- await mint({ tokenContract, owner }, owner, 0, {
- from: caller,
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- });
- });
-
- it('call from address with "mint operator" role', async () => {
- const { owner, tokenContract, regularAccounts } =
- await deployMTokenWithFixture();
-
- const amount = parseUnits('100');
- const to = regularAccounts[0].address;
-
- await mint({ tokenContract, owner }, to, amount);
- });
- });
-
- describe('burn()', () => {
- it('should fail: call from address without "burn operator" role', async () => {
- const { owner, tokenContract, regularAccounts } =
- await deployMTokenWithFixture();
-
- const caller = regularAccounts[0];
-
- await burn({ tokenContract, owner }, owner, 0, {
- from: caller,
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- });
- });
-
- it('should fail: call when user has insufficient balance', async () => {
- const { owner, tokenContract, regularAccounts } =
- await deployMTokenWithFixture();
-
- const amount = parseUnits('100');
- const to = regularAccounts[0].address;
-
- await burn({ tokenContract, owner }, to, amount, {
- revertMessage: 'ERC20: burn amount exceeds balance',
- });
- });
-
- it('call from address with "mint operator" role', async () => {
- const { owner, tokenContract, regularAccounts } =
- await deployMTokenWithFixture();
-
- const amount = parseUnits('100');
- const to = regularAccounts[0].address;
-
- await mint({ tokenContract, owner }, to, amount);
- await burn({ tokenContract, owner }, to, amount);
- });
- });
-
- describe('setMetadata()', () => {
- it('should fail: call from address without DEFAULT_ADMIN_ROLE role', async () => {
- const { owner, tokenContract, regularAccounts } =
- await deployMTokenWithFixture();
-
- const caller = regularAccounts[0];
-
- await setMetadataTest({ tokenContract, owner }, 'url', 'some value', {
- from: caller,
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- });
- });
-
- it('call from address with DEFAULT_ADMIN_ROLE role', async () => {
- const { owner, tokenContract } = await deployMTokenWithFixture();
-
- await setMetadataTest(
- { tokenContract, owner },
- 'url',
- 'some value',
- undefined,
- );
- });
- });
-
- describe('_beforeTokenTransfer()', () => {
- it('should fail: mint(...) when address is blacklisted', async () => {
- const { owner, regularAccounts, accessControl, tokenContract } =
- await deployMTokenWithFixture();
- const blacklisted = regularAccounts[0];
-
- await blackList(
- { blacklistable: tokenContract, accessControl, owner },
- blacklisted,
- );
- await mint({ tokenContract, owner }, blacklisted, 1, {
- revertMessage: acErrors.WMAC_HAS_ROLE,
- });
- });
-
- it('should fail: transfer(...) when from address is blacklisted', async () => {
- const { owner, regularAccounts, accessControl, tokenContract } =
- await deployMTokenWithFixture();
-
- const blacklisted = regularAccounts[0];
- const to = regularAccounts[1];
-
- await mint({ tokenContract, owner }, blacklisted, 1);
- await blackList(
- { blacklistable: tokenContract, accessControl, owner },
- blacklisted,
- );
-
- await expect(
- tokenContract.connect(blacklisted).transfer(to.address, 1),
- ).revertedWith(acErrors.WMAC_HAS_ROLE);
- });
-
- it('should fail: transfer(...) when to address is blacklisted', async () => {
- const { owner, regularAccounts, accessControl, tokenContract } =
- await deployMTokenWithFixture();
-
- const blacklisted = regularAccounts[0];
- const from = regularAccounts[1];
-
- await mint({ tokenContract, owner }, from, 1);
- await blackList(
- { blacklistable: tokenContract, accessControl, owner },
- blacklisted,
- );
-
- await expect(
- tokenContract.connect(from).transfer(blacklisted.address, 1),
- ).revertedWith(acErrors.WMAC_HAS_ROLE);
- });
-
- it('should fail: transferFrom(...) when from address is blacklisted', async () => {
- const { owner, regularAccounts, accessControl, tokenContract } =
- await deployMTokenWithFixture();
-
- const blacklisted = regularAccounts[0];
- const to = regularAccounts[1];
-
- await mint({ tokenContract, owner }, blacklisted, 1);
- await blackList(
- { blacklistable: tokenContract, accessControl, owner },
- blacklisted,
- );
-
- await tokenContract.connect(blacklisted).approve(to.address, 1);
-
- await expect(
- tokenContract
- .connect(to)
- .transferFrom(blacklisted.address, to.address, 1),
- ).revertedWith(acErrors.WMAC_HAS_ROLE);
- });
-
- it('should fail: transferFrom(...) when to address is blacklisted', async () => {
- const { owner, regularAccounts, accessControl, tokenContract } =
- await deployMTokenWithFixture();
-
- const blacklisted = regularAccounts[0];
- const from = regularAccounts[1];
- const caller = regularAccounts[2];
-
- await mint({ tokenContract, owner }, from, 1);
-
- await blackList(
- { blacklistable: tokenContract, accessControl, owner },
- blacklisted,
- );
- await tokenContract.connect(from).approve(caller.address, 1);
-
- await expect(
- tokenContract
- .connect(caller)
- .transferFrom(from.address, blacklisted.address, 1),
- ).revertedWith(acErrors.WMAC_HAS_ROLE);
- });
-
- it('should fail: burn(...) when address is blacklisted', async () => {
- const { owner, regularAccounts, accessControl, tokenContract } =
- await deployMTokenWithFixture();
-
- const blacklisted = regularAccounts[0];
-
- await mint({ tokenContract, owner }, blacklisted, 1);
- await blackList(
- { blacklistable: tokenContract, accessControl, owner },
- blacklisted,
- );
- await burn({ tokenContract, owner }, blacklisted, 1, {
- revertMessage: acErrors.WMAC_HAS_ROLE,
- });
- });
-
- it('burnGoverned(...) when address is blacklisted', async () => {
- const { owner, regularAccounts, accessControl, tokenContract } =
- await deployMTokenWithFixture();
-
- const blacklisted = regularAccounts[0];
-
- await mint({ tokenContract, owner }, blacklisted, 1);
- await blackList(
- { blacklistable: tokenContract, accessControl, owner },
- blacklisted,
- );
-
- const balanceBefore = await tokenContract.balanceOf(
- blacklisted.address,
- );
- await expect(
- tokenContract.connect(owner).burnGoverned(blacklisted.address, 1),
- ).to.not.reverted;
- const balanceAfter = await tokenContract.balanceOf(blacklisted.address);
- expect(balanceBefore.sub(balanceAfter)).eq(1);
- });
-
- it('should fail: burnGoverned(...) when caller lacks burner role', async () => {
- const { owner, regularAccounts, tokenContract } =
- await deployMTokenWithFixture();
-
- const unauthorized = regularAccounts[0];
- const target = regularAccounts[1];
-
- await mint({ tokenContract, owner }, target, 1);
-
- await expect(
- tokenContract.connect(unauthorized).burnGoverned(target.address, 1),
- ).revertedWith(acErrors.WMAC_HASNT_ROLE);
- });
-
- it('transferFrom(...) when caller address is blacklisted', async () => {
- const { owner, regularAccounts, accessControl, tokenContract } =
- await deployMTokenWithFixture();
-
- const blacklisted = regularAccounts[0];
- const from = regularAccounts[1];
- const to = regularAccounts[2];
-
- await mint({ tokenContract, owner }, from, 1);
- await blackList(
- { blacklistable: tokenContract, accessControl, owner },
- blacklisted,
- );
-
- await tokenContract.connect(from).approve(blacklisted.address, 1);
-
- await expect(
- tokenContract
- .connect(blacklisted)
- .transferFrom(from.address, to.address, 1),
- ).not.reverted;
- });
-
- it('transfer(...) when caller address was blacklisted and then un-blacklisted', async () => {
- const { owner, regularAccounts, accessControl, tokenContract } =
- await deployMTokenWithFixture();
-
- const blacklisted = regularAccounts[0];
- const to = regularAccounts[2];
-
- await mint({ tokenContract, owner }, blacklisted, 1);
- await blackList(
- { blacklistable: tokenContract, accessControl, owner },
- blacklisted,
- );
-
- await expect(
- tokenContract.connect(blacklisted).transfer(to.address, 1),
- ).revertedWith(acErrors.WMAC_HAS_ROLE);
-
- await unBlackList(
- { blacklistable: tokenContract, accessControl, owner },
- blacklisted,
- );
-
- await expect(tokenContract.connect(blacklisted).transfer(to.address, 1))
- .not.reverted;
- });
- });
- });
- describe('roles check', () => {
- it('DataFeed', async function () {
- const fixture = await deployMTokenVaultsWithFixture();
- const dataFeed = fixture.tokenDataFeed as Contract;
-
- if (!dataFeed || !tokenRoleNames.customFeedAdmin || isTac) {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (this as any).skip();
- return;
- }
-
- expect(await dataFeed.feedAdminRole()).eq(
- await dataFeed[tokenRoleNames.customFeedAdmin](),
- );
- expect(await dataFeed.feedAdminRole()).eq(tokenRoles.customFeedAdmin);
- });
-
- it('CustomAggregator', async function () {
- const fixture = await deployMTokenVaultsWithFixture();
- const customAggregator = (fixture.tokenCustomAggregatorFeed ??
- fixture.tokenCustomAggregatorFeedGrowth) as Contract;
-
- if (!customAggregator || !tokenRoleNames.customFeedAdmin || isTac) {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (this as any).skip();
- return;
- }
-
- expect(await customAggregator.feedAdminRole()).eq(
- await customAggregator[tokenRoleNames.customFeedAdmin](),
- );
- expect(await customAggregator.feedAdminRole()).eq(
- tokenRoles.customFeedAdmin,
- );
- });
-
- it('DepositVault', async function () {
- const fixture = await deployMTokenVaultsWithFixture();
- const depositVault = fixture.tokenDepositVault as Contract;
-
- if (!depositVault) {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (this as any).skip();
- return;
- }
-
- expect(await depositVault.vaultRole()).eq(
- token === 'mTBILL'
- ? tokenRoles.depositVaultAdmin
- : await depositVault[tokenRoleNames.depositVaultAdmin](),
- );
- expect(await depositVault.vaultRole()).eq(tokenRoles.depositVaultAdmin);
- });
-
- it('DepositVaultWithUSTB', async function () {
- const fixture = await deployMTokenVaultsWithFixture();
- const depositVaultUstb = fixture.tokenDepositVaultUstb as Contract;
-
- if (!depositVaultUstb) {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (this as any).skip();
- return;
- }
-
- expect(await depositVaultUstb.vaultRole()).eq(
- token === 'mTBILL'
- ? tokenRoles.depositVaultAdmin
- : await depositVaultUstb[tokenRoleNames.depositVaultAdmin](),
- );
- expect(await depositVaultUstb.vaultRole()).eq(
- tokenRoles.depositVaultAdmin,
- );
- });
-
- it('RedemptionVault', async function () {
- const fixture = await deployMTokenVaultsWithFixture();
- const redemptionVault = fixture.tokenRedemptionVault as Contract;
-
- if (!redemptionVault) {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (this as any).skip();
- return;
- }
-
- expect(await redemptionVault.vaultRole()).eq(
- token === 'mTBILL'
- ? tokenRoles.redemptionVaultAdmin
- : await redemptionVault[tokenRoleNames.redemptionVaultAdmin](),
- );
- expect(await redemptionVault.vaultRole()).eq(
- tokenRoles.redemptionVaultAdmin,
- );
- });
-
- it('RedemptionVaultWithSwapper', async function () {
- const fixture = await deployMTokenVaultsWithFixture();
- const redemptionVaultWithSwapper =
- fixture.tokenRedemptionVaultWithSwapper as Contract;
-
- if (!redemptionVaultWithSwapper) {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (this as any).skip();
- return;
- }
-
- expect(await redemptionVaultWithSwapper.vaultRole()).eq(
- token === 'mTBILL'
- ? tokenRoles.redemptionVaultAdmin
- : await redemptionVaultWithSwapper[
- tokenRoleNames.redemptionVaultAdmin
- ](),
- );
- expect(await redemptionVaultWithSwapper.vaultRole()).eq(
- tokenRoles.redemptionVaultAdmin,
- );
- });
-
- it('RedemptionVaultWithBUIDL', async function () {
- const fixture = await deployMTokenVaultsWithFixture();
- const redemptionVaultWithBuidl =
- fixture.tokenRedemptionVaultWithBuidl as Contract;
-
- if (!redemptionVaultWithBuidl) {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (this as any).skip();
- return;
- }
-
- expect(await redemptionVaultWithBuidl.vaultRole()).eq(
- token === 'mTBILL'
- ? tokenRoles.redemptionVaultAdmin
- : await redemptionVaultWithBuidl[
- tokenRoleNames.redemptionVaultAdmin
- ](),
- );
- expect(await redemptionVaultWithBuidl.vaultRole()).eq(
- tokenRoles.redemptionVaultAdmin,
- );
- });
-
- it('vaults greenlistedRole()', async function () {
- const fixture = await deployMTokenVaultsWithFixture();
-
- const vaults = (
- [
- fixture.tokenDepositVault,
- fixture.tokenDepositVaultUstb,
- fixture.tokenRedemptionVault,
- fixture.tokenRedemptionVaultWithSwapper,
- fixture.tokenRedemptionVaultWithBuidl,
- ] as (Contract | null)[]
- ).filter((v): v is Contract => !!v);
-
- if (vaults.length === 0) {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (this as any).skip();
- return;
- }
-
- // Token-level (separated) greenlist products override greenlistedRole()
- // to return their configured role (mGLO reuses mGLOBAL's
- // M_GLOBAL_GREENLISTED_ROLE via sharedGreenlistRoleSource); every other
- // product inherits the shared common GREENLISTED_ROLE.
- const expectedGreenlistedRole = tokenLevelGreenlistTokens.includes(token)
- ? tokenRoles.greenlisted
- : allRoles.common.greenlisted;
-
- for (const vault of vaults) {
- expect(await vault.greenlistedRole()).eq(expectedGreenlistedRole);
- }
- });
- });
-};
diff --git a/test/common/vault-initializer.helpers.ts b/test/common/vault-initializer.helpers.ts
new file mode 100644
index 00000000..1c716fa6
--- /dev/null
+++ b/test/common/vault-initializer.helpers.ts
@@ -0,0 +1,470 @@
+import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
+import { expect } from 'chai';
+import { BigNumberish, constants, Contract, ContractTransaction } from 'ethers';
+import { ethers } from 'hardhat';
+
+import {
+ AccountOrContract,
+ getAccount,
+ handleRevert,
+ OptionalCommonParams,
+} from './common.helpers';
+
+import {
+ DepositVaultTest,
+ DepositVaultTest__factory,
+ DepositVaultWithAaveTest,
+ DepositVaultWithAaveTest__factory,
+ DepositVaultWithMorphoTest,
+ DepositVaultWithMorphoTest__factory,
+ DepositVaultWithMTokenTest,
+ DepositVaultWithMTokenTest__factory,
+ DepositVaultWithUSTBTest,
+ DepositVaultWithUSTBTest__factory,
+ ManageableVaultTester,
+ ManageableVaultTester__factory,
+ RedemptionVaultTest,
+ RedemptionVaultTest__factory,
+ RedemptionVaultWithAaveTest,
+ RedemptionVaultWithAaveTest__factory,
+ RedemptionVaultWithMorphoTest,
+ RedemptionVaultWithMorphoTest__factory,
+ RedemptionVaultWithMTokenTest,
+ RedemptionVaultWithMTokenTest__factory,
+ RedemptionVaultWithUSTBTest,
+ RedemptionVaultWithUSTBTest__factory,
+} from '../../typechain-types';
+
+export const DV_USTB_INIT_FN =
+ 'initialize((uint256,uint256,uint256,address,address,address,address,address,uint256,uint256,uint256,uint256,bool),(uint256,uint256),address)';
+
+export const DV_MTOKEN_INIT_FN = DV_USTB_INIT_FN;
+
+export const RV_USTB_INIT_FN =
+ 'initialize((uint256,uint256,uint256,address,address,address,address,address,uint256,uint256,uint256,uint256,bool),(address,address,address,address,uint256),address)';
+
+export const RV_MTOKEN_INIT_FN = RV_USTB_INIT_FN;
+
+export type InitializerParamsMv = {
+ accessControl: AccountOrContract;
+ mockedSanctionsList: AccountOrContract;
+ mTBILL: AccountOrContract;
+ mTokenToUsdDataFeed: AccountOrContract;
+ tokensReceiver: AccountOrContract;
+ minAmount?: BigNumberish;
+ instantFee?: BigNumberish;
+ minInstantFee?: BigNumberish;
+ maxInstantFee?: BigNumberish;
+ maxInstantShare?: BigNumberish;
+ variationTolerance?: BigNumberish;
+ sequentialRequestProcessing?: boolean;
+ maxApproveRequestId?: BigNumberish;
+};
+
+export type InitializerParamsDv = {
+ maxAmountPerRequest?: BigNumberish;
+ minMTokenAmountForFirstDeposit?: BigNumberish;
+} & InitializerParamsMv;
+
+export type InitializerParamsDvWithUstb = {
+ ustbToken: AccountOrContract;
+} & InitializerParamsDv;
+
+export type InitializerParamsDvWithMToken = {
+ depositVault: AccountOrContract;
+} & InitializerParamsDv;
+
+export type InitializerParamsRv = {
+ requestRedeemer: AccountOrContract;
+ loanLp?: AccountOrContract;
+ loanRepaymentAddress?: AccountOrContract;
+ redemptionVaultLoanSwapper?: AccountOrContract;
+ loanApr?: BigNumberish;
+} & InitializerParamsMv;
+
+export type InitializerParamsRvWithMToken = {
+ redemptionVault: AccountOrContract;
+} & InitializerParamsRv;
+
+export type InitializerParamsRvWithUstb = {
+ ustbRedemption: AccountOrContract;
+} & InitializerParamsRv;
+
+const resolveDeployer = async (opt?: OptionalCommonParams) => {
+ if (opt?.from) {
+ return opt.from;
+ }
+
+ const [deployer] = await ethers.getSigners();
+ return deployer;
+};
+
+const initializeContract = async (
+ contract: TContract,
+ initFn: () => Promise,
+ opt?: OptionalCommonParams,
+): Promise => {
+ if (await handleRevert(initFn, contract as Contract, opt)) {
+ return contract;
+ }
+
+ await expect(initFn()).to.not.be.reverted;
+ return contract;
+};
+
+const deployIfNeeded = async (
+ contract: TContract | undefined,
+ deploy: (deployer: SignerWithAddress) => Promise,
+ opt?: OptionalCommonParams,
+): Promise<{ contract: TContract; deployer: SignerWithAddress }> => {
+ const deployer = await resolveDeployer(opt);
+
+ if (contract) {
+ return { contract, deployer };
+ }
+
+ return {
+ contract: await deploy(deployer),
+ deployer,
+ };
+};
+
+export const getInitializerParamsMv = ({
+ accessControl,
+ mockedSanctionsList,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ tokensReceiver,
+ minAmount,
+ instantFee,
+ minInstantFee,
+ maxInstantFee,
+ maxInstantShare,
+ variationTolerance,
+ sequentialRequestProcessing,
+ maxApproveRequestId,
+}: InitializerParamsMv) => {
+ return [
+ {
+ ac: getAccount(accessControl),
+ sanctionsList: getAccount(mockedSanctionsList),
+ variationTolerance: variationTolerance ?? 1,
+ minAmount: minAmount ?? 1000,
+ mToken: getAccount(mTBILL),
+ mTokenDataFeed: getAccount(mTokenToUsdDataFeed),
+ tokensReceiver: getAccount(tokensReceiver),
+ instantFee: instantFee ?? 100,
+ minInstantFee: minInstantFee ?? 0,
+ maxInstantFee: maxInstantFee ?? 10000,
+ maxInstantShare: maxInstantShare ?? 100_00,
+ sequentialRequestProcessing: sequentialRequestProcessing ?? false,
+ maxApproveRequestId: maxApproveRequestId ?? 100,
+ },
+ ] as const;
+};
+
+export const getInitializerParamsRv = (
+ {
+ requestRedeemer,
+ loanLp,
+ loanRepaymentAddress,
+ redemptionVaultLoanSwapper,
+ loanApr,
+ ...commonParams
+ }: InitializerParamsRv,
+ extraParams?: TExtraParams,
+) => {
+ return [
+ ...getInitializerParamsMv(commonParams),
+ {
+ requestRedeemer: getAccount(requestRedeemer),
+ loanLp: getAccount(loanLp ?? constants.AddressZero),
+ loanRepaymentAddress: getAccount(
+ loanRepaymentAddress ?? constants.AddressZero,
+ ),
+ loanSwapperVault: getAccount(
+ redemptionVaultLoanSwapper ?? constants.AddressZero,
+ ),
+ loanApr: loanApr ?? 0,
+ },
+ ...(extraParams ?? []),
+ ] as const;
+};
+
+export const getInitializerParamsRvWithMToken = ({
+ redemptionVault,
+ ...commonParams
+}: InitializerParamsRvWithMToken) => {
+ return [
+ ...getInitializerParamsRv(commonParams),
+ getAccount(redemptionVault),
+ ] as const;
+};
+
+export const getInitializerParamsRvWithUstb = ({
+ ustbRedemption,
+ ...commonParams
+}: InitializerParamsRvWithUstb) => {
+ return [
+ ...getInitializerParamsRv(commonParams),
+ getAccount(ustbRedemption),
+ ] as const;
+};
+
+export const getInitializerParamsDv = ({
+ maxAmountPerRequest,
+ minMTokenAmountForFirstDeposit,
+ ...commonParams
+}: InitializerParamsDv) => {
+ return [
+ ...getInitializerParamsMv(commonParams),
+ {
+ minMTokenAmountForFirstDeposit: minMTokenAmountForFirstDeposit ?? 0,
+ maxAmountPerRequest: maxAmountPerRequest ?? constants.MaxUint256,
+ },
+ ] as const;
+};
+
+export const getInitializerParamsDvWithUstb = ({
+ ustbToken,
+ ...commonParams
+}: InitializerParamsDvWithUstb) => {
+ return [
+ ...getInitializerParamsDv(commonParams),
+ getAccount(ustbToken),
+ ] as const;
+};
+
+export const getInitializerParamsDvWithMToken = ({
+ depositVault,
+ ...commonParams
+}: InitializerParamsDvWithMToken) => {
+ return [
+ ...getInitializerParamsDv(commonParams),
+ getAccount(depositVault),
+ ] as const;
+};
+
+export const initializeMv = async (
+ params: InitializerParamsMv,
+ contract?: ManageableVaultTester,
+ opt?: OptionalCommonParams,
+): Promise => {
+ const { contract: vault, deployer } = await deployIfNeeded(
+ contract,
+ (deployer) => new ManageableVaultTester__factory(deployer).deploy(),
+ opt,
+ );
+ const initParams = getInitializerParamsMv(params);
+
+ return initializeContract(
+ vault,
+ () =>
+ vault.connect(opt?.from ?? deployer).initializeExternal(...initParams),
+ opt,
+ );
+};
+
+const initializeStandardDv = async <
+ TContract extends
+ | DepositVaultTest
+ | DepositVaultWithAaveTest
+ | DepositVaultWithMorphoTest,
+>(
+ params: InitializerParamsDv,
+ contract: TContract | undefined,
+ deploy: (deployer: SignerWithAddress) => Promise,
+ opt?: OptionalCommonParams,
+): Promise => {
+ const { contract: vault, deployer } = await deployIfNeeded(
+ contract,
+ deploy,
+ opt,
+ );
+ const from = opt?.from ?? deployer;
+ const initParams = getInitializerParamsDv(params);
+
+ return initializeContract(
+ vault,
+ () => vault.connect(from).initialize(...initParams),
+ opt,
+ );
+};
+
+export const initializeDv = (
+ params: InitializerParamsDv,
+ contract?: DepositVaultTest,
+ opt?: OptionalCommonParams,
+) =>
+ initializeStandardDv(
+ params,
+ contract,
+ (deployer) => new DepositVaultTest__factory(deployer).deploy(),
+ opt,
+ );
+
+export const initializeDvWithAave = (
+ params: InitializerParamsDv,
+ contract?: DepositVaultWithAaveTest,
+ opt?: OptionalCommonParams,
+) =>
+ initializeStandardDv(
+ params,
+ contract,
+ (deployer) => new DepositVaultWithAaveTest__factory(deployer).deploy(),
+ opt,
+ );
+
+export const initializeDvWithMorpho = (
+ params: InitializerParamsDv,
+ contract?: DepositVaultWithMorphoTest,
+ opt?: OptionalCommonParams,
+) =>
+ initializeStandardDv(
+ params,
+ contract,
+ (deployer) => new DepositVaultWithMorphoTest__factory(deployer).deploy(),
+ opt,
+ );
+
+export const initializeDvWithUstb = async (
+ params: InitializerParamsDvWithUstb,
+ contract?: DepositVaultWithUSTBTest,
+ opt?: OptionalCommonParams,
+): Promise => {
+ const { contract: vault, deployer } = await deployIfNeeded(
+ contract,
+ (deployer) => new DepositVaultWithUSTBTest__factory(deployer).deploy(),
+ opt,
+ );
+ const from = opt?.from ?? deployer;
+ const initParams = getInitializerParamsDvWithUstb(params);
+
+ return initializeContract(
+ vault,
+ () => vault.connect(from)[DV_USTB_INIT_FN](...initParams),
+ opt,
+ );
+};
+
+export const initializeDvWithMToken = async (
+ params: InitializerParamsDvWithMToken,
+ contract?: DepositVaultWithMTokenTest,
+ opt?: OptionalCommonParams,
+): Promise => {
+ const { contract: vault, deployer } = await deployIfNeeded(
+ contract,
+ (deployer) => new DepositVaultWithMTokenTest__factory(deployer).deploy(),
+ opt,
+ );
+ const from = opt?.from ?? deployer;
+ const initParams = getInitializerParamsDvWithMToken(params);
+
+ return initializeContract(
+ vault,
+ () => vault.connect(from)[DV_MTOKEN_INIT_FN](...initParams),
+ opt,
+ );
+};
+
+const initializeStandardRv = async <
+ TContract extends
+ | RedemptionVaultTest
+ | RedemptionVaultWithAaveTest
+ | RedemptionVaultWithMorphoTest,
+>(
+ params: InitializerParamsRv,
+ contract: TContract | undefined,
+ deploy: (deployer: SignerWithAddress) => Promise,
+ opt?: OptionalCommonParams,
+): Promise => {
+ const { contract: vault, deployer } = await deployIfNeeded(
+ contract,
+ deploy,
+ opt,
+ );
+ const from = opt?.from ?? deployer;
+ const initParams = getInitializerParamsRv(params);
+
+ return initializeContract(
+ vault,
+ () => vault.connect(from).initialize(...initParams),
+ opt,
+ );
+};
+
+export const initializeRv = (
+ params: InitializerParamsRv,
+ contract?: RedemptionVaultTest,
+ opt?: OptionalCommonParams,
+) =>
+ initializeStandardRv(
+ params,
+ contract,
+ (deployer) => new RedemptionVaultTest__factory(deployer).deploy(),
+ opt,
+ );
+
+export const initializeRvWithAave = (
+ params: InitializerParamsRv,
+ contract?: RedemptionVaultWithAaveTest,
+ opt?: OptionalCommonParams,
+) =>
+ initializeStandardRv(
+ params,
+ contract,
+ (deployer) => new RedemptionVaultWithAaveTest__factory(deployer).deploy(),
+ opt,
+ );
+
+export const initializeRvWithMorpho = (
+ params: InitializerParamsRv,
+ contract?: RedemptionVaultWithMorphoTest,
+ opt?: OptionalCommonParams,
+) =>
+ initializeStandardRv(
+ params,
+ contract,
+ (deployer) => new RedemptionVaultWithMorphoTest__factory(deployer).deploy(),
+ opt,
+ );
+
+export const initializeRvWithUstb = async (
+ params: InitializerParamsRvWithUstb,
+ contract?: RedemptionVaultWithUSTBTest,
+ opt?: OptionalCommonParams,
+): Promise => {
+ const { contract: vault, deployer } = await deployIfNeeded(
+ contract,
+ (deployer) => new RedemptionVaultWithUSTBTest__factory(deployer).deploy(),
+ opt,
+ );
+ const from = opt?.from ?? deployer;
+ const initParams = getInitializerParamsRvWithUstb(params);
+
+ return initializeContract(
+ vault,
+ () => vault.connect(from)[RV_USTB_INIT_FN](...initParams),
+ opt,
+ );
+};
+
+export const initializeRvWithMToken = async (
+ params: InitializerParamsRvWithMToken,
+ contract?: RedemptionVaultWithMTokenTest,
+ opt?: OptionalCommonParams,
+): Promise => {
+ const { contract: vault, deployer } = await deployIfNeeded(
+ contract,
+ (deployer) => new RedemptionVaultWithMTokenTest__factory(deployer).deploy(),
+ opt,
+ );
+ const from = opt?.from ?? deployer;
+ const initParams = getInitializerParamsRvWithMToken(params);
+
+ return initializeContract(
+ vault,
+ () => vault.connect(from)[RV_MTOKEN_INIT_FN](...initParams),
+ opt,
+ );
+};
diff --git a/test/common/with-sanctions-list.helpers.ts b/test/common/with-sanctions-list.helpers.ts
index 55271586..4842ff00 100644
--- a/test/common/with-sanctions-list.helpers.ts
+++ b/test/common/with-sanctions-list.helpers.ts
@@ -1,7 +1,12 @@
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { expect } from 'chai';
-import { Account, OptionalCommonParams, getAccount } from './common.helpers';
+import {
+ Account,
+ OptionalCommonParams,
+ getAccount,
+ handleRevert,
+} from './common.helpers';
import { SanctionsListMock, WithSanctionsList } from '../../typechain-types';
@@ -28,26 +33,27 @@ export const setSanctionsList = async (
newSanctionsList: Account,
opt?: OptionalCommonParams,
) => {
+ const sender = opt?.from ?? owner;
newSanctionsList = getAccount(newSanctionsList);
- if (opt?.revertMessage) {
- await expect(
+ if (
+ await handleRevert(
withSanctionsList
- .connect(opt?.from ?? owner)
- .setSanctionsList(newSanctionsList),
- ).revertedWith(opt?.revertMessage);
+ .connect(sender)
+ .setSanctionsList.bind(this, newSanctionsList),
+ withSanctionsList,
+ opt,
+ )
+ ) {
return;
}
await expect(
- withSanctionsList
- .connect(opt?.from ?? owner)
- .setSanctionsList(newSanctionsList),
+ withSanctionsList.connect(sender).setSanctionsList(newSanctionsList),
).to.emit(
withSanctionsList,
- withSanctionsList.interface.events['SetSanctionsList(address,address)']
- .name,
- ).to.not.reverted;
+ withSanctionsList.interface.events['SetSanctionsList(address)'].name,
+ );
expect(await withSanctionsList.sanctionsList()).eq(newSanctionsList);
};
diff --git a/test/integration/ChainlinkAdapters.test.ts b/test/integration/ChainlinkAdapters.test.ts
index d685f59d..ff9f6b88 100644
--- a/test/integration/ChainlinkAdapters.test.ts
+++ b/test/integration/ChainlinkAdapters.test.ts
@@ -17,7 +17,10 @@ for (const networkKey in configsPerNetwork) {
const config = configsPerNetwork[network as Network]!;
describe(`Chainlink Adapters on ${network}`, function () {
- this.timeout(120000);
+ // Each adapter's first fixture load resets the mainnet fork, which can take
+ // ~2min over RPC on its own; give the suite headroom so slow forks (e.g. the
+ // Syrup adapters) don't flake on the timeout.
+ this.timeout(300000);
if (config.syrupAdapters?.length) {
syrupAdaptersSuits(
diff --git a/test/integration/ContractsUpgrade.test.ts b/test/integration/ContractsUpgrade.test.ts
index bd403410..7c91d644 100644
--- a/test/integration/ContractsUpgrade.test.ts
+++ b/test/integration/ContractsUpgrade.test.ts
@@ -1,412 +1,1935 @@
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
+import { increase } from '@nomicfoundation/hardhat-network-helpers/dist/src/helpers/time';
+import { days } from '@nomicfoundation/hardhat-network-helpers/dist/src/helpers/time/duration';
+import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
+import { expect } from 'chai';
+import { BytesLike, constants } from 'ethers';
import { parseUnits } from 'ethers/lib/utils';
+import { ethers } from 'hardhat';
-import { hyperEvmUpgradeFixture } from './fixtures/upgrades.fixture';
+import { mainnetUpgradeFixture } from './fixtures/upgrades.fixture';
-import { approveBase18 } from '../common/common.helpers';
+import { getAllRoles, getRolesForToken } from '../../helpers/roles';
import {
- depositInstantTest,
- depositRequestTest,
- safeApproveRequestTest,
- safeBulkApproveRequestTest as safeBulkApproveDepositRequestTest,
-} from '../common/deposit-vault.helpers';
-import { mint } from '../common/mTBILL.helpers';
+ AggregatorV3Mock__factory,
+ MidasAccessControl,
+ MidasAccessControlTimelockController,
+ MidasTimelockManager,
+} from '../../typechain-types';
+import { acErrors, grantRoleMultTester } from '../common/ac.helpers';
+import { pauseGlobalTest } from '../common/common.helpers';
+import { burn, mint } from '../common/mtoken.helpers';
import {
- redeemInstantTest,
- redeemRequestTest,
- safeApproveRedeemRequestTest,
- safeBulkApproveRequestTest as safeBulkApproveRedeemRequestTest,
-} from '../common/redemption-vault.helpers';
+ executeTimelockOperationTester,
+ bulkScheduleTimelockOperationTester,
+} from '../common/timelock-manager.helpers';
-describe.skip('ContractsUpgrade - HyperEVM Fork Integration Tests', function () {
+describe('ContractsUpgrade - Mainnet Upgrade Integration Tests', function () {
this.timeout(120000);
- it('deposit instant', async function () {
- const {
- xaut0Whale,
- xaut0,
- vaultsManager,
- depositVault,
- xbxaut,
- xbxautDataFeed,
- } = await loadFixture(hyperEvmUpgradeFixture);
-
- await approveBase18(xaut0Whale, xaut0, depositVault, 1);
-
- await depositInstantTest(
- {
- mTBILL: xbxaut,
- mTokenToUsdDataFeed: xbxautDataFeed,
- depositVault,
- owner: vaultsManager,
- },
- xaut0,
- 1,
- { from: xaut0Whale },
- );
- });
+ const resetTimelockDelays = async ({
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ }: {
+ timelockManager: MidasTimelockManager;
+ acDefaultAdmin: SignerWithAddress;
+ accessControl: MidasAccessControl;
+ timelock: MidasAccessControlTimelockController;
+ }) => {
+ const roles = getAllRoles();
+
+ const rolesToReset = [
+ roles.common.defaultAdmin,
+ roles.common.pauseAdmin,
+ roles.common.timelockOperationPauser,
+ roles.common.securityCouncilManager,
+ roles.common.greenlistedOperator,
+ roles.common.blacklistedOperator,
+ roles.tokenRoles.mTBILL.minter,
+ roles.tokenRoles.mTBILL.burner,
+ roles.tokenRoles.mTBILL.pauser,
+ roles.tokenRoles.mGLOBAL.minter,
+ roles.tokenRoles.mGLOBAL.burner,
+ roles.tokenRoles.mGLOBAL.pauser,
+ roles.tokenRoles.mTBILL.customFeedAdmin as string,
+ roles.tokenRoles.mGLOBAL.customFeedAdmin as string,
+ roles.common.greenlisted,
+ roles.common.blacklisted,
+ ];
+
+ // await setRoleTimelocksAndExecute(
+ // {
+ // timelockManager,
+ // accessControl,
+ // timelock,
+ // owner: acDefaultAdmin,
+ // },
+ // rolesToReset.map((role) => ({
+ // role,
+ // delay: NO_DELAY,
+ // })),
+ // );
+ };
+
+ // every role has a default timelock delay of 1 hour when no explicit delay is set
+ const DEFAULT_TIMELOCK_DELAY = 3600;
+
+ type TimelockContext = {
+ timelockManager: MidasTimelockManager;
+ timelock: MidasAccessControlTimelockController;
+ accessControl: MidasAccessControl;
+ owner: SignerWithAddress;
+ };
+
+ /**
+ * Schedules an operation through the timelock manager, waits the default
+ * delay and executes it. Mirrors the flow used in MidasTimelockManager.test.ts.
+ */
+ const executeWriteViaTimelock = async (
+ ctx: TimelockContext,
+ target: string,
+ calldata: BytesLike,
+ proposer: SignerWithAddress,
+ {
+ delay = DEFAULT_TIMELOCK_DELAY,
+ checkAndSetDefaultDelay = true,
+ }: { delay?: number; checkAndSetDefaultDelay?: boolean } = {},
+ ) => {
+ if (checkAndSetDefaultDelay) {
+ const defaultDelay = await ctx.accessControl.defaultDelay();
+
+ if (defaultDelay === 0) {
+ await executeWriteViaTimelock(
+ ctx,
+ ctx.accessControl.address,
+ ctx.accessControl.interface.encodeFunctionData('setDefaultDelay', [
+ delay,
+ ]),
+ ctx.owner,
+ { delay: days(2), checkAndSetDefaultDelay: false },
+ );
+ }
+ }
- it('deposit instant with custom recipient', async function () {
- const {
- xaut0Whale,
- xaut0,
- vaultsManager,
- depositVault,
- xbxaut,
- xbxautDataFeed,
- customRecipient,
- } = await loadFixture(hyperEvmUpgradeFixture);
-
- await approveBase18(xaut0Whale, xaut0, depositVault, 1);
-
- await depositInstantTest(
- {
- mTBILL: xbxaut,
- mTokenToUsdDataFeed: xbxautDataFeed,
- depositVault,
- owner: vaultsManager,
- customRecipient,
- },
- xaut0,
- 1,
- { from: xaut0Whale },
+ await bulkScheduleTimelockOperationTester(
+ ctx,
+ [target],
+ [calldata as string],
+ {},
+ { from: proposer },
);
- });
- it('deposit request', async function () {
- const {
- xaut0Whale,
- xaut0,
- vaultsManager,
- depositVault,
- xbxaut,
- xbxautDataFeed,
- } = await loadFixture(hyperEvmUpgradeFixture);
-
- await approveBase18(xaut0Whale, xaut0, depositVault, 1);
-
- await depositRequestTest(
- {
- mTBILL: xbxaut,
- mTokenToUsdDataFeed: xbxautDataFeed,
- depositVault,
- owner: vaultsManager,
- },
- xaut0,
- 1,
- { from: xaut0Whale },
+ await increase(delay + 1);
+
+ await executeTimelockOperationTester(
+ ctx,
+ target,
+ calldata as string,
+ proposer.address,
+ { from: ctx.owner },
);
- });
+ };
- it('deposit request approve', async function () {
- const {
- xaut0Whale,
- xaut0,
- vaultsManager,
- depositVault,
- xbxaut,
- xbxautDataFeed,
- } = await loadFixture(hyperEvmUpgradeFixture);
-
- await approveBase18(xaut0Whale, xaut0, depositVault, 1);
-
- const { requestId, rate } = await depositRequestTest(
- {
- mTBILL: xbxaut,
- mTokenToUsdDataFeed: xbxautDataFeed,
- depositVault,
- owner: vaultsManager,
- },
- xaut0,
- 1,
- { from: xaut0Whale },
+ /**
+ * Grants a role to `account` through the timelock flow (no delay reset).
+ * The proposer must hold the admin role of `role` (DEFAULT_ADMIN_ROLE).
+ */
+ const grantRoleViaTimelock = async (
+ ctx: TimelockContext,
+ role: string,
+ account: string,
+ proposer: SignerWithAddress,
+ ) => {
+ await executeWriteViaTimelock(
+ ctx,
+ ctx.accessControl.address,
+ ctx.accessControl.interface.encodeFunctionData(
+ 'grantRole(bytes32,address)',
+ [role, account],
+ ),
+ proposer,
);
+ };
- await safeApproveRequestTest(
- {
- mTBILL: xbxaut,
- mTokenToUsdDataFeed: xbxautDataFeed,
- depositVault,
- owner: vaultsManager,
- },
- requestId!,
- rate!,
+ /**
+ * Grants multiple roles (sharing the same admin role) through the timelock.
+ */
+ const grantRoleMultViaTimelock = async (
+ ctx: TimelockContext,
+ roles: string[],
+ accounts: string[],
+ proposer: SignerWithAddress,
+ ) => {
+ const params = roles.map((role, index) => ({
+ role,
+ account: accounts[index],
+ delay: 0,
+ }));
+ await executeWriteViaTimelock(
+ ctx,
+ ctx.accessControl.address,
+ ctx.accessControl.interface.encodeFunctionData('grantRoleMult', [params]),
+ proposer,
);
+ };
+ describe('MidasAccessControl', () => {
+ describe('initializeRelationships()', () => {
+ it('should expose deployed pause and timelock managers', async () => {
+ const { accessControl, pauseManager, timelockManager } =
+ await loadFixture(mainnetUpgradeFixture);
+
+ expect(await accessControl.pauseManager()).to.eq(pauseManager.address);
+ expect(await accessControl.timelockManager()).to.eq(
+ timelockManager.address,
+ );
+ });
+ });
+
+ describe('grantRole()', () => {
+ it('should grant role to account', async () => {
+ const { accessControl, acDefaultAdmin, timelockManager, timelock } =
+ await loadFixture(mainnetUpgradeFixture);
+ const roles = getAllRoles();
+ const [, recipient] = await ethers.getSigners();
+
+ await resetTimelockDelays({
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ });
+
+ await expect(
+ accessControl
+ .connect(acDefaultAdmin)
+ ['grantRole(bytes32,address)'](
+ roles.common.pauseAdmin,
+ recipient.address,
+ ),
+ ).not.reverted;
+
+ expect(
+ await accessControl.hasRole(
+ roles.common.pauseAdmin,
+ recipient.address,
+ ),
+ ).to.eq(true);
+ });
+
+ it('should fail: when caller has no admin role', async () => {
+ const { accessControl, acDefaultAdmin, timelockManager, timelock } =
+ await loadFixture(mainnetUpgradeFixture);
+ const roles = getAllRoles();
+ const [, recipient, unauthorized] = await ethers.getSigners();
+
+ await resetTimelockDelays({
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ });
+
+ await expect(
+ accessControl
+ .connect(unauthorized)
+ ['grantRole(bytes32,address)'](
+ roles.common.pauseAdmin,
+ recipient.address,
+ ),
+ ).revertedWithCustomError(
+ accessControl,
+ acErrors.WMAC_HASNT_PERMISSION().customErrorName,
+ );
+ });
+
+ it('should grant role via timelock', async () => {
+ const { accessControl, acDefaultAdmin, timelockManager, timelock } =
+ await loadFixture(mainnetUpgradeFixture);
+ const roles = getAllRoles();
+ const [, recipient] = await ethers.getSigners();
+
+ await grantRoleViaTimelock(
+ { timelockManager, timelock, accessControl, owner: acDefaultAdmin },
+ roles.common.pauseAdmin,
+ recipient.address,
+ acDefaultAdmin,
+ );
+
+ expect(
+ await accessControl.hasRole(
+ roles.common.pauseAdmin,
+ recipient.address,
+ ),
+ ).to.eq(true);
+ });
+ });
+
+ describe('grantRoleMult()', () => {
+ it('should grant multiple roles', async () => {
+ const { accessControl, acDefaultAdmin, timelockManager, timelock } =
+ await loadFixture(mainnetUpgradeFixture);
+ const roles = getAllRoles();
+ const [, accountA, accountB] = await ethers.getSigners();
+
+ await resetTimelockDelays({
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ });
+
+ await grantRoleMultTester(
+ { accessControl, owner: acDefaultAdmin },
+ [
+ {
+ role: roles.common.pauseAdmin,
+ account: accountA.address,
+ delay: 0,
+ },
+ {
+ role: roles.common.timelockOperationPauser,
+ account: accountB.address,
+ delay: 0,
+ },
+ ],
+ {
+ from: acDefaultAdmin,
+ },
+ );
+ });
+
+ it('should fail: array is empty', async () => {
+ const { accessControl, acDefaultAdmin, timelockManager, timelock } =
+ await loadFixture(mainnetUpgradeFixture);
+
+ await resetTimelockDelays({
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ });
+
+ await grantRoleMultTester(
+ { accessControl, owner: acDefaultAdmin },
+ [],
+ {
+ from: acDefaultAdmin,
+ revertCustomError: { customErrorName: 'EmptyArray' },
+ },
+ );
+ });
+ });
});
- it('deposit request approve bulk', async function () {
- const {
- xaut0Whale,
- xaut0,
- vaultsManager,
- depositVault,
- xbxaut,
- xbxautDataFeed,
- } = await loadFixture(hyperEvmUpgradeFixture);
-
- await approveBase18(xaut0Whale, xaut0, depositVault, 1);
-
- const { requestId } = await depositRequestTest(
- {
- mTBILL: xbxaut,
- mTokenToUsdDataFeed: xbxautDataFeed,
- depositVault,
- owner: vaultsManager,
- },
- xaut0,
- 1,
- { from: xaut0Whale },
- );
+ describe('MidasPauseManager', () => {
+ describe('globalPause()', () => {
+ it('should pause globally when called by pause admin', async () => {
+ const {
+ accessControl,
+ pauseManager,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+ const roles = getAllRoles();
- await safeBulkApproveDepositRequestTest(
- {
- mTBILL: xbxaut,
- mTokenToUsdDataFeed: xbxautDataFeed,
- depositVault,
- owner: vaultsManager,
- },
- [{ id: requestId! }],
- );
+ await resetTimelockDelays({
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ });
+
+ await accessControl
+ .connect(acDefaultAdmin)
+ ['grantRole(bytes32,address)'](
+ roles.common.pauseAdmin,
+ acDefaultAdmin.address,
+ );
+
+ await pauseGlobalTest({
+ pauseManager,
+ owner: acDefaultAdmin,
+ });
+ });
+
+ it('should fail: when caller has no pause admin permission', async () => {
+ const {
+ pauseManager,
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+ const [, unauthorized] = await ethers.getSigners();
+
+ await resetTimelockDelays({
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ });
+
+ await pauseGlobalTest(
+ { pauseManager, owner: unauthorized },
+ {
+ from: unauthorized,
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ },
+ );
+ });
+
+ it('should fail: pause globally via timelock when pause delay is 0', async () => {
+ const {
+ accessControl,
+ pauseManager,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+ const roles = getAllRoles();
+
+ const ctx = {
+ timelockManager,
+ timelock,
+ accessControl,
+ owner: acDefaultAdmin,
+ };
+
+ await grantRoleViaTimelock(
+ ctx,
+ roles.common.pauseAdmin,
+ acDefaultAdmin.address,
+ acDefaultAdmin,
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ ctx,
+ [pauseManager.address],
+ [pauseManager.interface.encodeFunctionData('globalPause') as string],
+ {},
+ {
+ from: acDefaultAdmin,
+ revertCustomError: {
+ contract: timelockManager,
+ customErrorName: 'NoTimelockDelayForRole',
+ },
+ },
+ );
+ });
+ });
});
- it('deposit instant with custom recipient', async function () {
- const {
- xaut0Whale,
- xaut0,
- vaultsManager,
- depositVault,
- xbxaut,
- xbxautDataFeed,
- customRecipient,
- } = await loadFixture(hyperEvmUpgradeFixture);
-
- await approveBase18(xaut0Whale, xaut0, depositVault, 1);
-
- await depositRequestTest(
- {
- mTBILL: xbxaut,
- mTokenToUsdDataFeed: xbxautDataFeed,
- depositVault,
- owner: vaultsManager,
- customRecipient,
- },
- xaut0,
- 1,
- { from: xaut0Whale },
- );
+ describe('MidasTimelockManager', () => {
+ describe('deployment', () => {
+ it('should link to access control and timelock controller', async () => {
+ const {
+ accessControl,
+ timelockManager,
+ timelock,
+ securityCouncilMembers,
+ } = await loadFixture(mainnetUpgradeFixture);
+
+ expect(await timelockManager.accessControl()).to.eq(
+ accessControl.address,
+ );
+ expect(await timelockManager.timelock()).to.eq(timelock.address);
+ expect(await timelockManager.councilQuorum(0)).to.eq(3);
+ expect(await timelockManager.getSecurityCouncilMembers(0)).to.deep.eq(
+ securityCouncilMembers.map((s) => s.address),
+ );
+ });
+ });
});
- it('redeem instant', async function () {
- const {
- xaut0Whale,
- xaut0,
- vaultsManager,
- redemptionVaultSwapper,
- xbxaut,
- xbxautDataFeed,
- tokenManager,
- } = await loadFixture(hyperEvmUpgradeFixture);
-
- await mint(
- { tokenContract: xbxaut, owner: tokenManager },
- xaut0Whale,
- parseUnits('1'),
- );
+ describe('mTBILL', () => {
+ const mTbillRoles = getRolesForToken('mTBILL');
- await approveBase18(xaut0Whale, xbxaut, redemptionVaultSwapper, 1);
-
- await redeemInstantTest(
- {
- mTBILL: xbxaut,
- mTokenToUsdDataFeed: xbxautDataFeed,
- redemptionVault: redemptionVaultSwapper,
- owner: vaultsManager,
- },
- xaut0,
- 1,
- { from: xaut0Whale },
- );
+ it('should keep permissioned/min-balance flags off after upgrade', async () => {
+ const { mTbill, clawbackReceiver } = await loadFixture(
+ mainnetUpgradeFixture,
+ );
+
+ expect(await mTbill.isPermissioned()).eq(false);
+ expect(await mTbill.isMinHoldingBalanceEnforced()).eq(false);
+ expect(await mTbill.clawbackReceiver()).eq(clawbackReceiver.address);
+ expect(await mTbill.greenlistedRole()).eq(mTbillRoles.greenlisted);
+ expect(await mTbill.minBalanceExemptRole()).eq(
+ mTbillRoles.minBalanceExempt,
+ );
+ });
+
+ describe('mint()', () => {
+ it('should mint tokens to recipient', async () => {
+ const {
+ mTbill,
+ accessControl,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+ const [, recipient] = await ethers.getSigners();
+ const amount = parseUnits('1');
+
+ await resetTimelockDelays({
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ });
+
+ await accessControl
+ .connect(acDefaultAdmin)
+ ['grantRole(bytes32,address)'](
+ mTbillRoles.minter,
+ acDefaultAdmin.address,
+ );
+
+ await mint(
+ { tokenContract: mTbill, owner: acDefaultAdmin },
+ recipient.address,
+ amount,
+ );
+
+ expect(await mTbill.balanceOf(recipient.address)).to.eq(amount);
+ });
+
+ it('should fail: when caller has no mint operator role', async () => {
+ const {
+ mTbill,
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+ const [, recipient, unauthorized] = await ethers.getSigners();
+
+ await resetTimelockDelays({
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ });
+
+ await expect(
+ mTbill.connect(unauthorized).mint(recipient.address, 1),
+ ).revertedWithCustomError(
+ mTbill,
+ acErrors.WMAC_HASNT_PERMISSION().customErrorName,
+ );
+ });
+
+ it('should fail: when trying to schedule mint through timelock', async () => {
+ const {
+ mTbill,
+ accessControl,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+ const [, recipient] = await ethers.getSigners();
+ const amount = parseUnits('1');
+
+ const ctx = {
+ timelockManager,
+ timelock,
+ accessControl,
+ owner: acDefaultAdmin,
+ };
+
+ await grantRoleViaTimelock(
+ ctx,
+ mTbillRoles.minter,
+ acDefaultAdmin.address,
+ acDefaultAdmin,
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ ctx,
+ [mTbill.address],
+ [
+ mTbill.interface.encodeFunctionData('mint', [
+ recipient.address,
+ amount,
+ ]),
+ ],
+ {},
+ {
+ from: acDefaultAdmin,
+ revertCustomError: {
+ contract: timelockManager,
+ customErrorName: 'InvalidPreflightError',
+ },
+ },
+ );
+ });
+ });
+
+ describe('burn()', () => {
+ it('should burn tokens from holder', async () => {
+ const {
+ mTbill,
+ accessControl,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+ const [, holder] = await ethers.getSigners();
+ const amount = parseUnits('1');
+
+ await resetTimelockDelays({
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ });
+
+ await accessControl
+ .connect(acDefaultAdmin)
+ ['grantRole(bytes32,address)'](
+ mTbillRoles.minter,
+ acDefaultAdmin.address,
+ );
+ await accessControl
+ .connect(acDefaultAdmin)
+ ['grantRole(bytes32,address)'](
+ mTbillRoles.burner,
+ acDefaultAdmin.address,
+ );
+
+ await mint(
+ { tokenContract: mTbill, owner: acDefaultAdmin },
+ holder.address,
+ amount,
+ );
+
+ await burn(
+ { tokenContract: mTbill, owner: acDefaultAdmin },
+ holder.address,
+ amount,
+ );
+
+ expect(await mTbill.balanceOf(holder.address)).to.eq(0);
+ });
+
+ it('should fail: when caller has no burn operator role', async () => {
+ const {
+ mTbill,
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+ const [, holder, unauthorized] = await ethers.getSigners();
+
+ await resetTimelockDelays({
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ });
+
+ await expect(
+ mTbill.connect(unauthorized).burn(holder.address, 1),
+ ).revertedWithCustomError(
+ mTbill,
+ acErrors.WMAC_HASNT_PERMISSION().customErrorName,
+ );
+ });
+
+ it('should fail: when trying to schedule burn through timelock', async () => {
+ const {
+ mTbill,
+ accessControl,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+ const [, holder] = await ethers.getSigners();
+ const amount = parseUnits('1');
+
+ const ctx = {
+ timelockManager,
+ timelock,
+ accessControl,
+ owner: acDefaultAdmin,
+ };
+
+ await grantRoleMultViaTimelock(
+ ctx,
+ [mTbillRoles.minter, mTbillRoles.burner],
+ [acDefaultAdmin.address, acDefaultAdmin.address],
+ acDefaultAdmin,
+ );
+
+ await mint(
+ { tokenContract: mTbill, owner: acDefaultAdmin },
+ holder.address,
+ amount,
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ ctx,
+ [mTbill.address],
+ [
+ mTbill.interface.encodeFunctionData('burn', [
+ holder.address,
+ amount,
+ ]),
+ ],
+ {},
+ {
+ from: acDefaultAdmin,
+ revertCustomError: {
+ contract: timelockManager,
+ customErrorName: 'InvalidPreflightError',
+ },
+ },
+ );
+ });
+ });
+
+ describe('transfer()', () => {
+ it('should transfer between token holders', async function () {
+ const { mTbill, mTbillHolders } = await loadFixture(
+ mainnetUpgradeFixture,
+ );
+
+ const from = mTbillHolders[0];
+ const to = mTbillHolders[1];
+ const amount = parseUnits('0.01');
+
+ await expect(mTbill.connect(from).transfer(to.address, amount)).not
+ .reverted;
+
+ expect(await mTbill.balanceOf(to.address)).to.be.gte(amount);
+ });
+
+ it('should fail: when sender is blacklisted', async () => {
+ const {
+ mTbill,
+ accessControl,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+ const [, from, to] = await ethers.getSigners();
+ const amount = parseUnits('1');
+
+ await resetTimelockDelays({
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ });
+
+ const allRoles = await getAllRoles();
+ await grantRoleMultTester(
+ { accessControl, owner: acDefaultAdmin },
+ [
+ {
+ role: mTbillRoles.minter,
+ account: acDefaultAdmin.address,
+ delay: 0,
+ },
+ {
+ role: allRoles.common.blacklistedOperator,
+ account: acDefaultAdmin.address,
+ delay: 0,
+ },
+ ],
+ { from: acDefaultAdmin },
+ );
+
+ await mint(
+ { tokenContract: mTbill, owner: acDefaultAdmin },
+ from.address,
+ amount,
+ );
+
+ const roles = getAllRoles();
+ await accessControl
+ .connect(acDefaultAdmin)
+ ['grantRole(bytes32,address)'](
+ roles.common.blacklisted,
+ from.address,
+ );
+
+ await expect(
+ mTbill.connect(from).transfer(to.address, amount),
+ ).revertedWithCustomError(
+ mTbill,
+ acErrors.WMAC_BLACKLISTED().customErrorName,
+ );
+ });
+ });
});
- it('redeem instant with custom recipient', async function () {
- const {
- xaut0Whale,
- xaut0,
- vaultsManager,
- redemptionVaultSwapper,
- xbxaut,
- xbxautDataFeed,
- tokenManager,
- customRecipient,
- } = await loadFixture(hyperEvmUpgradeFixture);
-
- await mint(
- { tokenContract: xbxaut, owner: tokenManager },
- xaut0Whale,
- parseUnits('1'),
- );
+ describe('mGLOBAL', () => {
+ const mGlobalRoles = getRolesForToken('mGLOBAL');
- await approveBase18(xaut0Whale, xbxaut, redemptionVaultSwapper, 1);
-
- await redeemInstantTest(
- {
- mTBILL: xbxaut,
- mTokenToUsdDataFeed: xbxautDataFeed,
- redemptionVault: redemptionVaultSwapper,
- owner: vaultsManager,
- customRecipient,
- },
- xaut0,
- 1,
- { from: xaut0Whale },
- );
+ it('should enable permissioned flag after upgrade', async () => {
+ const { mGlobal, clawbackReceiver } = await loadFixture(
+ mainnetUpgradeFixture,
+ );
+
+ expect(await mGlobal.isPermissioned()).eq(true);
+ expect(await mGlobal.isMinHoldingBalanceEnforced()).eq(false);
+ expect(await mGlobal.clawbackReceiver()).eq(clawbackReceiver.address);
+ expect(await mGlobal.greenlistedRole()).eq(mGlobalRoles.greenlisted);
+ expect(await mGlobal.minBalanceExemptRole()).eq(
+ mGlobalRoles.minBalanceExempt,
+ );
+ });
+
+ describe('mint()', () => {
+ it('should mint tokens to greenlisted recipient', async () => {
+ const {
+ mGlobal,
+ accessControl,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+ const [, recipient] = await ethers.getSigners();
+ const amount = parseUnits('1');
+
+ await resetTimelockDelays({
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ });
+
+ await accessControl
+ .connect(acDefaultAdmin)
+ ['grantRole(bytes32,address)'](
+ mGlobalRoles.minter,
+ acDefaultAdmin.address,
+ );
+ await accessControl
+ .connect(acDefaultAdmin)
+ ['grantRole(bytes32,address)'](
+ mGlobalRoles.greenlisted,
+ recipient.address,
+ );
+
+ await mint(
+ { tokenContract: mGlobal, owner: acDefaultAdmin },
+ recipient.address,
+ amount,
+ );
+
+ expect(await mGlobal.balanceOf(recipient.address)).to.eq(amount);
+ });
+
+ it('should fail: when caller has no mint operator role', async () => {
+ const {
+ mGlobal,
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+ const [, recipient, unauthorized] = await ethers.getSigners();
+ await resetTimelockDelays({
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ });
+
+ await expect(
+ mGlobal.connect(unauthorized).mint(recipient.address, 1),
+ ).revertedWithCustomError(
+ mGlobal,
+ acErrors.WMAC_HASNT_PERMISSION().customErrorName,
+ );
+ });
+
+ it('should fail: when trying to schedule mint through timelock', async () => {
+ const {
+ mGlobal,
+ accessControl,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+ const [, recipient] = await ethers.getSigners();
+ const amount = parseUnits('1');
+
+ const ctx = {
+ timelockManager,
+ timelock,
+ accessControl,
+ owner: acDefaultAdmin,
+ };
+
+ await grantRoleViaTimelock(
+ ctx,
+ mGlobalRoles.minter,
+ acDefaultAdmin.address,
+ acDefaultAdmin,
+ );
+ await grantRoleViaTimelock(
+ ctx,
+ mGlobalRoles.greenlisted,
+ recipient.address,
+ acDefaultAdmin,
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ ctx,
+ [mGlobal.address],
+ [
+ mGlobal.interface.encodeFunctionData('mint', [
+ recipient.address,
+ amount,
+ ]),
+ ],
+ {},
+ {
+ from: acDefaultAdmin,
+ revertCustomError: {
+ contract: timelockManager,
+ customErrorName: 'InvalidPreflightError',
+ },
+ },
+ );
+ });
+ });
+
+ describe('burn()', () => {
+ it('should burn tokens from greenlisted holder', async () => {
+ const {
+ mGlobal,
+ accessControl,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+ const [, holder] = await ethers.getSigners();
+ const amount = parseUnits('1');
+ await resetTimelockDelays({
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ });
+
+ await accessControl
+ .connect(acDefaultAdmin)
+ ['grantRole(bytes32,address)'](
+ mGlobalRoles.minter,
+ acDefaultAdmin.address,
+ );
+ await accessControl
+ .connect(acDefaultAdmin)
+ ['grantRole(bytes32,address)'](
+ mGlobalRoles.burner,
+ acDefaultAdmin.address,
+ );
+ await accessControl
+ .connect(acDefaultAdmin)
+ ['grantRole(bytes32,address)'](
+ mGlobalRoles.greenlisted,
+ holder.address,
+ );
+
+ await mint(
+ { tokenContract: mGlobal, owner: acDefaultAdmin },
+ holder.address,
+ amount,
+ );
+
+ await burn(
+ { tokenContract: mGlobal, owner: acDefaultAdmin },
+ holder.address,
+ amount,
+ );
+
+ expect(await mGlobal.balanceOf(holder.address)).to.eq(0);
+ });
+
+ it('should fail: when trying to schedule burn through timelock', async () => {
+ const {
+ mGlobal,
+ accessControl,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+ const [, holder] = await ethers.getSigners();
+ const amount = parseUnits('1');
+
+ const ctx = {
+ timelockManager,
+ timelock,
+ accessControl,
+ owner: acDefaultAdmin,
+ };
+
+ await grantRoleMultViaTimelock(
+ ctx,
+ [mGlobalRoles.minter, mGlobalRoles.burner],
+ [acDefaultAdmin.address, acDefaultAdmin.address],
+ acDefaultAdmin,
+ );
+ await grantRoleViaTimelock(
+ ctx,
+ mGlobalRoles.greenlisted,
+ holder.address,
+ acDefaultAdmin,
+ );
+
+ await mint(
+ { tokenContract: mGlobal, owner: acDefaultAdmin },
+ holder.address,
+ amount,
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ ctx,
+ [mGlobal.address],
+ [
+ mGlobal.interface.encodeFunctionData('burn', [
+ holder.address,
+ amount,
+ ]),
+ ],
+ {},
+ {
+ from: acDefaultAdmin,
+ revertCustomError: {
+ contract: timelockManager,
+ customErrorName: 'InvalidPreflightError',
+ },
+ },
+ );
+ });
+ });
+
+ describe('transfer()', () => {
+ it('should transfer between greenlisted token holders', async function () {
+ const {
+ mGlobal,
+ accessControl,
+ acDefaultAdmin,
+ mGlobalHolders,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+
+ const from = mGlobalHolders[0];
+ const to = mGlobalHolders[1];
+ const amount = parseUnits('0.01');
+ const greenlistRole = await mGlobal.greenlistedRole();
+
+ await resetTimelockDelays({
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ });
+
+ await accessControl
+ .connect(acDefaultAdmin)
+ ['grantRole(bytes32,address)'](greenlistRole, from.address);
+ await accessControl
+ .connect(acDefaultAdmin)
+ ['grantRole(bytes32,address)'](greenlistRole, to.address);
+
+ await expect(mGlobal.connect(from).transfer(to.address, amount)).not
+ .reverted;
+ });
+
+ it('should fail: when sender is not greenlisted', async () => {
+ const {
+ mGlobal,
+ accessControl,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+ const [, from, to] = await ethers.getSigners();
+ const amount = parseUnits('1');
+ await resetTimelockDelays({
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ });
+
+ await accessControl
+ .connect(acDefaultAdmin)
+ ['grantRole(bytes32,address)'](
+ mGlobalRoles.minter,
+ acDefaultAdmin.address,
+ );
+ await accessControl
+ .connect(acDefaultAdmin)
+ ['grantRole(bytes32,address)'](mGlobalRoles.greenlisted, to.address);
+ await accessControl
+ .connect(acDefaultAdmin)
+ ['grantRole(bytes32,address)'](
+ mGlobalRoles.greenlisted,
+ from.address,
+ );
+
+ await mint(
+ { tokenContract: mGlobal, owner: acDefaultAdmin },
+ from.address,
+ amount,
+ );
+
+ await accessControl
+ .connect(acDefaultAdmin)
+ .revokeRole(mGlobalRoles.greenlisted, from.address);
+
+ await expect(
+ mGlobal.connect(from).transfer(to.address, amount),
+ ).revertedWithCustomError(mGlobal, 'NotGreenlisted');
+ });
+ });
});
- it('redeem request', async function () {
- const {
- xaut0Whale,
- xaut0,
- vaultsManager,
- redemptionVaultSwapper,
- xbxaut,
- xbxautDataFeed,
- tokenManager,
- } = await loadFixture(hyperEvmUpgradeFixture);
-
- await mint(
- { tokenContract: xbxaut, owner: tokenManager },
- xaut0Whale,
- parseUnits('1'),
- );
- await approveBase18(xaut0Whale, xbxaut, redemptionVaultSwapper, 1);
-
- await redeemRequestTest(
- {
- mTBILL: xbxaut,
- mTokenToUsdDataFeed: xbxautDataFeed,
- redemptionVault: redemptionVaultSwapper,
- owner: vaultsManager,
- },
- xaut0,
- 1,
- { from: xaut0Whale },
- );
+ describe('mTBILL DataFeed', () => {
+ describe('getDataInBase18()', () => {
+ it('should return a positive price', async () => {
+ const { mTbillDataFeed } = await loadFixture(mainnetUpgradeFixture);
+
+ const price = await mTbillDataFeed.getDataInBase18();
+ expect(price).to.be.gt(0);
+ });
+ });
+
+ describe('aggregator()', () => {
+ it('should expose configured aggregator address', async () => {
+ const { mTbillDataFeed } = await loadFixture(mainnetUpgradeFixture);
+
+ expect(await mTbillDataFeed.aggregator()).to.not.eq(
+ constants.AddressZero,
+ );
+ });
+ });
+
+ describe('changeAggregator()', () => {
+ it('should change aggregator after resetting delays', async () => {
+ const {
+ mTbillDataFeed,
+ accessControl,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+ const [deployer] = await ethers.getSigners();
+
+ await resetTimelockDelays({
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ });
+
+ const contractAdminRole = await mTbillDataFeed.contractAdminRole();
+ await accessControl
+ .connect(acDefaultAdmin)
+ ['grantRole(bytes32,address)'](
+ contractAdminRole,
+ acDefaultAdmin.address,
+ );
+
+ const newAggregator = await new AggregatorV3Mock__factory(
+ deployer,
+ ).deploy();
+
+ await mTbillDataFeed
+ .connect(acDefaultAdmin)
+ .changeAggregator(newAggregator.address);
+
+ expect(await mTbillDataFeed.aggregator()).to.eq(newAggregator.address);
+ });
+
+ it('should fail: when caller has no feed admin permission', async () => {
+ const { mTbillDataFeed } = await loadFixture(mainnetUpgradeFixture);
+ const [deployer, unauthorized] = await ethers.getSigners();
+
+ const newAggregator = await new AggregatorV3Mock__factory(
+ deployer,
+ ).deploy();
+
+ await expect(
+ mTbillDataFeed
+ .connect(unauthorized)
+ .changeAggregator(newAggregator.address),
+ ).revertedWithCustomError(
+ mTbillDataFeed,
+ acErrors.WMAC_HASNT_PERMISSION().customErrorName,
+ );
+ });
+
+ it('should change aggregator via timelock', async () => {
+ const {
+ mTbillDataFeed,
+ accessControl,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+ const [deployer] = await ethers.getSigners();
+
+ const ctx = {
+ timelockManager,
+ timelock,
+ accessControl,
+ owner: acDefaultAdmin,
+ };
+
+ const contractAdminRole = await mTbillDataFeed.contractAdminRole();
+ await grantRoleViaTimelock(
+ ctx,
+ contractAdminRole,
+ acDefaultAdmin.address,
+ acDefaultAdmin,
+ );
+
+ const newAggregator = await new AggregatorV3Mock__factory(
+ deployer,
+ ).deploy();
+
+ await executeWriteViaTimelock(
+ ctx,
+ mTbillDataFeed.address,
+ mTbillDataFeed.interface.encodeFunctionData('changeAggregator', [
+ newAggregator.address,
+ ]),
+ acDefaultAdmin,
+ );
+
+ expect(await mTbillDataFeed.aggregator()).to.eq(newAggregator.address);
+ });
+ });
+
+ describe('setHealthyDiff()', () => {
+ const newHealthyDiff = 2 * 24 * 3600;
+
+ it('should set healthy diff after resetting delays', async () => {
+ const {
+ mTbillDataFeed,
+ accessControl,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+
+ await resetTimelockDelays({
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ });
+
+ const contractAdminRole = await mTbillDataFeed.contractAdminRole();
+ await accessControl
+ .connect(acDefaultAdmin)
+ ['grantRole(bytes32,address)'](
+ contractAdminRole,
+ acDefaultAdmin.address,
+ );
+
+ await mTbillDataFeed
+ .connect(acDefaultAdmin)
+ .setHealthyDiff(newHealthyDiff);
+
+ expect(await mTbillDataFeed.healthyDiff()).to.eq(newHealthyDiff);
+ });
+
+ it('should set healthy diff via timelock', async () => {
+ const {
+ mTbillDataFeed,
+ accessControl,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+
+ const ctx = {
+ timelockManager,
+ timelock,
+ accessControl,
+ owner: acDefaultAdmin,
+ };
+
+ const contractAdminRole = await mTbillDataFeed.contractAdminRole();
+ await grantRoleViaTimelock(
+ ctx,
+ contractAdminRole,
+ acDefaultAdmin.address,
+ acDefaultAdmin,
+ );
+
+ await executeWriteViaTimelock(
+ ctx,
+ mTbillDataFeed.address,
+ mTbillDataFeed.interface.encodeFunctionData('setHealthyDiff', [
+ newHealthyDiff,
+ ]),
+ acDefaultAdmin,
+ );
+
+ expect(await mTbillDataFeed.healthyDiff()).to.eq(newHealthyDiff);
+ });
+ });
});
- it('redeem request with custom recipient', async function () {
- const {
- xaut0Whale,
- xaut0,
- vaultsManager,
- redemptionVaultSwapper,
- xbxaut,
- xbxautDataFeed,
- tokenManager,
- customRecipient,
- } = await loadFixture(hyperEvmUpgradeFixture);
-
- await mint(
- { tokenContract: xbxaut, owner: tokenManager },
- xaut0Whale,
- parseUnits('1'),
- );
- await approveBase18(xaut0Whale, xbxaut, redemptionVaultSwapper, 1);
-
- await redeemRequestTest(
- {
- mTBILL: xbxaut,
- mTokenToUsdDataFeed: xbxautDataFeed,
- redemptionVault: redemptionVaultSwapper,
- owner: vaultsManager,
- customRecipient,
- },
- xaut0,
- 1,
- { from: xaut0Whale },
- );
+ describe('mTBILL CustomAggregatorFeed', () => {
+ describe('latestRoundData()', () => {
+ it('should return valid round data', async () => {
+ const { mTbillCustomFeed } = await loadFixture(mainnetUpgradeFixture);
+
+ const [, answer] = await mTbillCustomFeed.latestRoundData();
+ expect(answer).to.be.gt(0);
+ });
+ });
+
+ describe('decimals()', () => {
+ it('should return feed decimals', async () => {
+ const { mTbillCustomFeed } = await loadFixture(mainnetUpgradeFixture);
+
+ expect(await mTbillCustomFeed.decimals()).to.eq(8);
+ });
+ });
+
+ describe('setRoundData()', () => {
+ it('should submit a new round after resetting delays', async () => {
+ const {
+ mTbillCustomFeed,
+ accessControl,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+
+ await resetTimelockDelays({
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ });
+
+ const contractAdminRole = await mTbillCustomFeed.contractAdminRole();
+ await accessControl
+ .connect(acDefaultAdmin)
+ ['grantRole(bytes32,address)'](
+ contractAdminRole,
+ acDefaultAdmin.address,
+ );
+
+ const roundBefore = await mTbillCustomFeed.latestRound();
+ const answer = await mTbillCustomFeed.lastAnswer();
+
+ await mTbillCustomFeed.connect(acDefaultAdmin).setRoundData(answer);
+
+ expect(await mTbillCustomFeed.latestRound()).to.eq(roundBefore.add(1));
+ expect(await mTbillCustomFeed.lastAnswer()).to.eq(answer);
+ });
+
+ it('should fail: when caller has no feed admin permission', async () => {
+ const { mTbillCustomFeed } = await loadFixture(mainnetUpgradeFixture);
+ const [, unauthorized] = await ethers.getSigners();
+ const answer = await mTbillCustomFeed.lastAnswer();
+
+ await expect(
+ mTbillCustomFeed.connect(unauthorized).setRoundData(answer),
+ ).revertedWithCustomError(
+ mTbillCustomFeed,
+ acErrors.WMAC_HASNT_PERMISSION().customErrorName,
+ );
+ });
+
+ it('should submit a new round via timelock', async () => {
+ const {
+ mTbillCustomFeed,
+ accessControl,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+
+ const ctx = {
+ timelockManager,
+ timelock,
+ accessControl,
+ owner: acDefaultAdmin,
+ };
+
+ const contractAdminRole = await mTbillCustomFeed.contractAdminRole();
+ await grantRoleViaTimelock(
+ ctx,
+ contractAdminRole,
+ acDefaultAdmin.address,
+ acDefaultAdmin,
+ );
+
+ const roundBefore = await mTbillCustomFeed.latestRound();
+ const answer = await mTbillCustomFeed.lastAnswer();
+
+ await executeWriteViaTimelock(
+ ctx,
+ mTbillCustomFeed.address,
+ mTbillCustomFeed.interface.encodeFunctionData('setRoundData', [
+ answer,
+ ]),
+ acDefaultAdmin,
+ );
+
+ expect(await mTbillCustomFeed.latestRound()).to.eq(roundBefore.add(1));
+ });
+ });
+
+ describe('setMaxAnswerDeviation()', () => {
+ const newDeviation = parseUnits('1', 8);
+
+ it('should set max answer deviation after resetting delays', async () => {
+ const {
+ mTbillCustomFeed,
+ accessControl,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+
+ await resetTimelockDelays({
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ });
+
+ const contractAdminRole = await mTbillCustomFeed.contractAdminRole();
+ await accessControl
+ .connect(acDefaultAdmin)
+ ['grantRole(bytes32,address)'](
+ contractAdminRole,
+ acDefaultAdmin.address,
+ );
+
+ await mTbillCustomFeed
+ .connect(acDefaultAdmin)
+ .setMaxAnswerDeviation(newDeviation);
+
+ expect(await mTbillCustomFeed.maxAnswerDeviation()).to.eq(newDeviation);
+ });
+
+ it('should set max answer deviation via timelock', async () => {
+ const {
+ mTbillCustomFeed,
+ accessControl,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+
+ const ctx = {
+ timelockManager,
+ timelock,
+ accessControl,
+ owner: acDefaultAdmin,
+ };
+
+ const contractAdminRole = await mTbillCustomFeed.contractAdminRole();
+ await grantRoleViaTimelock(
+ ctx,
+ contractAdminRole,
+ acDefaultAdmin.address,
+ acDefaultAdmin,
+ );
+
+ await executeWriteViaTimelock(
+ ctx,
+ mTbillCustomFeed.address,
+ mTbillCustomFeed.interface.encodeFunctionData(
+ 'setMaxAnswerDeviation',
+ [newDeviation],
+ ),
+ acDefaultAdmin,
+ );
+
+ expect(await mTbillCustomFeed.maxAnswerDeviation()).to.eq(newDeviation);
+ });
+ });
});
- it('redeem request approve', async function () {
- const {
- xaut0Whale,
- xaut0,
- vaultsManager,
- redemptionVaultSwapper,
- xbxaut,
- xbxautDataFeed,
- tokenManager,
- } = await loadFixture(hyperEvmUpgradeFixture);
-
- await mint(
- { tokenContract: xbxaut, owner: tokenManager },
- xaut0Whale,
- parseUnits('1'),
- );
- await approveBase18(xaut0Whale, xbxaut, redemptionVaultSwapper, 1);
-
- const { requestId, rate } = await redeemRequestTest(
- {
- mTBILL: xbxaut,
- mTokenToUsdDataFeed: xbxautDataFeed,
- redemptionVault: redemptionVaultSwapper,
- owner: vaultsManager,
- },
- xaut0,
- 1,
- { from: xaut0Whale },
- );
+ describe('mGLOBAL DataFeed', () => {
+ describe('getDataInBase18()', () => {
+ it('should return a positive price', async () => {
+ const { mGlobalDataFeed } = await loadFixture(mainnetUpgradeFixture);
- await safeApproveRedeemRequestTest(
- {
- mTBILL: xbxaut,
- mTokenToUsdDataFeed: xbxautDataFeed,
- redemptionVault: redemptionVaultSwapper,
- owner: vaultsManager,
- },
- requestId!,
- rate!,
- );
+ const price = await mGlobalDataFeed.getDataInBase18();
+ expect(price).to.be.gt(0);
+ });
+ });
+
+ describe('changeAggregator()', () => {
+ it('should change aggregator after resetting delays', async () => {
+ const {
+ mGlobalDataFeed,
+ accessControl,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+ const [deployer] = await ethers.getSigners();
+
+ await resetTimelockDelays({
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ });
+
+ const contractAdminRole = await mGlobalDataFeed.contractAdminRole();
+ await accessControl
+ .connect(acDefaultAdmin)
+ ['grantRole(bytes32,address)'](
+ contractAdminRole,
+ acDefaultAdmin.address,
+ );
+
+ const newAggregator = await new AggregatorV3Mock__factory(
+ deployer,
+ ).deploy();
+
+ await mGlobalDataFeed
+ .connect(acDefaultAdmin)
+ .changeAggregator(newAggregator.address);
+
+ expect(await mGlobalDataFeed.aggregator()).to.eq(newAggregator.address);
+ });
+
+ it('should fail: when caller has no feed admin permission', async () => {
+ const { mGlobalDataFeed } = await loadFixture(mainnetUpgradeFixture);
+ const [deployer, unauthorized] = await ethers.getSigners();
+
+ const newAggregator = await new AggregatorV3Mock__factory(
+ deployer,
+ ).deploy();
+
+ await expect(
+ mGlobalDataFeed
+ .connect(unauthorized)
+ .changeAggregator(newAggregator.address),
+ ).revertedWithCustomError(
+ mGlobalDataFeed,
+ acErrors.WMAC_HASNT_PERMISSION().customErrorName,
+ );
+ });
+
+ it('should change aggregator via timelock', async () => {
+ const {
+ mGlobalDataFeed,
+ accessControl,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+ const [deployer] = await ethers.getSigners();
+
+ const ctx = {
+ timelockManager,
+ timelock,
+ accessControl,
+ owner: acDefaultAdmin,
+ };
+
+ const contractAdminRole = await mGlobalDataFeed.contractAdminRole();
+ await grantRoleViaTimelock(
+ ctx,
+ contractAdminRole,
+ acDefaultAdmin.address,
+ acDefaultAdmin,
+ );
+
+ const newAggregator = await new AggregatorV3Mock__factory(
+ deployer,
+ ).deploy();
+
+ await executeWriteViaTimelock(
+ ctx,
+ mGlobalDataFeed.address,
+ mGlobalDataFeed.interface.encodeFunctionData('changeAggregator', [
+ newAggregator.address,
+ ]),
+ acDefaultAdmin,
+ );
+
+ expect(await mGlobalDataFeed.aggregator()).to.eq(newAggregator.address);
+ });
+ });
});
- it('redeem request approve bulk', async function () {
- const {
- xaut0Whale,
- xaut0,
- vaultsManager,
- redemptionVaultSwapper,
- xbxaut,
- xbxautDataFeed,
- tokenManager,
- } = await loadFixture(hyperEvmUpgradeFixture);
-
- await mint(
- { tokenContract: xbxaut, owner: tokenManager },
- xaut0Whale,
- parseUnits('1'),
- );
- await approveBase18(xaut0Whale, xbxaut, redemptionVaultSwapper, 1);
-
- const { requestId, rate } = await redeemRequestTest(
- {
- mTBILL: xbxaut,
- mTokenToUsdDataFeed: xbxautDataFeed,
- redemptionVault: redemptionVaultSwapper,
- owner: vaultsManager,
- },
- xaut0,
- 1,
- { from: xaut0Whale },
- );
+ describe('mGLOBAL CustomAggregatorFeedGrowth', () => {
+ describe('latestRoundData()', () => {
+ it('should return valid round data', async () => {
+ const { mGlobalCustomFeedGrowth } = await loadFixture(
+ mainnetUpgradeFixture,
+ );
- await safeBulkApproveRedeemRequestTest(
- {
- mTBILL: xbxaut,
- mTokenToUsdDataFeed: xbxautDataFeed,
- redemptionVault: redemptionVaultSwapper,
- owner: vaultsManager,
- },
- [{ id: requestId! }],
- rate!,
- );
+ const [, answer] = await mGlobalCustomFeedGrowth.latestRoundData();
+ expect(answer).to.be.gt(0);
+ });
+ });
+
+ describe('decimals()', () => {
+ it('should return feed decimals', async () => {
+ const { mGlobalCustomFeedGrowth } = await loadFixture(
+ mainnetUpgradeFixture,
+ );
+
+ expect(await mGlobalCustomFeedGrowth.decimals()).to.eq(8);
+ });
+ });
+
+ describe('setRoundData()', () => {
+ it('should submit a new round after resetting delays', async () => {
+ const {
+ mGlobalCustomFeedGrowth,
+ accessControl,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+
+ await resetTimelockDelays({
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ });
+
+ const contractAdminRole =
+ await mGlobalCustomFeedGrowth.contractAdminRole();
+ await accessControl
+ .connect(acDefaultAdmin)
+ ['grantRole(bytes32,address)'](
+ contractAdminRole,
+ acDefaultAdmin.address,
+ );
+
+ const raw = await mGlobalCustomFeedGrowth.latestRoundDataRaw();
+ const roundBefore = await mGlobalCustomFeedGrowth.latestRound();
+ const dataTimestamp =
+ (await ethers.provider.getBlock('latest')).timestamp - 1;
+
+ await mGlobalCustomFeedGrowth
+ .connect(acDefaultAdmin)
+ .setRoundData(raw.answer, dataTimestamp, raw.growthApr);
+
+ expect(await mGlobalCustomFeedGrowth.latestRound()).to.eq(
+ roundBefore.add(1),
+ );
+ });
+
+ it('should fail: when caller has no feed admin permission', async () => {
+ const { mGlobalCustomFeedGrowth } = await loadFixture(
+ mainnetUpgradeFixture,
+ );
+ const [, unauthorized] = await ethers.getSigners();
+
+ const raw = await mGlobalCustomFeedGrowth.latestRoundDataRaw();
+ const dataTimestamp =
+ (await ethers.provider.getBlock('latest')).timestamp - 1;
+
+ await expect(
+ mGlobalCustomFeedGrowth
+ .connect(unauthorized)
+ .setRoundData(raw.answer, dataTimestamp, raw.growthApr),
+ ).revertedWithCustomError(
+ mGlobalCustomFeedGrowth,
+ acErrors.WMAC_HASNT_PERMISSION().customErrorName,
+ );
+ });
+
+ it('should submit a new round via timelock', async () => {
+ const {
+ mGlobalCustomFeedGrowth,
+ accessControl,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+
+ const ctx = {
+ timelockManager,
+ timelock,
+ accessControl,
+ owner: acDefaultAdmin,
+ };
+
+ const contractAdminRole =
+ await mGlobalCustomFeedGrowth.contractAdminRole();
+ await grantRoleViaTimelock(
+ ctx,
+ contractAdminRole,
+ acDefaultAdmin.address,
+ acDefaultAdmin,
+ );
+
+ const raw = await mGlobalCustomFeedGrowth.latestRoundDataRaw();
+ const roundBefore = await mGlobalCustomFeedGrowth.latestRound();
+ const dataTimestamp =
+ (await ethers.provider.getBlock('latest')).timestamp - 1;
+
+ await executeWriteViaTimelock(
+ ctx,
+ mGlobalCustomFeedGrowth.address,
+ mGlobalCustomFeedGrowth.interface.encodeFunctionData('setRoundData', [
+ raw.answer,
+ dataTimestamp,
+ raw.growthApr,
+ ]),
+ acDefaultAdmin,
+ );
+
+ expect(await mGlobalCustomFeedGrowth.latestRound()).to.eq(
+ roundBefore.add(1),
+ );
+ });
+ });
+
+ describe('setMaxGrowthApr()', () => {
+ it('should set max growth apr after resetting delays', async () => {
+ const {
+ mGlobalCustomFeedGrowth,
+ accessControl,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+
+ await resetTimelockDelays({
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ });
+
+ const contractAdminRole =
+ await mGlobalCustomFeedGrowth.contractAdminRole();
+ await accessControl
+ .connect(acDefaultAdmin)
+ ['grantRole(bytes32,address)'](
+ contractAdminRole,
+ acDefaultAdmin.address,
+ );
+
+ const newMaxGrowthApr = await mGlobalCustomFeedGrowth.maxGrowthApr();
+
+ await mGlobalCustomFeedGrowth
+ .connect(acDefaultAdmin)
+ .setMaxGrowthApr(newMaxGrowthApr);
+
+ expect(await mGlobalCustomFeedGrowth.maxGrowthApr()).to.eq(
+ newMaxGrowthApr,
+ );
+ });
+
+ it('should set max growth apr via timelock', async () => {
+ const {
+ mGlobalCustomFeedGrowth,
+ accessControl,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+
+ const ctx = {
+ timelockManager,
+ timelock,
+ accessControl,
+ owner: acDefaultAdmin,
+ };
+
+ const contractAdminRole =
+ await mGlobalCustomFeedGrowth.contractAdminRole();
+ await grantRoleViaTimelock(
+ ctx,
+ contractAdminRole,
+ acDefaultAdmin.address,
+ acDefaultAdmin,
+ );
+
+ const newMaxGrowthApr = await mGlobalCustomFeedGrowth.maxGrowthApr();
+
+ await executeWriteViaTimelock(
+ ctx,
+ mGlobalCustomFeedGrowth.address,
+ mGlobalCustomFeedGrowth.interface.encodeFunctionData(
+ 'setMaxGrowthApr',
+ [newMaxGrowthApr],
+ ),
+ acDefaultAdmin,
+ );
+
+ expect(await mGlobalCustomFeedGrowth.maxGrowthApr()).to.eq(
+ newMaxGrowthApr,
+ );
+ });
+ });
+
+ describe('setOnlyUp()', () => {
+ it('should set onlyUp after resetting delays', async () => {
+ const {
+ mGlobalCustomFeedGrowth,
+ accessControl,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+
+ await resetTimelockDelays({
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ });
+
+ const contractAdminRole =
+ await mGlobalCustomFeedGrowth.contractAdminRole();
+ await accessControl
+ .connect(acDefaultAdmin)
+ ['grantRole(bytes32,address)'](
+ contractAdminRole,
+ acDefaultAdmin.address,
+ );
+
+ const newOnlyUp = !(await mGlobalCustomFeedGrowth.onlyUp());
+
+ await mGlobalCustomFeedGrowth
+ .connect(acDefaultAdmin)
+ .setOnlyUp(newOnlyUp);
+
+ expect(await mGlobalCustomFeedGrowth.onlyUp()).to.eq(newOnlyUp);
+ });
+
+ it('should set onlyUp via timelock', async () => {
+ const {
+ mGlobalCustomFeedGrowth,
+ accessControl,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+
+ const ctx = {
+ timelockManager,
+ timelock,
+ accessControl,
+ owner: acDefaultAdmin,
+ };
+
+ const contractAdminRole =
+ await mGlobalCustomFeedGrowth.contractAdminRole();
+ await grantRoleViaTimelock(
+ ctx,
+ contractAdminRole,
+ acDefaultAdmin.address,
+ acDefaultAdmin,
+ );
+
+ const newOnlyUp = !(await mGlobalCustomFeedGrowth.onlyUp());
+
+ await executeWriteViaTimelock(
+ ctx,
+ mGlobalCustomFeedGrowth.address,
+ mGlobalCustomFeedGrowth.interface.encodeFunctionData('setOnlyUp', [
+ newOnlyUp,
+ ]),
+ acDefaultAdmin,
+ );
+
+ expect(await mGlobalCustomFeedGrowth.onlyUp()).to.eq(newOnlyUp);
+ });
+ });
+
+ describe('setMaxAnswerDeviation()', () => {
+ const newDeviation = parseUnits('1', 8);
+
+ it('should set max answer deviation after resetting delays', async () => {
+ const {
+ mGlobalCustomFeedGrowth,
+ accessControl,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+
+ await resetTimelockDelays({
+ timelockManager,
+ acDefaultAdmin,
+ accessControl,
+ timelock,
+ });
+
+ const contractAdminRole =
+ await mGlobalCustomFeedGrowth.contractAdminRole();
+ await accessControl
+ .connect(acDefaultAdmin)
+ ['grantRole(bytes32,address)'](
+ contractAdminRole,
+ acDefaultAdmin.address,
+ );
+
+ await mGlobalCustomFeedGrowth
+ .connect(acDefaultAdmin)
+ .setMaxAnswerDeviation(newDeviation);
+
+ expect(await mGlobalCustomFeedGrowth.maxAnswerDeviation()).to.eq(
+ newDeviation,
+ );
+ });
+
+ it('should set max answer deviation via timelock', async () => {
+ const {
+ mGlobalCustomFeedGrowth,
+ accessControl,
+ acDefaultAdmin,
+ timelockManager,
+ timelock,
+ } = await loadFixture(mainnetUpgradeFixture);
+
+ const ctx = {
+ timelockManager,
+ timelock,
+ accessControl,
+ owner: acDefaultAdmin,
+ };
+
+ const contractAdminRole =
+ await mGlobalCustomFeedGrowth.contractAdminRole();
+ await grantRoleViaTimelock(
+ ctx,
+ contractAdminRole,
+ acDefaultAdmin.address,
+ acDefaultAdmin,
+ );
+
+ await executeWriteViaTimelock(
+ ctx,
+ mGlobalCustomFeedGrowth.address,
+ mGlobalCustomFeedGrowth.interface.encodeFunctionData(
+ 'setMaxAnswerDeviation',
+ [newDeviation],
+ ),
+ acDefaultAdmin,
+ );
+
+ expect(await mGlobalCustomFeedGrowth.maxAnswerDeviation()).to.eq(
+ newDeviation,
+ );
+ });
+ });
});
});
diff --git a/test/integration/DepositVaultWithUSTB.test.ts b/test/integration/DepositVaultWithUSTB.test.ts
index cfa043e1..b2be35ad 100644
--- a/test/integration/DepositVaultWithUSTB.test.ts
+++ b/test/integration/DepositVaultWithUSTB.test.ts
@@ -112,7 +112,13 @@ describe('DepositVaultWithUSTB - Mainnet Fork Integration Tests', function () {
},
usdc,
usdcAmount,
- { from: testUser, revertMessage: 'DVU: unsupported USTB token' },
+ {
+ from: testUser,
+ revertCustomError: {
+ customErrorName: 'UnsupportedUSTBToken',
+ args: [usdc.address],
+ },
+ },
);
});
@@ -170,7 +176,12 @@ describe('DepositVaultWithUSTB - Mainnet Fork Integration Tests', function () {
},
usdc,
usdcAmount,
- { from: testUser, revertMessage: 'DVU: USTB fee is not 0' },
+ {
+ from: testUser,
+ revertCustomError: {
+ customErrorName: 'USTBFeeNotZero',
+ },
+ },
);
});
});
diff --git a/test/integration/RedemptionVaultWithAave.test.ts b/test/integration/RedemptionVaultWithAave.test.ts
index cc1b6f4f..2df4cae5 100644
--- a/test/integration/RedemptionVaultWithAave.test.ts
+++ b/test/integration/RedemptionVaultWithAave.test.ts
@@ -179,8 +179,10 @@ describe('RedemptionVaultWithAave - Mainnet Fork Integration Tests', function ()
mTBILLAmount,
);
- // Perform redemption: 1000 mTBILL @ 1:1 rate, 1% fee = 990 USDC needed
- // Vault has 500 USDC, so shortfall = 490 USDC from Aave
+ // Perform redemption: 1000 mTBILL @ 1:1 rate, 1% fee.
+ // The vault sources the gross redeem amount (1000 USDC, fee-inclusive) and
+ // keeps the 10 USDC fee. With 500 USDC on hand the shortfall pulled from
+ // Aave is 1000 - 500 = 500 USDC.
const result = await redeemInstantWithAaveTest(
{
redemptionVault: redemptionVaultWithAave,
@@ -197,8 +199,8 @@ describe('RedemptionVaultWithAave - Mainnet Fork Integration Tests', function ()
// Verify user received USDC
expect(result?.userUSDCReceived).to.equal(parseUnits('990', 6));
- // Verify aToken decrease equals the shortfall (990 - 500 = 490)
- const expectedShortfall = parseUnits('490', 6);
+ // Verify aToken decrease equals the shortfall (1000 - 500 = 500)
+ const expectedShortfall = parseUnits('500', 6);
expect(result?.aTokenUsed).to.be.closeTo(
expectedShortfall,
parseUnits('1', 6), // 1 USDC tolerance for Aave interest accrual
@@ -250,7 +252,7 @@ describe('RedemptionVaultWithAave - Mainnet Fork Integration Tests', function ()
mTBILLAmount,
{
from: testUser,
- revertMessage: 'RVA: insufficient aToken balance',
+ revertMessage: 'ERC20: transfer amount exceeds balance',
},
);
});
@@ -309,7 +311,7 @@ describe('RedemptionVaultWithAave - Mainnet Fork Integration Tests', function ()
mTBILLAmount,
{
from: testUser,
- revertMessage: 'RVA: no pool for token',
+ revertMessage: 'ERC20: transfer amount exceeds balance',
},
);
});
diff --git a/test/integration/RedemptionVaultWithMToken.test.ts b/test/integration/RedemptionVaultWithMToken.test.ts
index 53d5da7c..37f40c01 100644
--- a/test/integration/RedemptionVaultWithMToken.test.ts
+++ b/test/integration/RedemptionVaultWithMToken.test.ts
@@ -228,7 +228,7 @@ describe('RedemptionVaultWithMToken - Mainnet Fork Integration Tests', function
);
await redemptionVaultWithMToken
.connect(owner)
- .withdrawToken(mTBILL.address, vaultMTBILL, owner.address);
+ .withdrawToken(mTBILL.address, vaultMTBILL);
const mFONEAmount = 1000;
@@ -252,7 +252,7 @@ describe('RedemptionVaultWithMToken - Mainnet Fork Integration Tests', function
parseUnits(String(mFONEAmount)),
0,
),
- ).to.be.revertedWith('RVMT: balance < needed');
+ ).to.be.revertedWith('ERC20: transfer amount exceeds balance');
});
});
});
diff --git a/test/integration/RedemptionVaultWithMorpho.test.ts b/test/integration/RedemptionVaultWithMorpho.test.ts
index b0b54460..eedebfe0 100644
--- a/test/integration/RedemptionVaultWithMorpho.test.ts
+++ b/test/integration/RedemptionVaultWithMorpho.test.ts
@@ -251,7 +251,7 @@ describe('RedemptionVaultWithMorpho - Mainnet Fork Integration Tests', function
mTBILLAmount,
{
from: testUser,
- revertMessage: 'RVM: insufficient shares',
+ revertMessage: 'ERC20: transfer amount exceeds balance',
},
);
});
@@ -310,7 +310,7 @@ describe('RedemptionVaultWithMorpho - Mainnet Fork Integration Tests', function
mTBILLAmount,
{
from: testUser,
- revertMessage: 'RVM: no vault for token',
+ revertMessage: 'ERC20: transfer amount exceeds balance',
},
);
});
diff --git a/test/integration/RedemptionVaultWithUSTB.test.ts b/test/integration/RedemptionVaultWithUSTB.test.ts
index 0ed4f87c..eaafc3c6 100644
--- a/test/integration/RedemptionVaultWithUSTB.test.ts
+++ b/test/integration/RedemptionVaultWithUSTB.test.ts
@@ -208,7 +208,7 @@ describe('RedemptionVaultWithUSTB - Mainnet Fork Integration Tests', function ()
mTBILLAmount,
{
from: testUser,
- revertMessage: 'RVU: ustb fee not zero',
+ revertMessage: 'ERC20: transfer amount exceeds balance',
},
);
});
@@ -261,7 +261,7 @@ describe('RedemptionVaultWithUSTB - Mainnet Fork Integration Tests', function ()
mTBILLAmount,
{
from: testUser,
- revertMessage: 'RVU: insufficient USTB balance',
+ revertMessage: 'ERC20: transfer amount exceeds balance',
},
);
});
diff --git a/test/integration/fixtures/aave.fixture.ts b/test/integration/fixtures/aave.fixture.ts
index 25402143..0ba88788 100644
--- a/test/integration/fixtures/aave.fixture.ts
+++ b/test/integration/fixtures/aave.fixture.ts
@@ -2,16 +2,18 @@ import { parseUnits } from 'ethers/lib/utils';
import { ethers } from 'hardhat';
import { rpcUrls } from '../../../config';
-import { getAllRoles } from '../../../helpers/roles';
import {
- MidasAccessControlTest,
- MTBILLTest,
DepositVaultWithAaveTest,
RedemptionVaultWithAaveTest,
DataFeedTest,
AggregatorV3Mock,
} from '../../../typechain-types';
import { deployProxyContract } from '../../common/deploy.helpers';
+import {
+ getInitializerParamsDv,
+ getInitializerParamsRv,
+} from '../../common/fixtures';
+import { setupIntegrationBase } from '../helpers/ac.helpers';
import { impersonateAndFundAccount, resetFork } from '../helpers/fork.helpers';
import { MAINNET_ADDRESSES } from '../helpers/mainnet-addresses';
@@ -20,50 +22,18 @@ const FORK_BLOCK_NUMBER = 24441000;
async function setupAaveBase() {
await resetFork(rpcUrls.main, FORK_BLOCK_NUMBER);
- const [
+ const {
+ accessControl,
+ mTBILL,
owner,
tokensReceiver,
feeReceiver,
requestRedeemer,
vaultAdmin,
testUser,
- ] = await ethers.getSigners();
- const allRoles = getAllRoles();
-
- const accessControl = await deployProxyContract(
- 'MidasAccessControlTest',
- [],
- );
-
- const mTBILL = await deployProxyContract('mTBILLTest', [
- accessControl.address,
- ]);
-
- const rolesArray = [
- allRoles.common.defaultAdmin,
- allRoles.tokenRoles.mTBILL.minter,
- allRoles.tokenRoles.mTBILL.burner,
- allRoles.tokenRoles.mTBILL.pauser,
- allRoles.tokenRoles.mTBILL.depositVaultAdmin,
- allRoles.tokenRoles.mTBILL.redemptionVaultAdmin,
- allRoles.common.greenlistedOperator,
- ];
-
- for (const role of rolesArray) {
- await accessControl.grantRole(role, owner.address);
- }
-
- await accessControl.grantRole(
- allRoles.tokenRoles.mTBILL.depositVaultAdmin,
- vaultAdmin.address,
- );
-
- await accessControl.grantRole(
- allRoles.tokenRoles.mTBILL.redemptionVaultAdmin,
- vaultAdmin.address,
- );
-
- await accessControl.grantRole(allRoles.common.greenlisted, testUser.address);
+ clawbackReceiver,
+ roles: allRoles,
+ } = await setupIntegrationBase();
const usdcAggregator = (await (
await ethers.getContractFactory('AggregatorV3Mock')
@@ -167,6 +137,7 @@ async function setupAaveBase() {
usdtWhale,
aUsdtWhale,
roles: allRoles,
+ clawbackReceiver,
};
}
@@ -178,30 +149,22 @@ export async function aaveDepositFixture() {
const depositVaultWithAave =
await deployProxyContract(
'DepositVaultWithAaveTest',
- [
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: base.mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: base.feeReceiver.address,
- tokensReceiver: base.tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: ethers.constants.MaxUint256,
- },
- ethers.constants.AddressZero, // sanctions list
- 200,
- parseUnits('0'),
- 0,
- ethers.constants.MaxUint256,
- ],
+ getInitializerParamsDv({
+ accessControl: accessControl.address,
+ mockedSanctionsList: ethers.constants.AddressZero,
+ mTBILL: mTBILL.address,
+ mTokenToUsdDataFeed: base.mTokenToUsdDataFeed.address,
+ tokensReceiver: base.tokensReceiver.address,
+ minAmount: parseUnits('0'),
+ instantFee: 100,
+ variationTolerance: 200,
+ maxApproveRequestId: ethers.constants.MaxUint256,
+ }),
+ undefined,
);
// Grant MINTER_ROLE to deposit vault
- await accessControl.grantRole(
+ await accessControl['grantRole(bytes32,address)'](
roles.tokenRoles.mTBILL.minter,
depositVaultWithAave.address,
);
@@ -247,35 +210,23 @@ export async function aaveRedemptionFixture() {
const redemptionVaultWithAave =
await deployProxyContract(
'RedemptionVaultWithAaveTest',
- [
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: base.mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: base.feeReceiver.address,
- tokensReceiver: base.tokensReceiver.address,
- },
- {
- instantFee: 100, // 1%
- instantDailyLimit: ethers.constants.MaxUint256,
- },
- ethers.constants.AddressZero, // sanctions list
- 200, // variation tolerance 2%
- parseUnits('100', 18), // min amount
- {
- minFiatRedeemAmount: parseUnits('1000', 18),
- fiatAdditionalFee: 100,
- fiatFlatFee: parseUnits('10', 18),
- },
- base.requestRedeemer.address,
- ],
- 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,(uint256,uint256,uint256),address)',
+ getInitializerParamsRv({
+ accessControl: accessControl.address,
+ mockedSanctionsList: ethers.constants.AddressZero,
+ mTBILL: mTBILL.address,
+ mTokenToUsdDataFeed: base.mTokenToUsdDataFeed.address,
+ tokensReceiver: base.tokensReceiver.address,
+ minAmount: parseUnits('0'),
+ instantFee: 100,
+ variationTolerance: 200,
+ maxApproveRequestId: ethers.constants.MaxUint256,
+ requestRedeemer: base.requestRedeemer.address,
+ }),
+ undefined,
);
// Grant BURN_ROLE to redemption vault
- await accessControl.grantRole(
+ await accessControl['grantRole(bytes32,address)'](
roles.tokenRoles.mTBILL.burner,
redemptionVaultWithAave.address,
);
diff --git a/test/integration/fixtures/morpho.fixture.ts b/test/integration/fixtures/morpho.fixture.ts
index 299984d6..99f62a73 100644
--- a/test/integration/fixtures/morpho.fixture.ts
+++ b/test/integration/fixtures/morpho.fixture.ts
@@ -2,16 +2,18 @@ import { parseUnits } from 'ethers/lib/utils';
import { ethers } from 'hardhat';
import { rpcUrls } from '../../../config';
-import { getAllRoles } from '../../../helpers/roles';
import {
- MidasAccessControlTest,
- MTBILLTest,
DepositVaultWithMorphoTest,
RedemptionVaultWithMorphoTest,
DataFeedTest,
AggregatorV3Mock,
} from '../../../typechain-types';
import { deployProxyContract } from '../../common/deploy.helpers';
+import {
+ getInitializerParamsDv,
+ getInitializerParamsRv,
+} from '../../common/fixtures';
+import { setupIntegrationBase } from '../helpers/ac.helpers';
import { impersonateAndFundAccount, resetFork } from '../helpers/fork.helpers';
import { MAINNET_ADDRESSES } from '../helpers/mainnet-addresses';
@@ -21,50 +23,17 @@ const FORK_BLOCK_NUMBER = 24441000;
async function setupMorphoBase() {
await resetFork(rpcUrls.main, FORK_BLOCK_NUMBER);
- const [
+ const {
+ accessControl,
+ mTBILL,
owner,
tokensReceiver,
feeReceiver,
requestRedeemer,
vaultAdmin,
testUser,
- ] = await ethers.getSigners();
- const allRoles = getAllRoles();
-
- const accessControl = await deployProxyContract(
- 'MidasAccessControlTest',
- [],
- );
-
- const mTBILL = await deployProxyContract('mTBILLTest', [
- accessControl.address,
- ]);
-
- const rolesArray = [
- allRoles.common.defaultAdmin,
- allRoles.tokenRoles.mTBILL.minter,
- allRoles.tokenRoles.mTBILL.burner,
- allRoles.tokenRoles.mTBILL.pauser,
- allRoles.tokenRoles.mTBILL.depositVaultAdmin,
- allRoles.tokenRoles.mTBILL.redemptionVaultAdmin,
- allRoles.common.greenlistedOperator,
- ];
-
- for (const role of rolesArray) {
- await accessControl.grantRole(role, owner.address);
- }
-
- await accessControl.grantRole(
- allRoles.tokenRoles.mTBILL.depositVaultAdmin,
- vaultAdmin.address,
- );
-
- await accessControl.grantRole(
- allRoles.tokenRoles.mTBILL.redemptionVaultAdmin,
- vaultAdmin.address,
- );
-
- await accessControl.grantRole(allRoles.common.greenlisted, testUser.address);
+ roles: allRoles,
+ } = await setupIntegrationBase();
const usdcAggregator = (await (
await ethers.getContractFactory('AggregatorV3Mock')
@@ -180,30 +149,22 @@ export async function morphoDepositFixture() {
const depositVaultWithMorpho =
await deployProxyContract(
'DepositVaultWithMorphoTest',
- [
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: base.mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: base.feeReceiver.address,
- tokensReceiver: base.tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: ethers.constants.MaxUint256,
- },
- ethers.constants.AddressZero, // sanctions list
- 200,
- parseUnits('0'),
- 0,
- ethers.constants.MaxUint256,
- ],
+ getInitializerParamsDv({
+ accessControl: accessControl.address,
+ mockedSanctionsList: ethers.constants.AddressZero,
+ mTBILL: mTBILL.address,
+ mTokenToUsdDataFeed: base.mTokenToUsdDataFeed.address,
+ tokensReceiver: base.tokensReceiver.address,
+ minAmount: parseUnits('0'),
+ instantFee: 100,
+ variationTolerance: 200,
+ maxApproveRequestId: ethers.constants.MaxUint256,
+ }),
+ undefined,
);
// Grant MINTER_ROLE to deposit vault
- await accessControl.grantRole(
+ await accessControl['grantRole(bytes32,address)'](
roles.tokenRoles.mTBILL.minter,
depositVaultWithMorpho.address,
);
@@ -255,35 +216,23 @@ export async function morphoRedemptionFixture() {
const redemptionVaultWithMorpho =
await deployProxyContract(
'RedemptionVaultWithMorphoTest',
- [
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: base.mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: base.feeReceiver.address,
- tokensReceiver: base.tokensReceiver.address,
- },
- {
- instantFee: 100, // 1%
- instantDailyLimit: ethers.constants.MaxUint256,
- },
- ethers.constants.AddressZero, // sanctions list
- 200, // variation tolerance 2%
- parseUnits('100', 18), // min amount
- {
- minFiatRedeemAmount: parseUnits('1000', 18),
- fiatAdditionalFee: 100,
- fiatFlatFee: parseUnits('10', 18),
- },
- base.requestRedeemer.address,
- ],
- 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,(uint256,uint256,uint256),address)',
+ getInitializerParamsRv({
+ accessControl: accessControl.address,
+ mockedSanctionsList: ethers.constants.AddressZero,
+ mTBILL: mTBILL.address,
+ mTokenToUsdDataFeed: base.mTokenToUsdDataFeed.address,
+ tokensReceiver: base.tokensReceiver.address,
+ minAmount: parseUnits('100', 18),
+ instantFee: 100,
+ variationTolerance: 200,
+ maxApproveRequestId: ethers.constants.MaxUint256,
+ requestRedeemer: base.requestRedeemer.address,
+ }),
+ undefined,
);
// Grant BURN_ROLE to redemption vault
- await accessControl.grantRole(
+ await accessControl['grantRole(bytes32,address)'](
roles.tokenRoles.mTBILL.burner,
redemptionVaultWithMorpho.address,
);
diff --git a/test/integration/fixtures/mtoken.fixture.ts b/test/integration/fixtures/mtoken.fixture.ts
index 329aa6f9..c8f597f9 100644
--- a/test/integration/fixtures/mtoken.fixture.ts
+++ b/test/integration/fixtures/mtoken.fixture.ts
@@ -2,10 +2,7 @@ import { parseUnits } from 'ethers/lib/utils';
import { ethers } from 'hardhat';
import { rpcUrls } from '../../../config';
-import { getAllRoles } from '../../../helpers/roles';
import {
- MidasAccessControlTest,
- MTBILLTest,
DepositVaultTest,
DepositVaultWithMTokenTest,
RedemptionVaultTest,
@@ -14,6 +11,20 @@ import {
AggregatorV3Mock,
} from '../../../typechain-types';
import { deployProxyContract } from '../../common/deploy.helpers';
+import {
+ getInitializerParamsDv,
+ getInitializerParamsDvWithMToken,
+ getInitializerParamsRv,
+ getInitializerParamsRvWithMToken,
+} from '../../common/fixtures';
+import {
+ DV_MTOKEN_INIT_FN,
+ RV_MTOKEN_INIT_FN,
+} from '../../common/vault-initializer.helpers';
+import {
+ deployIntegrationMToken,
+ setupIntegrationBase,
+} from '../helpers/ac.helpers';
import { impersonateAndFundAccount, resetFork } from '../helpers/fork.helpers';
import { MAINNET_ADDRESSES } from '../helpers/mainnet-addresses';
@@ -22,58 +33,33 @@ const FORK_BLOCK_NUMBER = 24441000;
async function setupMTokenBase() {
await resetFork(rpcUrls.main, FORK_BLOCK_NUMBER);
- const [
+ const {
+ accessControl,
+ mTBILL,
owner,
tokensReceiver,
feeReceiver,
requestRedeemer,
vaultAdmin,
testUser,
- targetTokensReceiver,
- ] = await ethers.getSigners();
- const allRoles = getAllRoles();
-
- const accessControl = await deployProxyContract(
- 'MidasAccessControlTest',
- [],
- );
+ clawbackReceiver,
+ regularUsers,
+ roles: allRoles,
+ } = await setupIntegrationBase();
- // "Target" mToken (simulating mTBILL)
- const mTBILL = await deployProxyContract('mTBILLTest', [
- accessControl.address,
- ]);
+ // Extra receiver so USDC flowing through the target vault doesn't contaminate
+ // the product vault's tokensReceiver balance assertions.
+ const targetTokensReceiver = regularUsers[0];
- // "Product" mToken (simulating mFONE)
- const mFONE = await deployProxyContract('mTBILLTest', [
+ // "Product" mToken (simulating mFONE). Shares the mTBILL role hashes so the
+ // product vaults can mint/burn it with the same granted roles.
+ const mFONE = await deployIntegrationMToken(
accessControl.address,
- ]);
-
- const rolesArray = [
- allRoles.common.defaultAdmin,
- allRoles.tokenRoles.mTBILL.minter,
- allRoles.tokenRoles.mTBILL.burner,
- allRoles.tokenRoles.mTBILL.pauser,
- allRoles.tokenRoles.mTBILL.depositVaultAdmin,
- allRoles.tokenRoles.mTBILL.redemptionVaultAdmin,
- allRoles.common.greenlistedOperator,
- ];
-
- for (const role of rolesArray) {
- await accessControl.grantRole(role, owner.address);
- }
-
- await accessControl.grantRole(
- allRoles.tokenRoles.mTBILL.depositVaultAdmin,
- vaultAdmin.address,
- );
-
- await accessControl.grantRole(
- allRoles.tokenRoles.mTBILL.redemptionVaultAdmin,
- vaultAdmin.address,
+ clawbackReceiver.address,
+ allRoles.tokenRoles.mTBILL,
+ { name: 'mFONE', symbol: 'mFONE' },
);
- await accessControl.grantRole(allRoles.common.greenlisted, testUser.address);
-
// USDC data feed
const usdcAggregator = (await (
await ethers.getContractFactory('AggregatorV3Mock')
@@ -171,30 +157,22 @@ export async function mTokenDepositFixture() {
// doesn't contaminate the product DV's tokensReceiver balance assertions
const targetDepositVault = await deployProxyContract(
'DepositVaultTest',
- [
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: base.mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: base.feeReceiver.address,
- tokensReceiver: base.targetTokensReceiver.address,
- },
- {
- instantFee: 0,
- instantDailyLimit: ethers.constants.MaxUint256,
- },
- ethers.constants.AddressZero,
- 200,
- parseUnits('0'),
- 0,
- ethers.constants.MaxUint256,
- ],
+ getInitializerParamsDv({
+ accessControl: accessControl.address,
+ mockedSanctionsList: ethers.constants.AddressZero,
+ mTBILL: mTBILL.address,
+ mTokenToUsdDataFeed: base.mTokenToUsdDataFeed.address,
+ tokensReceiver: base.targetTokensReceiver.address,
+ minAmount: parseUnits('0'),
+ instantFee: 0,
+ variationTolerance: 200,
+ maxApproveRequestId: ethers.constants.MaxUint256,
+ }),
+ undefined,
);
// Grant minter to target DV (so it can mint mTBILL)
- await accessControl.grantRole(
+ await accessControl['grantRole(bytes32,address)'](
roles.tokenRoles.mTBILL.minter,
targetDepositVault.address,
);
@@ -214,38 +192,29 @@ export async function mTokenDepositFixture() {
const depositVaultWithMToken =
await deployProxyContract(
'DepositVaultWithMTokenTest',
- [
- accessControl.address,
- {
- mToken: mFONE.address,
- mTokenDataFeed: base.mFoneToUsdDataFeed.address,
- },
- {
- feeReceiver: base.feeReceiver.address,
- tokensReceiver: base.tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: ethers.constants.MaxUint256,
- },
- ethers.constants.AddressZero,
- 200,
- parseUnits('0'),
- 0,
- ethers.constants.MaxUint256,
- targetDepositVault.address,
- ],
- 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,uint256,uint256,address)',
+ getInitializerParamsDvWithMToken({
+ accessControl: accessControl.address,
+ mockedSanctionsList: ethers.constants.AddressZero,
+ mTBILL: mFONE.address,
+ mTokenToUsdDataFeed: base.mFoneToUsdDataFeed.address,
+ tokensReceiver: base.tokensReceiver.address,
+ minAmount: parseUnits('0'),
+ instantFee: 100,
+ variationTolerance: 200,
+ maxApproveRequestId: ethers.constants.MaxUint256,
+ depositVault: targetDepositVault.address,
+ }),
+ DV_MTOKEN_INIT_FN,
);
// Grant minter to product DV (so it can mint mFONE)
- await accessControl.grantRole(
+ await accessControl['grantRole(bytes32,address)'](
roles.tokenRoles.mTBILL.minter,
depositVaultWithMToken.address,
);
// Greenlist the product DV so it can call depositInstant on target DV
- await accessControl.grantRole(
+ await accessControl['grantRole(bytes32,address)'](
roles.common.greenlisted,
depositVaultWithMToken.address,
);
@@ -253,7 +222,7 @@ export async function mTokenDepositFixture() {
// Waive fees on target DV for the product DV (required for _autoInvest)
await targetDepositVault
.connect(owner)
- .addWaivedFeeAccount(depositVaultWithMToken.address);
+ .setWaivedFeeAccount(depositVaultWithMToken.address, true);
// Add USDC as payment token on product DV
await depositVaultWithMToken
@@ -280,34 +249,23 @@ export async function mTokenRedemptionFixture() {
// Deploy target RV (plain RedemptionVault for mTBILL)
const targetRedemptionVault = await deployProxyContract(
'RedemptionVaultTest',
- [
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: base.mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: base.feeReceiver.address,
- tokensReceiver: base.tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: ethers.constants.MaxUint256,
- },
- ethers.constants.AddressZero,
- 200,
- parseUnits('100', 18),
- {
- minFiatRedeemAmount: parseUnits('1000', 18),
- fiatAdditionalFee: 100,
- fiatFlatFee: parseUnits('10', 18),
- },
- base.requestRedeemer.address,
- ],
+ getInitializerParamsRv({
+ accessControl: accessControl.address,
+ mockedSanctionsList: ethers.constants.AddressZero,
+ mTBILL: mTBILL.address,
+ mTokenToUsdDataFeed: base.mTokenToUsdDataFeed.address,
+ tokensReceiver: base.tokensReceiver.address,
+ minAmount: parseUnits('100', 18),
+ instantFee: 100,
+ variationTolerance: 200,
+ maxApproveRequestId: ethers.constants.MaxUint256,
+ requestRedeemer: base.requestRedeemer.address,
+ }),
+ undefined,
);
// Grant BURN_ROLE to target RV (so it can burn mTBILL)
- await accessControl.grantRole(
+ await accessControl['grantRole(bytes32,address)'](
roles.tokenRoles.mTBILL.burner,
targetRedemptionVault.address,
);
@@ -332,36 +290,24 @@ export async function mTokenRedemptionFixture() {
const redemptionVaultWithMToken =
await deployProxyContract(
'RedemptionVaultWithMTokenTest',
- [
- accessControl.address,
- {
- mToken: mFONE.address,
- mTokenDataFeed: base.mFoneToUsdDataFeed.address,
- },
- {
- feeReceiver: base.feeReceiver.address,
- tokensReceiver: base.tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: ethers.constants.MaxUint256,
- },
- ethers.constants.AddressZero,
- 200,
- parseUnits('100', 18),
- {
- minFiatRedeemAmount: parseUnits('1000', 18),
- fiatAdditionalFee: 100,
- fiatFlatFee: parseUnits('10', 18),
- },
- base.requestRedeemer.address,
- targetRedemptionVault.address,
- ],
- 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,(uint256,uint256,uint256),address,address)',
+ getInitializerParamsRvWithMToken({
+ accessControl: accessControl.address,
+ mockedSanctionsList: ethers.constants.AddressZero,
+ mTBILL: mFONE.address,
+ mTokenToUsdDataFeed: base.mFoneToUsdDataFeed.address,
+ tokensReceiver: base.tokensReceiver.address,
+ minAmount: parseUnits('100', 18),
+ instantFee: 100,
+ variationTolerance: 200,
+ maxApproveRequestId: ethers.constants.MaxUint256,
+ requestRedeemer: base.requestRedeemer.address,
+ redemptionVault: targetRedemptionVault.address,
+ }),
+ RV_MTOKEN_INIT_FN,
);
// Grant BURN_ROLE to product RV (so it can burn mFONE)
- await accessControl.grantRole(
+ await accessControl['grantRole(bytes32,address)'](
roles.tokenRoles.mTBILL.burner,
redemptionVaultWithMToken.address,
);
@@ -378,7 +324,7 @@ export async function mTokenRedemptionFixture() {
);
// Greenlist the product RV on access control (target RV requires greenlist)
- await accessControl.grantRole(
+ await accessControl['grantRole(bytes32,address)'](
roles.common.greenlisted,
redemptionVaultWithMToken.address,
);
@@ -386,10 +332,13 @@ export async function mTokenRedemptionFixture() {
// Waive fees on target RV for the product RV address
await targetRedemptionVault
.connect(owner)
- .addWaivedFeeAccount(redemptionVaultWithMToken.address);
+ .setWaivedFeeAccount(redemptionVaultWithMToken.address, true);
// Mint mTBILL to product RV (simulating Fordefi deposit)
- await accessControl.grantRole(roles.tokenRoles.mTBILL.minter, owner.address);
+ await accessControl['grantRole(bytes32,address)'](
+ roles.tokenRoles.mTBILL.minter,
+ owner.address,
+ );
await mTBILL.mint(redemptionVaultWithMToken.address, parseUnits('50000'));
return {
diff --git a/test/integration/fixtures/pyth.fixture.ts b/test/integration/fixtures/pyth.fixture.ts
index 2a6d3097..256f0cac 100644
--- a/test/integration/fixtures/pyth.fixture.ts
+++ b/test/integration/fixtures/pyth.fixture.ts
@@ -44,7 +44,7 @@ export async function pythAdapterFixture(
);
// Grant default admin role to deployer
- await midasAccessControl.grantRole(
+ await midasAccessControl['grantRole(bytes32,address)'](
allRoles.common.defaultAdmin,
deployer.address,
);
diff --git a/test/integration/fixtures/upgrades.fixture.ts b/test/integration/fixtures/upgrades.fixture.ts
index f84a1580..63ff1bcd 100644
--- a/test/integration/fixtures/upgrades.fixture.ts
+++ b/test/integration/fixtures/upgrades.fixture.ts
@@ -1,26 +1,81 @@
-import { mine } from '@nomicfoundation/hardhat-network-helpers';
-import { parseUnits } from 'ethers/lib/utils';
+import { mine, setBalance } from '@nomicfoundation/hardhat-network-helpers';
+import { ContractFactory } from 'ethers';
+import { parseUnits } from 'ethers/lib.esm/utils';
import { ethers } from 'hardhat';
+import hre from 'hardhat';
import { rpcUrls } from '../../../config';
-import { MToken } from '../../../typechain-types';
+import { getAllRoles, getRolesForToken } from '../../../helpers/roles';
+import {
+ MidasAccessControl,
+ MidasAccessControlTimelockController,
+ MidasPauseManager,
+ MidasTimelockManager,
+ MidasAccessControl__factory,
+ DataFeed__factory,
+ CustomAggregatorV3CompatibleFeedGrowth__factory,
+ MToken,
+ DataFeed,
+ CustomAggregatorV3CompatibleFeed,
+ CustomAggregatorV3CompatibleFeed__factory,
+ MToken__factory,
+ CustomAggregatorV3CompatibleFeedGrowth,
+} from '../../../typechain-types';
+import { NO_DELAY, NULL_DELAY } from '../../common/ac.helpers';
+import { asyncForEach, Constructor } from '../../common/common.helpers';
+import { deployProxyContract } from '../../common/deploy.helpers';
import { impersonateAndFundAccount, resetFork } from '../helpers/fork.helpers';
-export async function hyperEvmUpgradeFixture() {
- const dvProxyAddress = '0x48fb106Ef0c0C1a19EdDC9C5d27A945E66DA1C4E';
- const rvSwapperProxyAddress = '0xD26bB9B45140D17eF14FbD4fCa8Cf0d610ac50E7';
+const mTokenConstructorArgs = (roles: ReturnType) => [
+ roles.tokenManager,
+ roles.minter,
+ roles.burner,
+ roles.greenlisted,
+ roles.minBalanceExempt,
+];
- const newDvImplementationAddress =
- '0x448897fEc88D145E22cA8594F1a928C72e1De8a6';
- const newRvSwapperImplementationAddress =
- '0x67581417D7AFe1E02d1Da4AbfD4fa6a2774e625f';
+const mTokenReinitializerParams = (
+ clawbackReceiver: string,
+ isPermissioned: boolean,
+ isMinHoldingBalanceEnforced = false,
+) => ({
+ fn: 'initializeV3',
+ args: [
+ clawbackReceiver,
+ ethers.constants.MaxUint256,
+ isPermissioned,
+ isMinHoldingBalanceEnforced,
+ ],
+});
- const tokenManagerAddress = '0x46a12DDCA8c92742251b2a2c33610BF8Ae090cd9';
- const vaultsManagerAddress = '0x2ACB4BdCbEf02f81BF713b696Ac26390d7f79A12';
+export async function mainnetUpgradeFixture() {
+ const acDefaultAdminAddress = '0xd4195CF4df289a4748C1A7B6dDBE770e27bA1227';
+
+ const acAddress = '0x0312A9D1Ff2372DDEdCBB21e4B6389aFc919aC4B';
const proxyAdminAddress = '0xbf25b58cB8DfaD688F7BcB2b87D71C23A6600AaC';
- const [customRecipient] = await ethers.getSigners();
- await resetFork(rpcUrls.hyperevm, 9874404);
+ // mGLOBAL addresses
+ const mGlobalAddress = '0x7433806912Eae67919e66aea853d46Fa0aef98A8';
+ const mGlobalCustomFeedGrowthAddress =
+ '0x66Aa9fcD63DF74e1f67A9452E6E59Fbc67f75E38';
+ const mGlobalDataFeedAddress = '0x58476f452df10E6Bf17dc1fee418E98dE9e14868';
+
+ // mTBILL addresses
+ const mTbillDataFeedAddress = '0xfCEE9754E8C375e145303b7cE7BEca3201734A2B';
+ const mTbillCustomFeedAddress = '0x056339C044055819E8Db84E71f5f2E1F536b2E5b';
+ const mTbillAddress = '0xDD629E5241CbC5919847783e6C96B2De4754e438';
+
+ // mBTC addresses
+ const mBtcDataFeedAddress = '0x9987BE0c1dc5Cd284a4D766f4B5feB4F3cb3E28e';
+ const mBtcCustomFeedAddress = '0xA537EF0343e83761ED42B8E017a1e495c9a189Ee';
+ const mBtcAddress = '0x007115416AB6c266329a03B09a8aa39aC2eF7d9d';
+
+ // mROX addresses
+ const mRoxAddress = '0x67E1F506B148d0Fc95a4E3fFb49068ceB6855c05';
+
+ const [clawbackReceiver, ...signers] = await ethers.getSigners();
+
+ await resetFork(rpcUrls.main, 25193577);
await mine();
@@ -34,92 +89,239 @@ export async function hyperEvmUpgradeFixture() {
proxyAdminOwnerAddress,
);
- const xaut0 = await ethers.getContractAt(
- 'ERC20',
- '0xf4D9235269a96aaDaFc9aDAe454a0618eBE37949',
- );
+ const acDefaultAdmin = await impersonateAndFundAccount(acDefaultAdminAddress);
- const xaut0Whale = await impersonateAndFundAccount(
- '0xaF7FD67AE6B3E25F83291D5600fBe3B776EEa4d3',
- );
+ await setBalance(proxyAdminOwner.address, parseUnits('1000', 18));
+ await setBalance(acDefaultAdmin.address, parseUnits('1000', 18));
- const vaultsManager = await impersonateAndFundAccount(vaultsManagerAddress);
+ const accessControl = (await ethers.getContractAt(
+ 'MidasAccessControl',
+ acAddress,
+ )) as MidasAccessControl;
- const tokenManager = await impersonateAndFundAccount(tokenManagerAddress);
-
- await proxyAdmin
- .connect(proxyAdminOwner)
- .upgrade(dvProxyAddress, newDvImplementationAddress);
+ const allRoles = getAllRoles();
+ const addressesMap: Record<
+ string,
+ {
+ proxy: string;
+ implementation: Constructor;
+ constructorArgs?: unknown[];
+ reinitializerParams?: {
+ fn: string;
+ args: unknown[];
+ };
+ }[]
+ > = {
+ ac: [
+ {
+ proxy: acAddress,
+ implementation: MidasAccessControl__factory,
+ reinitializerParams: {
+ fn: 'initializeV2',
+ args: [NULL_DELAY, [allRoles.tokenRoles.mGLOBAL.greenlisted]],
+ },
+ },
+ ],
+ mTbill: [
+ // 1 gap on the token level (mTBILL), missing gaps in Blacklistable and WithMidasAccessControl
+ {
+ proxy: mTbillAddress,
+ implementation: MToken__factory,
+ constructorArgs: mTokenConstructorArgs(allRoles.tokenRoles.mTBILL),
+ reinitializerParams: mTokenReinitializerParams(
+ clawbackReceiver.address,
+ false,
+ ),
+ },
+ // no gap in the product contract (MBtcDataFeed), has gap in DataFeed
+ {
+ proxy: mTbillDataFeedAddress,
+ implementation: DataFeed__factory,
+ constructorArgs: [allRoles.tokenRoles.mTBILL.customFeedAdmin],
+ },
+ // has gap in the product contract (MBtcCustomAggregatorFeed) and no gap in CustomAggregatorV3CompatibleFeed
+ {
+ proxy: mTbillCustomFeedAddress,
+ implementation: CustomAggregatorV3CompatibleFeed__factory,
+ constructorArgs: [allRoles.tokenRoles.mTBILL.customFeedAdmin],
+ },
+ ],
+ mBtc: [
+ // inherits mTBILL, has 2 __gap on token level (mToken, mBTC)
+ {
+ proxy: mBtcAddress,
+ implementation: MToken__factory,
+ constructorArgs: mTokenConstructorArgs(allRoles.tokenRoles.mBTC),
+ reinitializerParams: mTokenReinitializerParams(
+ clawbackReceiver.address,
+ false,
+ ),
+ },
+ // no gap in the product contract (MBtcDataFeed), has gap in DataFeed
+ {
+ proxy: mBtcDataFeedAddress,
+ implementation: DataFeed__factory,
+ constructorArgs: [allRoles.tokenRoles.mBTC.customFeedAdmin],
+ },
+ // no gap in the product contract (MBtcCustomAggregatorFeed) and no gap in CustomAggregatorV3CompatibleFeed
+ {
+ proxy: mBtcCustomFeedAddress,
+ implementation: CustomAggregatorV3CompatibleFeed__factory,
+ constructorArgs: [allRoles.tokenRoles.mBTC.customFeedAdmin],
+ },
+ ],
+ mGlobal: [
+ // was mTokenPermissioned; now unified mToken with isPermissioned=true
+ {
+ proxy: mGlobalAddress,
+ implementation: MToken__factory,
+ constructorArgs: mTokenConstructorArgs(allRoles.tokenRoles.mGLOBAL),
+ reinitializerParams: mTokenReinitializerParams(
+ clawbackReceiver.address,
+ true,
+ ),
+ },
+ {
+ // has gap in the product contract (MGlobalDataFeed), has gap in DataFeed
+ proxy: mGlobalDataFeedAddress,
+ implementation: DataFeed__factory,
+ constructorArgs: [allRoles.tokenRoles.mGLOBAL.customFeedAdmin],
+ },
+ // has gap in the product contract (MGlobalCustomFeedGrowth), has gap in CustomAggregatorV3CompatibleFeedGrowth
+ {
+ proxy: mGlobalCustomFeedGrowthAddress,
+ implementation: CustomAggregatorV3CompatibleFeedGrowth__factory,
+ constructorArgs: [allRoles.tokenRoles.mGLOBAL.customFeedAdmin],
+ },
+ ],
+ mRox: [
+ // inherits mToken, has 2 __gap on the token level (mToken, mROX)
+ {
+ proxy: mRoxAddress,
+ implementation: MToken__factory,
+ constructorArgs: mTokenConstructorArgs(allRoles.tokenRoles.mROX),
+ reinitializerParams: mTokenReinitializerParams(
+ clawbackReceiver.address,
+ false,
+ ),
+ },
+ ],
+ };
- await proxyAdmin
- .connect(proxyAdminOwner)
- .upgrade(rvSwapperProxyAddress, newRvSwapperImplementationAddress);
+ await asyncForEach(Object.entries(addressesMap), async ([, values]) => {
+ await asyncForEach(
+ values,
+ async (val) => {
+ await hre.upgrades.upgradeProxy(
+ val.proxy,
+ new val.implementation(proxyAdminOwner),
+ {
+ constructorArgs: val.constructorArgs ?? [],
+ call: val.reinitializerParams
+ ? {
+ fn: val.reinitializerParams.fn,
+ args: val.reinitializerParams.args,
+ }
+ : undefined,
+ },
+ );
+ },
+ true,
+ );
+ });
- const depositVault = await ethers.getContractAt(
- 'HBXautDepositVault',
- dvProxyAddress,
- );
+ const securityCouncilMembers = [
+ signers[0],
+ signers[1],
+ signers[2],
+ signers[3],
+ signers[4],
+ ];
- const redemptionVaultSwapper = await ethers.getContractAt(
- 'HBXautRedemptionVaultWithSwapper',
- rvSwapperProxyAddress,
+ const pauseManager = await deployProxyContract(
+ 'MidasPauseManager',
+ [acAddress, NO_DELAY, 3600],
);
- const xbxautDataFeed = await ethers.getContractAt(
- 'HBXautDataFeed',
- await depositVault.mTokenDataFeed(),
+ const timelockManager = await deployProxyContract(
+ 'MidasTimelockManager',
+ [acAddress, 100, securityCouncilMembers.map((s) => s.address)],
);
- const xbxaut = (await ethers.getContractAt(
- 'hbXAUt',
- await depositVault.mToken(),
- )) as MToken;
+ const timelock =
+ await deployProxyContract(
+ 'MidasAccessControlTimelockController',
+ [timelockManager.address],
+ );
- const xaut0DataFeed = await ethers.getContractAt(
- 'DataFeed',
- (
- await depositVault.tokensConfig(xaut0.address)
- ).dataFeed,
- );
+ await accessControl
+ .connect(acDefaultAdmin)
+ .initializeRelationships(timelockManager.address, pauseManager.address);
- await xaut0
- .connect(xaut0Whale)
- .transfer(redemptionVaultSwapper.address, parseUnits('10', 6));
+ await timelockManager
+ .connect(acDefaultAdmin)
+ .initializeTimelock(timelock.address);
- const requestRedeemerAddress = await redemptionVaultSwapper.requestRedeemer();
+ const mTbill = (await ethers.getContractAt(
+ 'mToken',
+ mTbillAddress,
+ )) as MToken;
+ const mGlobal = (await ethers.getContractAt(
+ 'mToken',
+ mGlobalAddress,
+ )) as MToken;
+ const mTbillDataFeed = (await ethers.getContractAt(
+ 'DataFeed',
+ mTbillDataFeedAddress,
+ )) as DataFeed;
+ const mTbillCustomFeed = (await ethers.getContractAt(
+ 'CustomAggregatorV3CompatibleFeed',
+ mTbillCustomFeedAddress,
+ )) as CustomAggregatorV3CompatibleFeed;
+ const mGlobalDataFeed = (await ethers.getContractAt(
+ 'DataFeed',
+ mGlobalDataFeedAddress,
+ )) as DataFeed;
+ const mGlobalCustomFeedGrowth = (await ethers.getContractAt(
+ 'CustomAggregatorV3CompatibleFeedGrowth',
+ mGlobalCustomFeedGrowthAddress,
+ )) as CustomAggregatorV3CompatibleFeedGrowth;
- const requestRedeemer = await impersonateAndFundAccount(
- requestRedeemerAddress,
+ const mTbillHolders = await Promise.all(
+ [
+ '0x0461bD693caE49bE9d030E5c212e080F9c78B846',
+ '0xc0C4Ab1D389F9540A50D1188226D7384a68cE788',
+ ].map((address) => impersonateAndFundAccount(address)),
);
- await xaut0
- .connect(requestRedeemer)
- .approve(redemptionVaultSwapper.address, parseUnits('1000', 6));
-
- await xaut0
- .connect(xaut0Whale)
- .transfer(requestRedeemerAddress, parseUnits('10', 6));
+ const mGlobalHolders = await Promise.all(
+ [
+ '0x882C825405fBBE45DCc1ad52b639aFbC4592EDb7',
+ '0xaB05c0DB9D26e96A9dcEDCAFCA23341316F6fe6F',
+ ].map((address) => impersonateAndFundAccount(address)),
+ );
return {
proxyAdmin,
proxyAdminOwner,
- dvProxyAddress,
- rvSwapperProxyAddress,
- newDvImplementationAddress,
- newRvSwapperImplementationAddress,
- xaut0Whale,
- xaut0,
- vaultsManager,
- redemptionVaultSwapper,
- depositVault,
- xbxautDataFeed,
- xaut0DataFeed,
- xbxaut,
- tokenManager,
- customRecipient,
+ acDefaultAdmin,
+ accessControl,
+ pauseManager,
+ timelockManager,
+ timelock,
+ securityCouncilMembers,
+ mTbill,
+ mGlobal,
+ mTbillDataFeed,
+ mTbillCustomFeed,
+ mGlobalDataFeed,
+ mGlobalCustomFeedGrowth,
+ mTbillHolders,
+ mGlobalHolders,
+ clawbackReceiver,
};
}
-export type DeployedContracts = Awaited<
- ReturnType
+export type MainnetUpgradeFixture = Awaited<
+ ReturnType
>;
diff --git a/test/integration/fixtures/ustb.fixture.ts b/test/integration/fixtures/ustb.fixture.ts
index 3ee3b96f..cf10ce59 100644
--- a/test/integration/fixtures/ustb.fixture.ts
+++ b/test/integration/fixtures/ustb.fixture.ts
@@ -2,16 +2,22 @@ import { parseUnits } from 'ethers/lib/utils';
import { ethers } from 'hardhat';
import { rpcUrls } from '../../../config';
-import { getAllRoles } from '../../../helpers/roles';
import {
- MidasAccessControlTest,
- MTBILLTest,
RedemptionVaultWithUSTBTest,
DataFeedTest,
AggregatorV3Mock,
DepositVaultWithUSTBTest,
} from '../../../typechain-types';
import { deployProxyContract } from '../../common/deploy.helpers';
+import {
+ getInitializerParamsDvWithUstb,
+ getInitializerParamsRvWithUstb,
+} from '../../common/fixtures';
+import {
+ DV_USTB_INIT_FN,
+ RV_USTB_INIT_FN,
+} from '../../common/vault-initializer.helpers';
+import { setupIntegrationBase } from '../helpers/ac.helpers';
import { impersonateAndFundAccount, resetFork } from '../helpers/fork.helpers';
import { MAINNET_ADDRESSES } from '../helpers/mainnet-addresses';
import { setupUSTBAllowlist } from '../helpers/ustb-helpers';
@@ -22,50 +28,17 @@ const FORK_BLOCK_NUMBER = 22540000;
async function setupUstbBase() {
await resetFork(rpcUrls.main, FORK_BLOCK_NUMBER);
- const [
+ const {
+ accessControl,
+ mTBILL,
owner,
tokensReceiver,
feeReceiver,
requestRedeemer,
vaultAdmin,
testUser,
- ] = await ethers.getSigners();
- const allRoles = getAllRoles();
-
- const accessControl = await deployProxyContract(
- 'MidasAccessControlTest',
- [],
- );
-
- const mTBILL = await deployProxyContract('mTBILLTest', [
- accessControl.address,
- ]);
-
- const rolesArray = [
- allRoles.common.defaultAdmin,
- allRoles.tokenRoles.mTBILL.minter,
- allRoles.tokenRoles.mTBILL.burner,
- allRoles.tokenRoles.mTBILL.pauser,
- allRoles.tokenRoles.mTBILL.redemptionVaultAdmin,
- allRoles.tokenRoles.mTBILL.depositVaultAdmin,
- allRoles.common.greenlistedOperator,
- ];
-
- for (const role of rolesArray) {
- await accessControl.grantRole(role, owner.address);
- }
-
- await accessControl.grantRole(
- allRoles.tokenRoles.mTBILL.redemptionVaultAdmin,
- vaultAdmin.address,
- );
-
- await accessControl.grantRole(
- allRoles.tokenRoles.mTBILL.depositVaultAdmin,
- vaultAdmin.address,
- );
-
- await accessControl.grantRole(allRoles.common.greenlisted, testUser.address);
+ roles: allRoles,
+ } = await setupIntegrationBase();
const usdcAggregator = (await (
await ethers.getContractFactory('AggregatorV3Mock')
@@ -162,32 +135,23 @@ export async function ustbDepositFixture() {
const depositVaultWithUSTB =
await deployProxyContract(
'DepositVaultWithUSTBTest',
- [
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: base.mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: base.feeReceiver.address,
- tokensReceiver: base.tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: ethers.constants.MaxUint256,
- },
- ethers.constants.AddressZero, // sanctions list
- 200,
- parseUnits('0'),
- 0,
- ethers.constants.MaxUint256,
- MAINNET_ADDRESSES.SUPERSTATE_TOKEN_PROXY,
- ],
- 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,uint256,uint256,address)',
+ getInitializerParamsDvWithUstb({
+ accessControl: accessControl.address,
+ mockedSanctionsList: ethers.constants.AddressZero,
+ mTBILL: mTBILL.address,
+ mTokenToUsdDataFeed: base.mTokenToUsdDataFeed.address,
+ tokensReceiver: base.tokensReceiver.address,
+ minAmount: parseUnits('0'),
+ instantFee: 100,
+ variationTolerance: 200,
+ maxApproveRequestId: ethers.constants.MaxUint256,
+ ustbToken: MAINNET_ADDRESSES.SUPERSTATE_TOKEN_PROXY,
+ }),
+ DV_USTB_INIT_FN,
);
// Grant MINTER_ROLE to vault
- await accessControl.grantRole(
+ await accessControl['grantRole(bytes32,address)'](
roles.tokenRoles.mTBILL.minter,
depositVaultWithUSTB.address,
);
@@ -218,36 +182,24 @@ export async function ustbRedemptionFixture() {
const redemptionVaultWithUSTB =
await deployProxyContract(
'RedemptionVaultWithUSTBTest',
- [
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: base.mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: base.feeReceiver.address,
- tokensReceiver: base.tokensReceiver.address,
- },
- {
- instantFee: 100, // 1%
- instantDailyLimit: ethers.constants.MaxUint256,
- },
- ethers.constants.AddressZero, // sanctions list
- 200, // variation tolerance 2%
- parseUnits('100', 18), // min amount
- {
- minFiatRedeemAmount: parseUnits('1000', 18),
- fiatAdditionalFee: 100,
- fiatFlatFee: parseUnits('10', 18),
- },
- base.requestRedeemer.address,
- MAINNET_ADDRESSES.REDEMPTION_IDLE_PROXY,
- ],
- 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,(uint256,uint256,uint256),address,address)',
+ getInitializerParamsRvWithUstb({
+ accessControl: accessControl.address,
+ mockedSanctionsList: ethers.constants.AddressZero,
+ mTBILL: mTBILL.address,
+ mTokenToUsdDataFeed: base.mTokenToUsdDataFeed.address,
+ tokensReceiver: base.tokensReceiver.address,
+ minAmount: parseUnits('100', 18),
+ instantFee: 100,
+ variationTolerance: 200,
+ maxApproveRequestId: ethers.constants.MaxUint256,
+ requestRedeemer: base.requestRedeemer.address,
+ ustbRedemption: MAINNET_ADDRESSES.REDEMPTION_IDLE_PROXY,
+ }),
+ RV_USTB_INIT_FN,
);
// Grant BURN_ROLE to vault
- await accessControl.grantRole(
+ await accessControl['grantRole(bytes32,address)'](
roles.tokenRoles.mTBILL.burner,
redemptionVaultWithUSTB.address,
);
diff --git a/test/integration/helpers/ac.helpers.ts b/test/integration/helpers/ac.helpers.ts
new file mode 100644
index 00000000..d2747306
--- /dev/null
+++ b/test/integration/helpers/ac.helpers.ts
@@ -0,0 +1,164 @@
+import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
+import { constants } from 'ethers';
+import { ethers } from 'hardhat';
+
+import { mTokensMetadata } from '../../../helpers/mtokens-metadata';
+import { getAllRoles } from '../../../helpers/roles';
+import {
+ MidasAccessControlTest,
+ MidasAccessControlTimelockController,
+ MidasPauseManagerTest,
+ MidasTimelockManager,
+ MToken,
+} from '../../../typechain-types';
+import { asyncForEach } from '../../common/common.helpers';
+import { deployProxyContract } from '../../common/deploy.helpers';
+
+type MTokenRoles = {
+ minter: string;
+ burner: string;
+ tokenManager: string;
+ greenlisted: string;
+ minBalanceExempt: string;
+};
+
+export async function deployAccessControlInfra(
+ councilMembers: SignerWithAddress[],
+) {
+ const accessControl = await deployProxyContract(
+ 'MidasAccessControlTest',
+ [0, []],
+ );
+
+ const pauseManager = await deployProxyContract(
+ 'MidasPauseManagerTest',
+ [accessControl.address, 0, 3600],
+ );
+
+ const timelockManager = await deployProxyContract(
+ 'MidasTimelockManager',
+ [
+ accessControl.address,
+ 100,
+ councilMembers.map((member) => member.address),
+ ],
+ );
+
+ const timelock =
+ await deployProxyContract(
+ 'MidasAccessControlTimelockController',
+ [timelockManager.address],
+ );
+
+ await timelockManager.initializeTimelock(timelock.address);
+ await accessControl.initializeRelationships(
+ timelockManager.address,
+ pauseManager.address,
+ );
+
+ return { accessControl, pauseManager, timelockManager, timelock };
+}
+
+export async function deployIntegrationMToken(
+ accessControl: string,
+ clawbackReceiver: string,
+ roles: MTokenRoles,
+ metadata: {
+ name: string;
+ symbol: string;
+ isPermissioned?: boolean;
+ } = mTokensMetadata.mTBILL,
+) {
+ return deployProxyContract(
+ 'mToken',
+ [
+ accessControl,
+ clawbackReceiver,
+ constants.MaxUint256,
+ !!metadata.isPermissioned,
+ false,
+ metadata.name,
+ metadata.symbol,
+ ],
+ 'initialize',
+ [
+ roles.tokenManager,
+ roles.minter,
+ roles.burner,
+ roles.greenlisted,
+ roles.minBalanceExempt,
+ ],
+ );
+}
+
+export async function setupIntegrationBase() {
+ const [
+ owner,
+ tokensReceiver,
+ feeReceiver,
+ requestRedeemer,
+ vaultAdmin,
+ testUser,
+ clawbackReceiver,
+ ...rest
+ ] = await ethers.getSigners();
+ const councilMembers = rest.slice(0, 5);
+ const regularUsers = rest.slice(5);
+
+ const roles = getAllRoles();
+ const mTBILLRoles = roles.tokenRoles.mTBILL;
+
+ const { accessControl, pauseManager, timelockManager, timelock } =
+ await deployAccessControlInfra(councilMembers);
+
+ const mTBILL = await deployIntegrationMToken(
+ accessControl.address,
+ clawbackReceiver.address,
+ mTBILLRoles,
+ );
+
+ const ownerRoles = [
+ roles.common.defaultAdmin,
+ mTBILLRoles.minter,
+ mTBILLRoles.burner,
+ mTBILLRoles.tokenManager,
+ mTBILLRoles.depositVaultAdmin,
+ mTBILLRoles.redemptionVaultAdmin,
+ roles.common.greenlistedOperator,
+ ];
+
+ await asyncForEach(ownerRoles, async (role) => {
+ await accessControl['grantRole(bytes32,address)'](role, owner.address);
+ });
+
+ await accessControl['grantRole(bytes32,address)'](
+ mTBILLRoles.depositVaultAdmin,
+ vaultAdmin.address,
+ );
+ await accessControl['grantRole(bytes32,address)'](
+ mTBILLRoles.redemptionVaultAdmin,
+ vaultAdmin.address,
+ );
+ await accessControl['grantRole(bytes32,address)'](
+ roles.common.greenlisted,
+ testUser.address,
+ );
+
+ return {
+ accessControl,
+ pauseManager,
+ timelockManager,
+ timelock,
+ mTBILL,
+ owner,
+ tokensReceiver,
+ feeReceiver,
+ requestRedeemer,
+ vaultAdmin,
+ testUser,
+ clawbackReceiver,
+ councilMembers,
+ regularUsers,
+ roles,
+ };
+}
diff --git a/test/temp-prefer-const.test.ts b/test/temp-prefer-const.test.ts
deleted file mode 100644
index 442bd6ef..00000000
--- a/test/temp-prefer-const.test.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-describe('test', () => {
- it('should work', () => {
- const owner = 'test';
- const customFeed = 'feed';
- console.log(owner, customFeed);
- });
-});
diff --git a/test/unit/Blacklistable.test.ts b/test/unit/Blacklistable.test.ts
index 732f00b1..d7548614 100644
--- a/test/unit/Blacklistable.test.ts
+++ b/test/unit/Blacklistable.test.ts
@@ -1,8 +1,7 @@
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
import { expect } from 'chai';
-import { BlacklistableTester__factory } from '../../typechain-types';
-import { acErrors, blackList, unBlackList } from '../common/ac.helpers';
+import { acErrors, blackList } from '../common/ac.helpers';
import { defaultDeploy } from '../common/fixtures';
describe('Blacklistable', function () {
@@ -19,30 +18,6 @@ describe('Blacklistable', function () {
).eq(true);
});
- it('onlyInitializing', async () => {
- const { accessControl, owner } = await loadFixture(defaultDeploy);
-
- const blackListable = await new BlacklistableTester__factory(
- owner,
- ).deploy();
-
- await expect(
- blackListable.initializeWithoutInitializer(accessControl.address),
- ).revertedWith('Initializable: contract is not initializing');
- });
-
- it('onlyInitializing unchained', async () => {
- const { accessControl, owner } = await loadFixture(defaultDeploy);
-
- const blackListable = await new BlacklistableTester__factory(
- owner,
- ).deploy();
-
- await expect(
- blackListable.initializeUnchainedWithoutInitializer(),
- ).revertedWith('Initializable: contract is not initializing');
- });
-
describe('modifier onlyNotBlacklisted', () => {
it('should fail: call from blacklisted user', async () => {
const { accessControl, blackListableTester, owner, regularAccounts } =
@@ -56,7 +31,10 @@ describe('Blacklistable', function () {
blackListableTester.onlyNotBlacklistedTester(
regularAccounts[0].address,
),
- ).revertedWith(acErrors.WMAC_HAS_ROLE);
+ ).revertedWithCustomError(
+ blackListableTester,
+ acErrors.WMAC_BLACKLISTED().customErrorName,
+ );
});
it('call from not blacklisted user', async () => {
@@ -70,54 +48,4 @@ describe('Blacklistable', function () {
).not.reverted;
});
});
-
- describe('addToBlackList', () => {
- it('should fail: call from user without BLACKLIST_OPERATOR_ROLE role', async () => {
- const { accessControl, blackListableTester, owner, regularAccounts } =
- await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- regularAccounts[0],
- {
- from: regularAccounts[0],
- revertMessage: `AccessControl: account ${regularAccounts[0].address.toLowerCase()} is missing role ${await accessControl.BLACKLIST_OPERATOR_ROLE()}`,
- },
- );
- });
-
- it('call from user with BLACKLIST_OPERATOR_ROLE role', async () => {
- const { accessControl, blackListableTester, owner, regularAccounts } =
- await loadFixture(defaultDeploy);
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- regularAccounts[0],
- );
- });
- });
-
- describe('removeFromBlackList', () => {
- it('should fail: call from user without BLACKLIST_OPERATOR_ROLE role', async () => {
- const { accessControl, blackListableTester, owner, regularAccounts } =
- await loadFixture(defaultDeploy);
-
- await unBlackList(
- { blacklistable: blackListableTester, accessControl, owner },
- regularAccounts[0],
- {
- from: regularAccounts[0],
- revertMessage: `AccessControl: account ${regularAccounts[0].address.toLowerCase()} is missing role ${await accessControl.BLACKLIST_OPERATOR_ROLE()}`,
- },
- );
- });
-
- it('call from user with BLACKLIST_OPERATOR_ROLE role', async () => {
- const { accessControl, blackListableTester, owner, regularAccounts } =
- await loadFixture(defaultDeploy);
- await unBlackList(
- { blacklistable: blackListableTester, accessControl, owner },
- regularAccounts[0],
- );
- });
- });
});
diff --git a/test/unit/CompositeDataFeed.test.ts b/test/unit/CompositeDataFeed.test.ts
index 49ecfd53..7d619ce1 100644
--- a/test/unit/CompositeDataFeed.test.ts
+++ b/test/unit/CompositeDataFeed.test.ts
@@ -3,8 +3,12 @@ import { expect } from 'chai';
import { parseUnits } from 'ethers/lib/utils';
import { ethers } from 'hardhat';
-import { CompositeDataFeedTest__factory } from '../../typechain-types';
+import {
+ CompositeDataFeed__factory,
+ CompositeDataFeedTest__factory,
+} from '../../typechain-types';
import { acErrors } from '../common/ac.helpers';
+import { validateImplementation } from '../common/common.helpers';
import { setRoundData } from '../common/data-feed.helpers';
import { defaultDeploy } from '../common/fixtures';
@@ -30,10 +34,13 @@ describe('CompositeDataFeed', function () {
expect(await compositeDataFeed.maxExpectedAnswer()).eq(
parseUnits('10000', 18),
);
+ await validateImplementation(CompositeDataFeed__factory);
});
it('initialize', async () => {
- const { compositeDataFeed, owner } = await loadFixture(defaultDeploy);
+ const { compositeDataFeed, owner, accessControl } = await loadFixture(
+ defaultDeploy,
+ );
await expect(
compositeDataFeed.initialize(
@@ -51,27 +58,47 @@ describe('CompositeDataFeed', function () {
await expect(
dataFeedNew.initialize(
- ethers.constants.AddressZero,
+ accessControl.address,
ethers.constants.AddressZero,
ethers.constants.AddressZero,
0,
0,
),
- ).revertedWith('CDF: invalid address');
+ ).revertedWith('CDF: invalid max exp. price');
await expect(
dataFeedNew.initialize(
+ ethers.constants.AddressZero,
dataFeedNew.address,
dataFeedNew.address,
+ 1,
+ 2,
+ ),
+ ).revertedWithCustomError(dataFeedNew, 'InvalidAddress');
+
+ await expect(
+ dataFeedNew.initialize(
+ accessControl.address,
ethers.constants.AddressZero,
- 0,
- 0,
+ ethers.constants.AddressZero,
+ 1,
+ 2,
),
).revertedWith('CDF: invalid address');
await expect(
dataFeedNew.initialize(
+ accessControl.address,
dataFeedNew.address,
+ ethers.constants.AddressZero,
+ 1,
+ 2,
+ ),
+ ).revertedWith('CDF: invalid address');
+
+ await expect(
+ dataFeedNew.initialize(
+ accessControl.address,
dataFeedNew.address,
dataFeedNew.address,
2,
@@ -90,7 +117,10 @@ describe('CompositeDataFeed', function () {
compositeDataFeed
.connect(regularAccounts[0])
.changeNumeratorFeed(ethers.constants.AddressZero),
- ).revertedWith(acErrors.WMAC_HASNT_ROLE);
+ ).revertedWithCustomError(
+ compositeDataFeed,
+ acErrors.WMAC_HASNT_PERMISSION().customErrorName,
+ );
});
it('should fail: pass zero address', async () => {
@@ -108,7 +138,9 @@ describe('CompositeDataFeed', function () {
await expect(
compositeDataFeed.changeNumeratorFeed(mTokenToUsdDataFeed.address),
- ).not.reverted;
+ )
+ .to.emit(compositeDataFeed, 'ChangeNumeratorFeed')
+ .withArgs(mTokenToUsdDataFeed.address);
});
});
@@ -122,7 +154,10 @@ describe('CompositeDataFeed', function () {
compositeDataFeed
.connect(regularAccounts[0])
.changeDenominatorFeed(ethers.constants.AddressZero),
- ).revertedWith(acErrors.WMAC_HASNT_ROLE);
+ ).revertedWithCustomError(
+ compositeDataFeed,
+ acErrors.WMAC_HASNT_PERMISSION().customErrorName,
+ );
});
it('should fail: pass zero address', async () => {
@@ -140,11 +175,13 @@ describe('CompositeDataFeed', function () {
await expect(
compositeDataFeed.changeDenominatorFeed(mTokenToUsdDataFeed.address),
- ).not.reverted;
+ )
+ .to.emit(compositeDataFeed, 'ChangeDenominatorFeed')
+ .withArgs(mTokenToUsdDataFeed.address);
});
});
- describe('setMinExpectedAnswer', () => {
+ describe('setMinMaxExpectedAnswer', () => {
it('should fail: call from address without DEFAULT_ADMIN_ROLE', async () => {
const { compositeDataFeed, regularAccounts } = await loadFixture(
defaultDeploy,
@@ -153,74 +190,64 @@ describe('CompositeDataFeed', function () {
await expect(
compositeDataFeed
.connect(regularAccounts[0])
- .setMinExpectedAnswer(parseUnits('1')),
- ).revertedWith(acErrors.WMAC_HASNT_ROLE);
+ .setMinMaxExpectedAnswer(parseUnits('1000'), parseUnits('1')),
+ ).revertedWithCustomError(
+ compositeDataFeed,
+ acErrors.WMAC_HASNT_PERMISSION().customErrorName,
+ );
});
- it('should fail: pass value more than max expected answer', async () => {
+ it('should fail: when min > max', async () => {
const { compositeDataFeed } = await loadFixture(defaultDeploy);
await expect(
- compositeDataFeed.setMinExpectedAnswer(parseUnits('10001')),
+ compositeDataFeed.setMinMaxExpectedAnswer(
+ parseUnits('1'),
+ parseUnits('10001'),
+ ),
).revertedWith('CDF: invalid exp. prices');
});
- it('pass new value', async () => {
+ it('should fail: when min is zero', async () => {
const { compositeDataFeed } = await loadFixture(defaultDeploy);
- await expect(compositeDataFeed.setMinExpectedAnswer(parseUnits('1'))).not
- .reverted;
- expect(await compositeDataFeed.minExpectedAnswer()).eq(parseUnits('1'));
- });
-
- it('when new value equals max expected answer', async () => {
- const { compositeDataFeed } = await loadFixture(defaultDeploy);
- await compositeDataFeed.setMaxExpectedAnswer(parseUnits('1000'));
-
- await expect(compositeDataFeed.setMinExpectedAnswer(parseUnits('1000')))
- .not.reverted;
- expect(await compositeDataFeed.minExpectedAnswer()).eq(
- parseUnits('1000'),
- );
- });
- });
-
- describe('setMaxExpectedAnswer', () => {
- it('should fail: call from address without DEFAULT_ADMIN_ROLE', async () => {
- const { compositeDataFeed, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
-
await expect(
- compositeDataFeed
- .connect(regularAccounts[0])
- .setMaxExpectedAnswer(parseUnits('1')),
- ).revertedWith(acErrors.WMAC_HASNT_ROLE);
+ compositeDataFeed.setMinMaxExpectedAnswer(parseUnits('1000'), 0),
+ ).revertedWith('CDF: invalid min exp. price');
});
- it('should fail: pass value less than min expected answer', async () => {
+ it('pass new values', async () => {
const { compositeDataFeed } = await loadFixture(defaultDeploy);
await expect(
- compositeDataFeed.setMaxExpectedAnswer(parseUnits('0.099')),
- ).revertedWith('CDF: invalid exp. prices');
- });
-
- it('pass new value', async () => {
- const { compositeDataFeed } = await loadFixture(defaultDeploy);
-
- await expect(compositeDataFeed.setMaxExpectedAnswer(parseUnits('100')))
- .not.reverted;
+ compositeDataFeed.setMinMaxExpectedAnswer(
+ parseUnits('100'),
+ parseUnits('1'),
+ ),
+ )
+ .to.emit(compositeDataFeed, 'SetMinMaxExpectedAnswer')
+ .withArgs(parseUnits('100'), parseUnits('1'));
+ expect(await compositeDataFeed.minExpectedAnswer()).eq(parseUnits('1'));
expect(await compositeDataFeed.maxExpectedAnswer()).eq(parseUnits('100'));
});
- it('when new value equals min expected answer', async () => {
+ it('when min equals max', async () => {
const { compositeDataFeed } = await loadFixture(defaultDeploy);
- await compositeDataFeed.setMinExpectedAnswer(parseUnits('1'));
- await expect(compositeDataFeed.setMaxExpectedAnswer(parseUnits('1'))).not
- .reverted;
- expect(await compositeDataFeed.maxExpectedAnswer()).eq(parseUnits('1'));
+ await expect(
+ compositeDataFeed.setMinMaxExpectedAnswer(
+ parseUnits('1000'),
+ parseUnits('1000'),
+ ),
+ )
+ .to.emit(compositeDataFeed, 'SetMinMaxExpectedAnswer')
+ .withArgs(parseUnits('1000'), parseUnits('1000'));
+ expect(await compositeDataFeed.minExpectedAnswer()).eq(
+ parseUnits('1000'),
+ );
+ expect(await compositeDataFeed.maxExpectedAnswer()).eq(
+ parseUnits('1000'),
+ );
});
});
@@ -268,7 +295,10 @@ describe('CompositeDataFeed', function () {
} = await loadFixture(defaultDeploy);
await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
await setRoundData({ mockedAggregator: mockedAggregatorMBasis }, 1);
- await compositeDataFeed.setMinExpectedAnswer(parseUnits('1'));
+ await compositeDataFeed.setMinMaxExpectedAnswer(
+ parseUnits('10000'),
+ parseUnits('1'),
+ );
expect(await compositeDataFeed.getDataInBase18()).eq(parseUnits('1'));
});
@@ -280,7 +310,10 @@ describe('CompositeDataFeed', function () {
} = await loadFixture(defaultDeploy);
await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1000);
await setRoundData({ mockedAggregator: mockedAggregatorMBasis }, 1);
- await compositeDataFeed.setMaxExpectedAnswer(parseUnits('1000'));
+ await compositeDataFeed.setMinMaxExpectedAnswer(
+ parseUnits('1000'),
+ parseUnits('0.1'),
+ );
expect(await compositeDataFeed.getDataInBase18()).eq(parseUnits('1000'));
});
@@ -292,7 +325,10 @@ describe('CompositeDataFeed', function () {
} = await loadFixture(defaultDeploy);
await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1001);
await setRoundData({ mockedAggregator: mockedAggregatorMBasis }, 1);
- await compositeDataFeed.setMaxExpectedAnswer(parseUnits('1000'));
+ await compositeDataFeed.setMinMaxExpectedAnswer(
+ parseUnits('1000'),
+ parseUnits('0.1'),
+ );
await expect(compositeDataFeed.getDataInBase18()).to.be.revertedWith(
'CDF: feed is unhealthy',
);
@@ -305,13 +341,16 @@ describe('CompositeDataFeed', function () {
} = await loadFixture(defaultDeploy);
await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 0.999);
await setRoundData({ mockedAggregator: mockedAggregatorMBasis }, 1);
- await compositeDataFeed.setMinExpectedAnswer(parseUnits('1'));
+ await compositeDataFeed.setMinMaxExpectedAnswer(
+ parseUnits('10000'),
+ parseUnits('1'),
+ );
await expect(compositeDataFeed.getDataInBase18()).to.be.revertedWith(
'CDF: feed is unhealthy',
);
});
- it('should fail when: num. feed is unhealthy ', async () => {
+ it('should fail: when: num. feed is unhealthy ', async () => {
const { compositeDataFeed, mockedAggregatorMToken } = await loadFixture(
defaultDeploy,
);
@@ -321,7 +360,7 @@ describe('CompositeDataFeed', function () {
);
});
- it('should fail when: denom. feed is unhealthy', async () => {
+ it('should fail: when: denom. feed is unhealthy', async () => {
const { compositeDataFeed, mockedAggregatorMBasis } = await loadFixture(
defaultDeploy,
);
diff --git a/test/unit/CustomFeed.test.ts b/test/unit/CustomFeed.test.ts
index 47c2e4b2..8b87b17f 100644
--- a/test/unit/CustomFeed.test.ts
+++ b/test/unit/CustomFeed.test.ts
@@ -4,23 +4,34 @@ import { expect } from 'chai';
import { parseUnits } from 'ethers/lib/utils';
import { ethers } from 'hardhat';
+import { encodeFnSelector } from '../../helpers/utils';
import {
CustomAggregatorV3CompatibleFeed__factory,
CustomAggregatorV3CompatibleFeedTester__factory,
- MBasisCustomAggregatorFeed__factory,
- MTBillCustomAggregatorFeed__factory,
} from '../../typechain-types';
-import { acErrors } from '../common/ac.helpers';
+import {
+ acErrors,
+ setPermissionRoleTester,
+ setRoleTimelocksTester,
+ setupGrantOperatorRole,
+} from '../common/ac.helpers';
+import { keccak256, validateImplementation } from '../common/common.helpers';
import {
calculatePriceDiviation,
+ setMaxAnswerDeviationTest,
+ setMinMaxAnswerTest,
setRoundData,
setRoundDataSafe,
} from '../common/custom-feed.helpers';
import { defaultDeploy } from '../common/fixtures';
+import {
+ executeTimelockOperationTester,
+ bulkScheduleTimelockOperationTester,
+} from '../common/timelock-manager.helpers';
describe('CustomAggregatorV3CompatibleFeed', function () {
it('deployment', async () => {
- const { customFeed, owner } = await loadFixture(defaultDeploy);
+ const { customFeed, owner, roles } = await loadFixture(defaultDeploy);
expect(await customFeed.maxAnswer()).eq(parseUnits('10000', 8));
expect(await customFeed.minAnswer()).eq(2);
@@ -31,17 +42,11 @@ describe('CustomAggregatorV3CompatibleFeed', function () {
expect(await customFeed.latestRound()).eq(0);
expect(await customFeed.lastAnswer()).eq(0);
expect(await customFeed.lastTimestamp()).eq(0);
- expect(await customFeed.feedAdminRole()).eq(
- await customFeed.CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE(),
+ expect(await customFeed.contractAdminRole()).eq(
+ keccak256('CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE'),
);
- const newFeed = await new CustomAggregatorV3CompatibleFeed__factory(
- owner,
- ).deploy();
-
- expect(await newFeed.feedAdminRole()).eq(
- await newFeed.DEFAULT_ADMIN_ROLE(),
- );
+ await validateImplementation(CustomAggregatorV3CompatibleFeed__factory);
});
it('initialize', async () => {
@@ -70,30 +75,6 @@ describe('CustomAggregatorV3CompatibleFeed', function () {
).revertedWith('CA: !max deviation');
});
- it('MBasisCustomAggregatorFeed', async () => {
- const fixture = await loadFixture(defaultDeploy);
-
- const tester = await new MBasisCustomAggregatorFeed__factory(
- fixture.owner,
- ).deploy();
-
- expect(await tester.feedAdminRole()).eq(
- await tester.M_BASIS_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE(),
- );
- });
-
- it('MTBillCustomAggregatorFeed', async () => {
- const fixture = await loadFixture(defaultDeploy);
-
- const tester = await new MTBillCustomAggregatorFeed__factory(
- fixture.owner,
- ).deploy();
-
- expect(await tester.feedAdminRole()).eq(
- await tester.M_TBILL_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE(),
- );
- });
-
describe('setRoundData', async () => {
it('call from owner', async () => {
const fixture = await loadFixture(defaultDeploy);
@@ -103,7 +84,7 @@ describe('CustomAggregatorV3CompatibleFeed', function () {
const fixture = await loadFixture(defaultDeploy);
await setRoundData(fixture, 10, {
from: fixture.regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
});
});
@@ -137,7 +118,7 @@ describe('CustomAggregatorV3CompatibleFeed', function () {
const fixture = await loadFixture(defaultDeploy);
await setRoundDataSafe(fixture, 10, {
from: fixture.regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
});
});
@@ -188,6 +169,289 @@ describe('CustomAggregatorV3CompatibleFeed', function () {
});
});
+ describe('setMaxAnswerDeviation()', () => {
+ const validMaxAnswerDeviation = parseUnits('0.5', 8);
+ const invalidMaxAnswerDeviation = parseUnits('101', 8);
+ const setMaxAnswerDeviationSelector = encodeFnSelector(
+ 'setMaxAnswerDeviation(uint256)',
+ );
+
+ it('call from owner', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+
+ await setMaxAnswerDeviationTest(fixture, validMaxAnswerDeviation);
+ });
+
+ it('should fail: call from non owner', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+
+ await setMaxAnswerDeviationTest(fixture, validMaxAnswerDeviation, {
+ from: fixture.regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ });
+ });
+
+ it('should fail: when maxAnswerDeviation is greater than 100%', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+
+ await setMaxAnswerDeviationTest(fixture, invalidMaxAnswerDeviation, {
+ revertMessage: 'CA: !max deviation',
+ });
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const { accessControl, customFeed, owner, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ const user = regularAccounts[0];
+ const feedAdminRole = await customFeed.contractAdminRole();
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: feedAdminRole,
+ targetContract: customFeed.address,
+ functionSelector: setMaxAnswerDeviationSelector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ customFeed.address,
+ setMaxAnswerDeviationSelector,
+ [{ account: user.address, enabled: true }],
+ );
+
+ expect(await accessControl.hasRole(feedAdminRole, user.address)).eq(
+ false,
+ );
+
+ await setMaxAnswerDeviationTest(
+ { customFeed, owner },
+ validMaxAnswerDeviation,
+ { from: user },
+ );
+ });
+
+ it('succeeds with scoped permission and feed admin role', async () => {
+ const { accessControl, customFeed, owner, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ const user = regularAccounts[0];
+ const feedAdminRole = await customFeed.contractAdminRole();
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: feedAdminRole,
+ targetContract: customFeed.address,
+ functionSelector: setMaxAnswerDeviationSelector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ customFeed.address,
+ setMaxAnswerDeviationSelector,
+ [{ account: user.address, enabled: true }],
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ feedAdminRole,
+ user.address,
+ );
+
+ await setMaxAnswerDeviationTest(
+ { customFeed, owner },
+ validMaxAnswerDeviation,
+ { from: user },
+ );
+ });
+
+ it('when called through timelock with contract admin role', async () => {
+ const {
+ accessControl,
+ customFeed,
+ owner,
+ regularAccounts,
+ timelock,
+ timelockManager,
+ } = await loadFixture(defaultDeploy);
+
+ const proposer = regularAccounts[0];
+ const feedAdminRole = await customFeed.contractAdminRole();
+
+ await accessControl['grantRole(bytes32,address)'](
+ feedAdminRole,
+ proposer.address,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [feedAdminRole],
+ [3600],
+ );
+
+ const calldata = customFeed.interface.encodeFunctionData(
+ 'setMaxAnswerDeviation',
+ [validMaxAnswerDeviation],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [customFeed.address],
+ [calldata],
+ {},
+ { from: proposer },
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ customFeed.address,
+ calldata,
+ proposer.address,
+ { from: owner },
+ );
+
+ expect(await customFeed.maxAnswerDeviation()).eq(validMaxAnswerDeviation);
+ });
+
+ it('when called through timelock with function admin role', async () => {
+ const {
+ accessControl,
+ customFeed,
+ owner,
+ regularAccounts,
+ timelock,
+ timelockManager,
+ } = await loadFixture(defaultDeploy);
+
+ const proposer = regularAccounts[0];
+ const feedAdminRole = await customFeed.contractAdminRole();
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: feedAdminRole,
+ targetContract: customFeed.address,
+ functionSelector: setMaxAnswerDeviationSelector,
+ grantOperator: owner,
+ });
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: feedAdminRole,
+ targetContract: timelockManager.address,
+ functionSelector: setMaxAnswerDeviationSelector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ feedAdminRole,
+ customFeed.address,
+ setMaxAnswerDeviationSelector,
+ [{ account: proposer.address, enabled: true }],
+ );
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ feedAdminRole,
+ timelockManager.address,
+ setMaxAnswerDeviationSelector,
+ [{ account: proposer.address, enabled: true }],
+ );
+
+ expect(await accessControl.hasRole(feedAdminRole, proposer.address)).eq(
+ false,
+ );
+
+ const feedPermissionKey = await accessControl.permissionRoleKey(
+ feedAdminRole,
+ customFeed.address,
+ setMaxAnswerDeviationSelector,
+ );
+ const timelockPermissionKey = await accessControl.permissionRoleKey(
+ feedAdminRole,
+ timelockManager.address,
+ setMaxAnswerDeviationSelector,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [feedPermissionKey, timelockPermissionKey],
+ [3600, 3600],
+ );
+
+ const calldata = customFeed.interface.encodeFunctionData(
+ 'setMaxAnswerDeviation',
+ [validMaxAnswerDeviation],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [customFeed.address],
+ [calldata],
+ {},
+ { from: proposer },
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ customFeed.address,
+ calldata,
+ proposer.address,
+ { from: owner },
+ );
+
+ expect(await customFeed.maxAnswerDeviation()).eq(validMaxAnswerDeviation);
+ });
+ });
+
+ describe('setMinMaxAnswer()', () => {
+ it('call from owner', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+
+ await setMinMaxAnswerTest(
+ fixture,
+ parseUnits('1', 8),
+ parseUnits('5000', 8),
+ );
+ });
+
+ it('should fail: call from non owner', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+
+ await setMinMaxAnswerTest(
+ fixture,
+ parseUnits('1', 8),
+ parseUnits('5000', 8),
+ {
+ from: fixture.regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ },
+ );
+ });
+
+ it('should fail: when min >= max', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+
+ await setMinMaxAnswerTest(
+ fixture,
+ parseUnits('100', 8),
+ parseUnits('100', 8),
+ { revertMessage: 'CA: !min/max' },
+ );
+ });
+ });
+
describe('_getDeviation', async () => {
it('when new price is 0', async () => {
const fixture = await loadFixture(defaultDeploy);
diff --git a/test/unit/CustomFeedGrowth.test.ts b/test/unit/CustomFeedGrowth.test.ts
index ebf70b35..03fa8299 100644
--- a/test/unit/CustomFeedGrowth.test.ts
+++ b/test/unit/CustomFeedGrowth.test.ts
@@ -4,20 +4,33 @@ import { expect } from 'chai';
import { parseUnits } from 'ethers/lib/utils';
import { ethers } from 'hardhat';
+import { encodeFnSelector } from '../../helpers/utils';
import {
CustomAggregatorV3CompatibleFeedGrowth__factory,
CustomAggregatorV3CompatibleFeedGrowthTester__factory,
} from '../../typechain-types';
-import { acErrors } from '../common/ac.helpers';
+import {
+ acErrors,
+ setPermissionRoleTester,
+ setRoleTimelocksTester,
+ setupGrantOperatorRole,
+} from '../common/ac.helpers';
+import { keccak256, validateImplementation } from '../common/common.helpers';
import {
setMaxGrowthApr,
+ setMaxAnswerDeviationTest,
setMinGrowthApr,
+ setMinMaxAnswerTest,
setOnlyUp,
setRoundDataGrowth,
setRoundDataSafeGrowth,
} from '../common/custom-feed-growth.helpers';
import { calculatePriceDiviation } from '../common/custom-feed.helpers';
import { defaultDeploy } from '../common/fixtures';
+import {
+ executeTimelockOperationTester,
+ bulkScheduleTimelockOperationTester,
+} from '../common/timelock-manager.helpers';
describe('CustomAggregatorV3CompatibleFeedGrowth', function () {
it('deployment', async () => {
@@ -34,16 +47,12 @@ describe('CustomAggregatorV3CompatibleFeedGrowth', function () {
expect(await customFeedGrowth.latestRound()).eq(0);
expect(await customFeedGrowth.lastAnswer()).eq(0);
expect(await customFeedGrowth.lastTimestamp()).eq(0);
- expect(await customFeedGrowth.feedAdminRole()).eq(
- await customFeedGrowth.CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE(),
+ expect(await customFeedGrowth.contractAdminRole()).eq(
+ keccak256('CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE'),
);
- const newFeed = await new CustomAggregatorV3CompatibleFeedGrowth__factory(
- owner,
- ).deploy();
-
- expect(await newFeed.feedAdminRole()).eq(
- await newFeed.DEFAULT_ADMIN_ROLE(),
+ await validateImplementation(
+ CustomAggregatorV3CompatibleFeedGrowth__factory,
);
});
@@ -183,7 +192,7 @@ describe('CustomAggregatorV3CompatibleFeedGrowth', function () {
const fixture = await loadFixture(defaultDeploy);
await setRoundDataGrowth(fixture, 10, -100, 0, {
from: fixture.regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
});
});
@@ -342,13 +351,11 @@ describe('CustomAggregatorV3CompatibleFeedGrowth', function () {
await setMinGrowthApr(fixture, -10);
await setOnlyUp(fixture, true);
- await fixture.customFeedGrowth.setMaxAnswerDeviation(
- parseUnits('1000', 8),
- );
+ await setMaxAnswerDeviationTest(fixture, parseUnits('100', 8));
await setRoundDataSafeGrowth({ ...fixture }, 10, -3600, 0);
- await setRoundDataSafeGrowth({ ...fixture }, 100, -1600, -1, {
+ await setRoundDataSafeGrowth({ ...fixture }, 11, -1600, -1, {
revertMessage: 'CAG: negative apr',
});
});
@@ -357,7 +364,7 @@ describe('CustomAggregatorV3CompatibleFeedGrowth', function () {
const fixture = await loadFixture(defaultDeploy);
await setRoundDataSafeGrowth(fixture, 10, -100, 0, {
from: fixture.regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
});
});
@@ -445,6 +452,43 @@ describe('CustomAggregatorV3CompatibleFeedGrowth', function () {
});
});
+ describe('setMinMaxAnswer()', () => {
+ it('call from owner', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+
+ await setMinMaxAnswerTest(
+ fixture,
+ parseUnits('1', 8),
+ parseUnits('5000', 8),
+ );
+ });
+
+ it('should fail: call from non owner', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+
+ await setMinMaxAnswerTest(
+ fixture,
+ parseUnits('1', 8),
+ parseUnits('5000', 8),
+ {
+ from: fixture.regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ },
+ );
+ });
+
+ it('should fail: when min >= max', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+
+ await setMinMaxAnswerTest(
+ fixture,
+ parseUnits('100', 8),
+ parseUnits('100', 8),
+ { revertMessage: 'CA: !min/max' },
+ );
+ });
+ });
+
describe('setMinGrowthApr', () => {
it('call from owner', async () => {
const fixture = await loadFixture(defaultDeploy);
@@ -465,7 +509,7 @@ describe('CustomAggregatorV3CompatibleFeedGrowth', function () {
const fixture = await loadFixture(defaultDeploy);
await setMinGrowthApr(fixture, 10, {
from: fixture.regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
});
});
@@ -498,7 +542,7 @@ describe('CustomAggregatorV3CompatibleFeedGrowth', function () {
const fixture = await loadFixture(defaultDeploy);
await setMaxGrowthApr(fixture, 10, {
from: fixture.regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
});
});
@@ -542,8 +586,236 @@ describe('CustomAggregatorV3CompatibleFeedGrowth', function () {
const fixture = await loadFixture(defaultDeploy);
await setOnlyUp(fixture, true, {
from: fixture.regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ });
+ });
+ });
+
+ describe('setMaxAnswerDeviation()', () => {
+ const validMaxAnswerDeviation = parseUnits('0.5', 8);
+ const invalidMaxAnswerDeviation = parseUnits('101', 8);
+ const setMaxAnswerDeviationSelector = encodeFnSelector(
+ 'setMaxAnswerDeviation(uint256)',
+ );
+
+ it('call from owner', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+
+ await setMaxAnswerDeviationTest(fixture, validMaxAnswerDeviation);
+ });
+
+ it('should fail: call from non owner', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+
+ await setMaxAnswerDeviationTest(fixture, validMaxAnswerDeviation, {
+ from: fixture.regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ });
+ });
+
+ it('should fail: when maxAnswerDeviation is greater than 100%', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+
+ await setMaxAnswerDeviationTest(fixture, invalidMaxAnswerDeviation, {
+ revertMessage: 'CAG: !max deviation',
+ });
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const { accessControl, customFeedGrowth, owner, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ const user = regularAccounts[0];
+ const feedAdminRole = await customFeedGrowth.contractAdminRole();
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: feedAdminRole,
+ targetContract: customFeedGrowth.address,
+ functionSelector: setMaxAnswerDeviationSelector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ customFeedGrowth.address,
+ setMaxAnswerDeviationSelector,
+ [{ account: user.address, enabled: true }],
+ );
+
+ expect(await accessControl.hasRole(feedAdminRole, user.address)).eq(
+ false,
+ );
+
+ await setMaxAnswerDeviationTest(
+ { customFeedGrowth, owner },
+ validMaxAnswerDeviation,
+ { from: user },
+ );
+ });
+
+ it('succeeds with scoped permission and feed admin role', async () => {
+ const { accessControl, customFeedGrowth, owner, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ const user = regularAccounts[0];
+ const feedAdminRole = await customFeedGrowth.contractAdminRole();
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: feedAdminRole,
+ targetContract: customFeedGrowth.address,
+ functionSelector: setMaxAnswerDeviationSelector,
+ grantOperator: owner,
});
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ customFeedGrowth.address,
+ setMaxAnswerDeviationSelector,
+ [{ account: user.address, enabled: true }],
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ feedAdminRole,
+ user.address,
+ );
+
+ await setMaxAnswerDeviationTest(
+ { customFeedGrowth, owner },
+ validMaxAnswerDeviation,
+ { from: user },
+ );
+ });
+
+ it('when called through timelock with contract admin role', async () => {
+ const {
+ accessControl,
+ customFeedGrowth,
+ owner,
+ regularAccounts,
+ timelock,
+ timelockManager,
+ } = await loadFixture(defaultDeploy);
+
+ const proposer = regularAccounts[0];
+ const feedAdminRole = await customFeedGrowth.contractAdminRole();
+
+ await accessControl['grantRole(bytes32,address)'](
+ feedAdminRole,
+ proposer.address,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [feedAdminRole],
+ [3600],
+ );
+
+ const calldata = customFeedGrowth.interface.encodeFunctionData(
+ 'setMaxAnswerDeviation',
+ [validMaxAnswerDeviation],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [customFeedGrowth.address],
+ [calldata],
+ {},
+ { from: proposer },
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ customFeedGrowth.address,
+ calldata,
+ proposer.address,
+ { from: owner },
+ );
+
+ expect(await customFeedGrowth.maxAnswerDeviation()).eq(
+ validMaxAnswerDeviation,
+ );
+ });
+
+ it('when called through timelock with function admin role', async () => {
+ const {
+ accessControl,
+ customFeedGrowth,
+ owner,
+ regularAccounts,
+ timelock,
+ timelockManager,
+ } = await loadFixture(defaultDeploy);
+
+ const proposer = regularAccounts[0];
+ const feedAdminRole = await customFeedGrowth.contractAdminRole();
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: feedAdminRole,
+ targetContract: customFeedGrowth.address,
+ functionSelector: setMaxAnswerDeviationSelector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ customFeedGrowth.address,
+ setMaxAnswerDeviationSelector,
+ [{ account: proposer.address, enabled: true }],
+ );
+
+ expect(await accessControl.hasRole(feedAdminRole, proposer.address)).eq(
+ false,
+ );
+
+ const feedPermissionKey = await accessControl.permissionRoleKey(
+ feedAdminRole,
+ customFeedGrowth.address,
+ setMaxAnswerDeviationSelector,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [feedPermissionKey],
+ [3600],
+ );
+
+ const calldata = customFeedGrowth.interface.encodeFunctionData(
+ 'setMaxAnswerDeviation',
+ [validMaxAnswerDeviation],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [customFeedGrowth.address],
+ [calldata],
+ {},
+ { from: proposer },
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ customFeedGrowth.address,
+ calldata,
+ proposer.address,
+ { from: owner },
+ );
+
+ expect(await customFeedGrowth.maxAnswerDeviation()).eq(
+ validMaxAnswerDeviation,
+ );
});
});
diff --git a/test/unit/DataFeed.test.ts b/test/unit/DataFeed.test.ts
index 4516947d..37826f70 100644
--- a/test/unit/DataFeed.test.ts
+++ b/test/unit/DataFeed.test.ts
@@ -4,14 +4,28 @@ import { expect } from 'chai';
import { parseUnits } from 'ethers/lib/utils';
import { ethers } from 'hardhat';
-import { DataFeedTest__factory } from '../../typechain-types';
-import { acErrors } from '../common/ac.helpers';
+import { encodeFnSelector } from '../../helpers/utils';
+import {
+ DataFeed__factory,
+ DataFeedTest__factory,
+} from '../../typechain-types';
+import {
+ acErrors,
+ setPermissionRoleTester,
+ setRoleTimelocksTester,
+ setupGrantOperatorRole,
+} from '../common/ac.helpers';
+import { validateImplementation } from '../common/common.helpers';
import {
setMinGrowthApr,
setRoundDataGrowth,
} from '../common/custom-feed-growth.helpers';
-import { setRoundData } from '../common/data-feed.helpers';
+import { setHealthyDiffTest, setRoundData } from '../common/data-feed.helpers';
import { defaultDeploy } from '../common/fixtures';
+import {
+ bulkScheduleTimelockOperationTester,
+ executeTimelockOperationTester,
+} from '../common/timelock-manager.helpers';
describe('DataFeed', function () {
it('deployment', async () => {
@@ -26,10 +40,11 @@ describe('DataFeed', function () {
expect(await dataFeed.maxExpectedAnswer()).eq(
parseUnits('10000', mockedAggregatorDecimals),
);
+ await validateImplementation(DataFeed__factory);
});
it('initialize', async () => {
- const { dataFeed, owner } = await loadFixture(defaultDeploy);
+ const { dataFeed, owner, accessControl } = await loadFixture(defaultDeploy);
await expect(
dataFeed.initialize(
@@ -45,28 +60,72 @@ describe('DataFeed', function () {
await expect(
dataFeedNew.initialize(
+ accessControl.address,
ethers.constants.AddressZero,
- ethers.constants.AddressZero,
- 0,
+ 1,
0,
0,
),
+ ).revertedWith('DF: invalid max exp. price');
+
+ await expect(
+ dataFeedNew.initialize(
+ ethers.constants.AddressZero,
+ dataFeedNew.address,
+ 1,
+ 1,
+ 2,
+ ),
+ ).revertedWithCustomError(dataFeedNew, 'InvalidAddress');
+
+ await expect(
+ dataFeedNew.initialize(
+ accessControl.address,
+ ethers.constants.AddressZero,
+ 1,
+ 1,
+ 2,
+ ),
).revertedWith('DF: invalid address');
await expect(
- dataFeedNew.initialize(dataFeedNew.address, dataFeedNew.address, 0, 0, 0),
+ dataFeedNew.initialize(
+ accessControl.address,
+ dataFeedNew.address,
+ 0,
+ 1,
+ 2,
+ ),
).revertedWith('DF: invalid diff');
await expect(
- dataFeedNew.initialize(dataFeedNew.address, dataFeedNew.address, 1, 0, 0),
+ dataFeedNew.initialize(
+ accessControl.address,
+ dataFeedNew.address,
+ 1,
+ 0,
+ 2,
+ ),
).revertedWith('DF: invalid min exp. price');
await expect(
- dataFeedNew.initialize(dataFeedNew.address, dataFeedNew.address, 1, 1, 0),
+ dataFeedNew.initialize(
+ accessControl.address,
+ dataFeedNew.address,
+ 1,
+ 1,
+ 0,
+ ),
).revertedWith('DF: invalid max exp. price');
await expect(
- dataFeedNew.initialize(dataFeedNew.address, dataFeedNew.address, 1, 2, 1),
+ dataFeedNew.initialize(
+ accessControl.address,
+ dataFeedNew.address,
+ 1,
+ 2,
+ 1,
+ ),
).revertedWith('DF: invalid exp. prices');
});
@@ -78,7 +137,10 @@ describe('DataFeed', function () {
dataFeed
.connect(regularAccounts[0])
.changeAggregator(ethers.constants.AddressZero),
- ).revertedWith(acErrors.WMAC_HASNT_ROLE);
+ ).revertedWithCustomError(
+ dataFeed,
+ acErrors.WMAC_HASNT_PERMISSION().customErrorName,
+ );
});
it('should fail: pass zero address', async () => {
@@ -92,8 +154,247 @@ describe('DataFeed', function () {
it('pass new aggregator address', async () => {
const { dataFeed, mockedAggregator } = await loadFixture(defaultDeploy);
- await expect(dataFeed.changeAggregator(mockedAggregator.address)).not
- .reverted;
+ await expect(dataFeed.changeAggregator(mockedAggregator.address))
+ .to.emit(dataFeed, 'ChangeAggregator')
+ .withArgs(mockedAggregator.address);
+ });
+ });
+
+ describe('setHealthyDiff()', () => {
+ const validHealthyDiff = 2 * 24 * 3600;
+ const invalidHealthyDiff = 0;
+ const setHealthyDiffSelector = encodeFnSelector('setHealthyDiff(uint256)');
+
+ it('call from owner', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+
+ await setHealthyDiffTest(fixture, validHealthyDiff);
+ });
+
+ it('should fail: call from non owner', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+
+ await setHealthyDiffTest(fixture, validHealthyDiff, {
+ from: fixture.regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ });
+ });
+
+ it('should fail: when healthy diff is 0', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+
+ await setHealthyDiffTest(fixture, invalidHealthyDiff, {
+ revertMessage: 'DF: invalid diff',
+ });
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const { accessControl, dataFeed, owner, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ const user = regularAccounts[0];
+ const feedAdminRole = await dataFeed.contractAdminRole();
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: feedAdminRole,
+ targetContract: dataFeed.address,
+ functionSelector: setHealthyDiffSelector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ dataFeed.address,
+ setHealthyDiffSelector,
+ [{ account: user.address, enabled: true }],
+ );
+
+ expect(await accessControl.hasRole(feedAdminRole, user.address)).eq(
+ false,
+ );
+
+ await setHealthyDiffTest({ dataFeed, owner }, validHealthyDiff, {
+ from: user,
+ });
+ });
+
+ it('succeeds with scoped permission and feed admin role', async () => {
+ const { accessControl, dataFeed, owner, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ const user = regularAccounts[0];
+ const feedAdminRole = await dataFeed.contractAdminRole();
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: feedAdminRole,
+ targetContract: dataFeed.address,
+ functionSelector: setHealthyDiffSelector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ dataFeed.address,
+ setHealthyDiffSelector,
+ [{ account: user.address, enabled: true }],
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ feedAdminRole,
+ user.address,
+ );
+
+ await setHealthyDiffTest({ dataFeed, owner }, validHealthyDiff, {
+ from: user,
+ });
+ });
+
+ it('when called through timelock with contract admin role', async () => {
+ const {
+ accessControl,
+ dataFeed,
+ owner,
+ regularAccounts,
+ timelock,
+ timelockManager,
+ } = await loadFixture(defaultDeploy);
+
+ const proposer = regularAccounts[0];
+ const feedAdminRole = await dataFeed.contractAdminRole();
+
+ await accessControl['grantRole(bytes32,address)'](
+ feedAdminRole,
+ proposer.address,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [feedAdminRole],
+ [3600],
+ );
+
+ const calldata = dataFeed.interface.encodeFunctionData('setHealthyDiff', [
+ validHealthyDiff,
+ ]);
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [dataFeed.address],
+ [calldata],
+ {},
+ { from: proposer },
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ dataFeed.address,
+ calldata,
+ proposer.address,
+ { from: owner },
+ );
+
+ expect(await dataFeed.healthyDiff()).eq(validHealthyDiff);
+ });
+
+ it('when called through timelock with function admin role', async () => {
+ const {
+ accessControl,
+ dataFeed,
+ owner,
+ regularAccounts,
+ timelock,
+ timelockManager,
+ } = await loadFixture(defaultDeploy);
+
+ const proposer = regularAccounts[0];
+ const feedAdminRole = await dataFeed.contractAdminRole();
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: feedAdminRole,
+ targetContract: dataFeed.address,
+ functionSelector: setHealthyDiffSelector,
+ grantOperator: owner,
+ });
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: feedAdminRole,
+ targetContract: timelockManager.address,
+ functionSelector: setHealthyDiffSelector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ feedAdminRole,
+ dataFeed.address,
+ setHealthyDiffSelector,
+ [{ account: proposer.address, enabled: true }],
+ );
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ feedAdminRole,
+ timelockManager.address,
+ setHealthyDiffSelector,
+ [{ account: proposer.address, enabled: true }],
+ );
+
+ expect(await accessControl.hasRole(feedAdminRole, proposer.address)).eq(
+ false,
+ );
+
+ const feedPermissionKey = await accessControl.permissionRoleKey(
+ feedAdminRole,
+ dataFeed.address,
+ setHealthyDiffSelector,
+ );
+ const timelockPermissionKey = await accessControl.permissionRoleKey(
+ feedAdminRole,
+ timelockManager.address,
+ setHealthyDiffSelector,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [feedPermissionKey, timelockPermissionKey],
+ [3600, 3600],
+ );
+
+ const calldata = dataFeed.interface.encodeFunctionData('setHealthyDiff', [
+ validHealthyDiff,
+ ]);
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [dataFeed.address],
+ [calldata],
+ {},
+ { from: proposer },
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ dataFeed.address,
+ calldata,
+ proposer.address,
+ { from: owner },
+ );
+
+ expect(await dataFeed.healthyDiff()).eq(validHealthyDiff);
});
});
@@ -146,14 +447,15 @@ describe('DataFeed', function () {
});
describe('DataFeed Deprecated', function () {
- it('should fail when: feed is deprecated', async () => {
- const { dataFeedDeprecated } = await loadFixture(defaultDeploy);
+ it('should fail: when: feed is deprecated', async () => {
+ const { deployDeprecatedFeed } = await loadFixture(defaultDeploy);
+ const { dataFeedDeprecated } = await deployDeprecatedFeed();
await expect(dataFeedDeprecated.getDataInBase18()).to.be.reverted;
});
});
describe('DataFeed Deprecated with growth', function () {
- it('should fail when: feed is deprecated (price < 0)', async () => {
+ it('should fail: when: feed is deprecated (price < 0)', async () => {
const { dataFeedGrowth, ...fixture } = await loadFixture(defaultDeploy);
await setMinGrowthApr(fixture, -1000000);
await setRoundDataGrowth(fixture, 0.001, -1000000, -1000000);
@@ -164,11 +466,12 @@ describe('DataFeed Deprecated with growth', function () {
});
describe('DataFeed Unhealthy', function () {
- it('should fail when: feed is unhealthy (by time)', async () => {
- const { dataFeedUnhealthy } = await loadFixture(defaultDeploy);
+ it('should fail: when: feed is unhealthy (by time)', async () => {
+ const { deployUnhealthyFeed } = await loadFixture(defaultDeploy);
+ const { dataFeedUnhealthy } = await deployUnhealthyFeed();
await expect(dataFeedUnhealthy.getDataInBase18()).to.be.reverted;
});
- it('should fail when: feed is unhealthy (by min answer)', async () => {
+ it('should fail: when: feed is unhealthy (by min answer)', async () => {
const { dataFeed, mockedAggregator } = await loadFixture(defaultDeploy);
await setRoundData({ mockedAggregator }, 0.1);
await expect(dataFeed.getDataInBase18()).to.be.not.reverted;
@@ -176,7 +479,7 @@ describe('DataFeed Unhealthy', function () {
await expect(dataFeed.getDataInBase18()).to.be.reverted;
});
- it('should fail when: feed is unhealthy (by max answer)', async () => {
+ it('should fail: when: feed is unhealthy (by max answer)', async () => {
const { dataFeed, mockedAggregator } = await loadFixture(defaultDeploy);
await setRoundData({ mockedAggregator }, 10000);
await expect(dataFeed.getDataInBase18()).to.be.not.reverted;
@@ -186,7 +489,7 @@ describe('DataFeed Unhealthy', function () {
});
describe('DataFeed Unhealthy with growth', function () {
- it('should fail when: feed is unhealthy (by time)', async () => {
+ it('should fail: when: feed is unhealthy (by time)', async () => {
const { dataFeedGrowth, ...fixture } = await loadFixture(defaultDeploy);
await setRoundDataGrowth(fixture, 0.1, -10, 0);
@@ -195,7 +498,7 @@ describe('DataFeed Unhealthy with growth', function () {
'DF: feed is unhealthy',
);
});
- it('should fail when: feed is unhealthy (by min answer)', async () => {
+ it('should fail: when: feed is unhealthy (by min answer)', async () => {
const { dataFeedGrowth, ...fixture } = await loadFixture(defaultDeploy);
await setRoundDataGrowth(fixture, 0.1, -100, 0);
await expect(dataFeedGrowth.getDataInBase18()).to.be.not.reverted;
@@ -205,11 +508,13 @@ describe('DataFeed Unhealthy with growth', function () {
);
});
- it('should fail when: feed is unhealthy (by max answer)', async () => {
+ it('should fail: when: feed is unhealthy (by max answer)', async () => {
const { dataFeedGrowth, ...fixture } = await loadFixture(defaultDeploy);
- await dataFeedGrowth.setMinExpectedAnswer(parseUnits('10', 8));
- await dataFeedGrowth.setMaxExpectedAnswer(parseUnits('100', 8));
+ await dataFeedGrowth.setMinMaxExpectedAnswer(
+ parseUnits('100', 8),
+ parseUnits('10', 8),
+ );
await setRoundDataGrowth(fixture, 100, -100, 0);
await expect(dataFeedGrowth.getDataInBase18()).to.be.not.reverted;
diff --git a/test/unit/DepositVault.test.ts b/test/unit/DepositVault.test.ts
index aa25d81e..1529cbc7 100644
--- a/test/unit/DepositVault.test.ts
+++ b/test/unit/DepositVault.test.ts
@@ -1,6247 +1,148 @@
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
-import { expect } from 'chai';
-import { constants } from 'ethers';
-import { parseUnits } from 'ethers/lib/utils';
-import { ethers } from 'hardhat';
+import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
-import { encodeFnSelector } from '../../helpers/utils';
import {
+ baseInitParamsDv,
+ depositVaultSuits,
+} from './suits/deposit-vault.suits';
+
+import {
+ DepositVault__factory,
DepositVaultTest__factory,
- EUsdDepositVault__factory,
- ManageableVaultTester__factory,
- MBasisDepositVault__factory,
} from '../../typechain-types';
-import { acErrors, blackList, greenList } from '../common/ac.helpers';
import {
approveBase18,
mintToken,
- pauseVault,
- pauseVaultFn,
+ validateImplementation,
} from '../common/common.helpers';
-import {
- setMinGrowthApr,
- setRoundDataGrowth,
-} from '../common/custom-feed-growth.helpers';
import { setRoundData } from '../common/data-feed.helpers';
-import {
- approveRequestTest,
- depositInstantTest,
- depositRequestTest,
- rejectRequestTest,
- safeApproveRequestTest,
- safeBulkApproveRequestTest,
- setMaxSupplyCapTest,
-} from '../common/deposit-vault.helpers';
+import { depositInstantTest } from '../common/deposit-vault.helpers';
import { defaultDeploy, mTokenPermissionedFixture } from '../common/fixtures';
-import { greenListEnable } from '../common/greenlist.helpers';
-import {
- addPaymentTokenTest,
- addWaivedFeeAccountTest,
- changeTokenAllowanceTest,
- removePaymentTokenTest,
- removeWaivedFeeAccountTest,
- setInstantFeeTest,
- setInstantDailyLimitTest,
- setMinAmountTest,
- setMinAmountToDepositTest,
- setVariabilityToleranceTest,
- withdrawTest,
- changeTokenFeeTest,
-} from '../common/manageable-vault.helpers';
-import { sanctionUser } from '../common/with-sanctions-list.helpers';
-
-describe('DepositVault', function () {
- it('deployment', async () => {
- const {
- depositVault,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- roles,
- } = await loadFixture(defaultDeploy);
-
- expect(await depositVault.mToken()).eq(mTBILL.address);
-
- expect(await depositVault.paused()).eq(false);
-
- expect(await depositVault.tokensReceiver()).eq(tokensReceiver.address);
- expect(await depositVault.feeReceiver()).eq(feeReceiver.address);
-
- expect(await depositVault.ONE_HUNDRED_PERCENT()).eq('10000');
-
- expect(await depositVault.minMTokenAmountForFirstDeposit()).eq('0');
- expect(await depositVault.minAmount()).eq(parseUnits('100'));
-
- expect(await depositVault.maxSupplyCap()).eq(constants.MaxUint256);
-
- expect(await depositVault.instantFee()).eq('100');
-
- expect(await depositVault.instantDailyLimit()).eq(parseUnits('100000'));
-
- expect(await depositVault.mTokenDataFeed()).eq(mTokenToUsdDataFeed.address);
- expect(await depositVault.variationTolerance()).eq(1);
-
- expect(await depositVault.vaultRole()).eq(
- roles.tokenRoles.mTBILL.depositVaultAdmin,
- );
-
- expect(await depositVault.MANUAL_FULLFILMENT_TOKEN()).eq(
- ethers.constants.AddressZero,
- );
- });
-
- it('failing deployment', async () => {
- const {
- accessControl,
- mTBILL,
- owner,
- mTokenToUsdDataFeed,
- feeReceiver,
- tokensReceiver,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
- const depositVault = await new DepositVaultTest__factory(owner).deploy();
-
- await expect(
- depositVault.initialize(
- ethers.constants.AddressZero,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- parseUnits('100'),
- constants.MaxUint256,
- ),
- ).to.be.reverted;
- await expect(
- depositVault.initialize(
- accessControl.address,
- {
- mToken: ethers.constants.AddressZero,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- parseUnits('100'),
- constants.MaxUint256,
- ),
- ).to.be.reverted;
- await expect(
- depositVault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: ethers.constants.AddressZero,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- parseUnits('100'),
- constants.MaxUint256,
- ),
- ).to.be.reverted;
- await expect(
- depositVault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: ethers.constants.AddressZero,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- parseUnits('100'),
- constants.MaxUint256,
- ),
- ).to.be.reverted;
- await expect(
- depositVault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: ethers.constants.AddressZero,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- parseUnits('100'),
- constants.MaxUint256,
- ),
- ).to.be.reverted;
- await expect(
- depositVault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100001,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- parseUnits('100'),
- constants.MaxUint256,
- ),
- ).to.be.reverted;
- });
-
- it('MBasisDepositVault', async () => {
- const fixture = await loadFixture(defaultDeploy);
-
- const tester = await new MBasisDepositVault__factory(
- fixture.owner,
- ).deploy();
-
- expect(await tester.vaultRole()).eq(
- await tester.M_BASIS_DEPOSIT_VAULT_ADMIN_ROLE(),
- );
- });
-
- it('EUsdDepositVault', async () => {
- const fixture = await loadFixture(defaultDeploy);
-
- const tester = await new EUsdDepositVault__factory(fixture.owner).deploy();
-
- expect(await tester.vaultRole()).eq(
- await tester.E_USD_DEPOSIT_VAULT_ADMIN_ROLE(),
- );
- });
-
- describe('initialization', () => {
- it('should fail: cal; initialize() when already initialized', async () => {
- const { depositVault } = await loadFixture(defaultDeploy);
-
- await expect(
- depositVault.initialize(
- constants.AddressZero,
- {
- mToken: constants.AddressZero,
- mTokenDataFeed: constants.AddressZero,
- },
- {
- feeReceiver: constants.AddressZero,
- tokensReceiver: constants.AddressZero,
- },
- {
- instantFee: 0,
- instantDailyLimit: 0,
- },
- constants.AddressZero,
- 0,
- 0,
- 0,
- 0,
- ),
- ).revertedWith('Initializable: contract is already initialized');
- });
-
- it('should fail: call with initializing == false', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initializeWithoutInitializer(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- ),
- ).revertedWith('Initializable: contract is not initializing');
- });
-
- it('should fail: when _tokensReceiver == address(this)', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- feeReceiver,
- mTokenToUsdDataFeed,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: vault.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- ),
- ).revertedWith('invalid address');
- });
- it('should fail: when _feeReceiver == address(this)', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- mTokenToUsdDataFeed,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: vault.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- ),
- ).revertedWith('invalid address');
- });
- it('should fail: when limit = 0', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: 0,
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- ),
- ).revertedWith('zero limit');
- });
- it('should fail: when mToken dataFeed address zero', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: constants.AddressZero,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- ),
- ).revertedWith('zero address');
- });
- it('should fail: when variationTolarance zero', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mockedSanctionsList,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 0,
- parseUnits('100'),
- ),
- ).revertedWith('fee == 0');
- });
- });
-
- describe('setMinMTokenAmountForFirstDeposit()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { owner, depositVault, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
-
- await setMinAmountToDepositTest({ depositVault, owner }, 1.1, {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- });
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { owner, depositVault } = await loadFixture(defaultDeploy);
- await setMinAmountToDepositTest({ depositVault, owner }, 1.1);
- });
- });
-
- describe('setMaxSupplyCap()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { owner, depositVault, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
-
- await setMaxSupplyCapTest({ depositVault, owner }, 1.1, {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- });
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { owner, depositVault } = await loadFixture(defaultDeploy);
- await setMaxSupplyCapTest({ depositVault, owner }, 1.1);
- });
- });
-
- describe('setMinAmount()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { owner, depositVault, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
-
- await setMinAmountTest({ vault: depositVault, owner }, 1.1, {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- });
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { owner, depositVault } = await loadFixture(defaultDeploy);
- await setMinAmountTest({ vault: depositVault, owner }, 1.1);
- });
- });
-
- describe('setInstantDailyLimit()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { owner, depositVault, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
-
- await setInstantDailyLimitTest(
- { vault: depositVault, owner },
- parseUnits('1000'),
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- },
- );
- });
-
- it('should fail: try to set 0 limit', async () => {
- const { owner, depositVault } = await loadFixture(defaultDeploy);
-
- await setInstantDailyLimitTest(
- { vault: depositVault, owner },
- constants.Zero,
- {
- revertMessage: 'MV: limit zero',
- },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { owner, depositVault } = await loadFixture(defaultDeploy);
- await setInstantDailyLimitTest(
- { vault: depositVault, owner },
- parseUnits('1000'),
- );
- });
- });
-
- describe('addPaymentToken()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVault, regularAccounts, owner } = await loadFixture(
- defaultDeploy,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- ethers.constants.AddressZero,
- ethers.constants.AddressZero,
- 0,
- false,
- constants.MaxUint256,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
-
- it('should fail: when token is already added', async () => {
- const { depositVault, stableCoins, owner, dataFeed } = await loadFixture(
- defaultDeploy,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- false,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- false,
- constants.MaxUint256,
- {
- revertMessage: 'MV: already added',
- },
- );
- });
-
- it('should fail: when token dataFeed address zero', async () => {
- const { depositVault, stableCoins, owner } = await loadFixture(
- defaultDeploy,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- constants.AddressZero,
- 0,
- false,
- constants.MaxUint256,
- {
- revertMessage: 'zero address',
- },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVault, stableCoins, owner, dataFeed } = await loadFixture(
- defaultDeploy,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- false,
- );
- });
-
- it('call when allowance is zero', async () => {
- const { depositVault, stableCoins, owner, dataFeed } = await loadFixture(
- defaultDeploy,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- false,
- constants.Zero,
- );
- });
-
- it('call when allowance is not uint256 max', async () => {
- const { depositVault, stableCoins, owner, dataFeed } = await loadFixture(
- defaultDeploy,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- false,
- parseUnits('100'),
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role and add 3 options on a row', async () => {
- const { depositVault, stableCoins, owner, dataFeed } = await loadFixture(
- defaultDeploy,
- );
-
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.usdt,
- dataFeed.address,
- 0,
- true,
- );
- });
- });
-
- describe('addWaivedFeeAccount()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVault, regularAccounts, owner } = await loadFixture(
- defaultDeploy,
- );
- await addWaivedFeeAccountTest(
- { vault: depositVault, owner },
- ethers.constants.AddressZero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('should fail: if account fee already waived', async () => {
- const { depositVault, owner } = await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: depositVault, owner },
- owner.address,
- );
- await addWaivedFeeAccountTest(
- { vault: depositVault, owner },
- owner.address,
- { revertMessage: 'MV: already added' },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVault, owner } = await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: depositVault, owner },
- owner.address,
- );
- });
- });
-
- describe('removeWaivedFeeAccount()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVault, regularAccounts, owner } = await loadFixture(
- defaultDeploy,
- );
- await removeWaivedFeeAccountTest(
- { vault: depositVault, owner },
- ethers.constants.AddressZero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('should fail: if account not found in restriction', async () => {
- const { depositVault, owner } = await loadFixture(defaultDeploy);
- await removeWaivedFeeAccountTest(
- { vault: depositVault, owner },
- owner.address,
- { revertMessage: 'MV: not found' },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVault, owner } = await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: depositVault, owner },
- owner.address,
- );
- await removeWaivedFeeAccountTest(
- { vault: depositVault, owner },
- owner.address,
- );
- });
- });
-
- describe('setFee()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVault, regularAccounts, owner } = await loadFixture(
- defaultDeploy,
- );
- await setInstantFeeTest(
- { vault: depositVault, owner },
- ethers.constants.Zero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
-
- it('should fail: if new value greater then 100%', async () => {
- const { depositVault, owner } = await loadFixture(defaultDeploy);
- await setInstantFeeTest({ vault: depositVault, owner }, 10001, {
- revertMessage: 'fee > 100%',
+import { addPaymentTokenTest } from '../common/manageable-vault.helpers';
+import { initializeDv } from '../common/vault-initializer.helpers';
+
+depositVaultSuits(
+ 'DepositVault',
+ defaultDeploy,
+ {
+ createNew: async (owner: SignerWithAddress) =>
+ new DepositVaultTest__factory(owner).deploy(),
+ key: 'depositVault',
+ },
+ async () => {
+ await validateImplementation(DepositVault__factory);
+ },
+ {
+ deployUninitialized: (fixture) =>
+ new DepositVaultTest__factory(fixture.owner).deploy(),
+ initialize: async (fixture, params, opt) => {
+ await initializeDv(
+ { ...baseInitParamsDv(fixture), ...params },
+ opt?.contract,
+ opt,
+ );
+ },
+ },
+ (defaultDeploy) => {
+ describe('DepositVault', function () {
+ describe('depositInstant() with permissioned mToken', () => {
+ it('with permissioned mToken - deposit instant mints mToken to greenlisted user', async () => {
+ const baseFixture = await defaultDeploy();
+ const {
+ owner,
+ accessControl,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ mockedAggregator,
+ mTokenPermissioned,
+ mTokenPermissionedRoles,
+ mTokenPermissionedDepositVault,
+ } = await loadFixture(
+ mTokenPermissionedFixture.bind(this, baseFixture),
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenPermissionedRoles.greenlisted,
+ owner.address,
+ );
+ await addPaymentTokenTest(
+ { vault: mTokenPermissionedDepositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+
+ await mintToken(stableCoins.dai, owner, 100_000);
+ await approveBase18(
+ owner,
+ stableCoins.dai,
+ mTokenPermissionedDepositVault,
+ 100_000,
+ );
+
+ await depositInstantTest(
+ {
+ depositVault: mTokenPermissionedDepositVault,
+ owner,
+ mTBILL: mTokenPermissioned,
+ mTokenToUsdDataFeed,
+ },
+ stableCoins.dai,
+ 1000,
+ );
+ });
+
+ it('should fail: with permissioned mToken - deposit instant mints mToken to non-greenlisted user', async () => {
+ const baseFixture = await defaultDeploy();
+ const {
+ owner,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ mockedAggregator,
+ mTokenPermissioned,
+ mTokenPermissionedDepositVault,
+ } = await loadFixture(
+ mTokenPermissionedFixture.bind(this, baseFixture),
+ );
+
+ await addPaymentTokenTest(
+ { vault: mTokenPermissionedDepositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+
+ await mintToken(stableCoins.dai, owner, 100_000);
+ await approveBase18(
+ owner,
+ stableCoins.dai,
+ mTokenPermissionedDepositVault,
+ 100_000,
+ );
+
+ await depositInstantTest(
+ {
+ depositVault: mTokenPermissionedDepositVault,
+ owner,
+ mTBILL: mTokenPermissioned,
+ mTokenToUsdDataFeed,
+ },
+ stableCoins.dai,
+ 1000,
+ {
+ revertCustomError: {
+ customErrorName: 'NotGreenlisted',
+ },
+ },
+ );
+ });
});
});
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVault, owner } = await loadFixture(defaultDeploy);
- await setInstantFeeTest({ vault: depositVault, owner }, 100);
- });
- });
-
- describe('setVariabilityTolerance()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVault, regularAccounts, owner } = await loadFixture(
- defaultDeploy,
- );
- await setVariabilityToleranceTest(
- { vault: depositVault, owner },
- ethers.constants.Zero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('should fail: if new value zero', async () => {
- const { depositVault, owner } = await loadFixture(defaultDeploy);
- await setVariabilityToleranceTest(
- { vault: depositVault, owner },
- ethers.constants.Zero,
- { revertMessage: 'fee == 0' },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVault, owner } = await loadFixture(defaultDeploy);
- await setVariabilityToleranceTest({ vault: depositVault, owner }, 100);
- });
- });
-
- describe('removePaymentToken()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVault, regularAccounts, owner } = await loadFixture(
- defaultDeploy,
- );
- await removePaymentTokenTest(
- { vault: depositVault, owner },
- ethers.constants.AddressZero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
-
- it('should fail: when token is not exists', async () => {
- const { owner, depositVault, stableCoins } = await loadFixture(
- defaultDeploy,
- );
- await removePaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai.address,
- { revertMessage: 'MV: not exists' },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVault, stableCoins, owner, dataFeed } = await loadFixture(
- defaultDeploy,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await removePaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai.address,
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role and add 3 options on a row', async () => {
- const { depositVault, owner, stableCoins, dataFeed } = await loadFixture(
- defaultDeploy,
- );
-
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.usdt,
- dataFeed.address,
- 0,
- true,
- );
-
- await removePaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai.address,
- );
- await removePaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.usdc.address,
- );
- await removePaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.usdt.address,
- );
-
- await removePaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.usdt.address,
- { revertMessage: 'MV: not exists' },
- );
- });
- });
-
- describe('withdrawToken()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVault, regularAccounts, owner } = await loadFixture(
- defaultDeploy,
- );
- await withdrawTest(
- { vault: depositVault, owner },
- ethers.constants.AddressZero,
- 0,
- ethers.constants.AddressZero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
-
- it('should fail: when there is no token in vault', async () => {
- const { owner, depositVault, regularAccounts, stableCoins } =
- await loadFixture(defaultDeploy);
- await withdrawTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- 1,
- regularAccounts[0],
- { revertMessage: 'ERC20: transfer amount exceeds balance' },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVault, regularAccounts, stableCoins, owner } =
- await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, depositVault, 1);
- await withdrawTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- 1,
- regularAccounts[0],
- );
- });
- });
-
- describe('freeFromMinAmount()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const { depositVault, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- depositVault
- .connect(regularAccounts[0])
- .freeFromMinAmount(regularAccounts[1].address, true),
- ).to.be.revertedWith('WMAC: hasnt role');
- });
- it('should not fail', async () => {
- const { depositVault, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- depositVault.freeFromMinAmount(regularAccounts[0].address, true),
- ).to.not.reverted;
-
- expect(
- await depositVault.isFreeFromMinAmount(regularAccounts[0].address),
- ).to.eq(true);
- });
- it('should fail: already in list', async () => {
- const { depositVault, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- depositVault.freeFromMinAmount(regularAccounts[0].address, true),
- ).to.not.reverted;
-
- expect(
- await depositVault.isFreeFromMinAmount(regularAccounts[0].address),
- ).to.eq(true);
-
- await expect(
- depositVault.freeFromMinAmount(regularAccounts[0].address, true),
- ).to.revertedWith('DV: already free');
- });
- });
-
- describe('changeTokenAllowance()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVault, regularAccounts, owner } = await loadFixture(
- defaultDeploy,
- );
- await changeTokenAllowanceTest(
- { vault: depositVault, owner },
- ethers.constants.AddressZero,
- 0,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('should fail: token not exist', async () => {
- const { depositVault, owner, stableCoins } = await loadFixture(
- defaultDeploy,
- );
- await changeTokenAllowanceTest(
- { vault: depositVault, owner },
- stableCoins.dai.address,
- 0,
- { revertMessage: 'MV: token not exists' },
- );
- });
- it('should fail: allowance zero', async () => {
- const { depositVault, owner, stableCoins, dataFeed } = await loadFixture(
- defaultDeploy,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: depositVault, owner },
- stableCoins.dai.address,
- 0,
- { revertMessage: 'MV: zero allowance' },
- );
- });
- it('should fail: if mint exceed allowance', async () => {
- const {
- depositVault,
- stableCoins,
- owner,
- dataFeed,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, owner, 100000);
- await approveBase18(owner, stableCoins.dai, depositVault, 100000);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: depositVault, owner },
- stableCoins.dai.address,
- 100,
- );
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'MV: exceed allowance',
- },
- );
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'MV: exceed allowance',
- },
- );
- });
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVault, owner, stableCoins, dataFeed } = await loadFixture(
- defaultDeploy,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: depositVault, owner },
- stableCoins.dai.address,
- 100000000,
- );
- });
- it('should decrease if allowance < UINT_MAX', async () => {
- const {
- depositVault,
- stableCoins,
- owner,
- dataFeed,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, owner, 100000);
- await approveBase18(owner, stableCoins.dai, depositVault, 100000);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: depositVault, owner },
- stableCoins.dai.address,
- parseUnits('1000'),
- );
-
- const tokenConfigBefore = await depositVault.tokensConfig(
- stableCoins.dai.address,
- );
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 999,
- );
-
- const tokenConfigAfter = await depositVault.tokensConfig(
- stableCoins.dai.address,
- );
-
- expect(tokenConfigBefore.allowance.sub(tokenConfigAfter.allowance)).eq(
- parseUnits('999'),
- );
- });
- it('should not decrease if allowance = UINT_MAX', async () => {
- const {
- depositVault,
- stableCoins,
- owner,
- dataFeed,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, owner, 100000);
- await approveBase18(owner, stableCoins.dai, depositVault, 100000);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: depositVault, owner },
- stableCoins.dai.address,
- constants.MaxUint256,
- );
-
- const tokenConfigBefore = await depositVault.tokensConfig(
- stableCoins.dai.address,
- );
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 999,
- );
-
- const tokenConfigAfter = await depositVault.tokensConfig(
- stableCoins.dai.address,
- );
-
- expect(tokenConfigBefore.allowance).eq(tokenConfigAfter.allowance);
- });
- });
-
- describe('changeTokenFee()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { depositVault, regularAccounts, owner } = await loadFixture(
- defaultDeploy,
- );
- await changeTokenFeeTest(
- { vault: depositVault, owner },
- ethers.constants.AddressZero,
- 0,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('should fail: token not exist', async () => {
- const { depositVault, owner, stableCoins } = await loadFixture(
- defaultDeploy,
- );
- await changeTokenFeeTest(
- { vault: depositVault, owner },
- stableCoins.dai.address,
- 0,
- { revertMessage: 'MV: token not exists' },
- );
- });
- it('should fail: fee > 100%', async () => {
- const { depositVault, owner, stableCoins, dataFeed } = await loadFixture(
- defaultDeploy,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenFeeTest(
- { vault: depositVault, owner },
- stableCoins.dai.address,
- 10001,
- { revertMessage: 'fee > 100%' },
- );
- });
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { depositVault, owner, stableCoins, dataFeed } = await loadFixture(
- defaultDeploy,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenFeeTest(
- { vault: depositVault, owner },
- stableCoins.dai.address,
- 100,
- );
- });
- });
-
- describe('depositInstant()', async () => {
- it('should fail: when there is no token in vault', async () => {
- const { owner, depositVault, stableCoins, mTBILL, mTokenToUsdDataFeed } =
- await loadFixture(defaultDeploy);
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'MV: token not exists',
- },
- );
- });
-
- it('should fail: when trying to deposit 0 amount', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 0,
- {
- revertMessage: 'DV: invalid amount',
- },
- );
- });
-
- it('should fail: when function paused', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector(
- 'depositInstant(address,uint256,uint256,bytes32)',
- );
- await pauseVaultFn(depositVault, selector);
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: when rounding is invalid', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVault, owner }, 10);
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100.0000000001,
- {
- revertMessage: 'MV: invalid rounding',
- },
- );
- });
-
- it('should fail: call with insufficient allowance', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVault, owner }, 10);
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'ERC20: insufficient allowance',
- },
- );
- });
-
- it('should fail: call with insufficient balance', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVault, owner }, 10);
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'ERC20: transfer amount exceeds balance',
- },
- );
- });
-
- it('should fail: dataFeed rate 0 ', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mockedAggregator,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, stableCoins.dai, depositVault, 10);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await mintToken(stableCoins.dai, owner, 100_000);
- await setRoundData({ mockedAggregator }, 0);
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'DF: feed is deprecated',
- },
- );
- });
-
- it('should fail: call for amount < minAmountToDepositTest', async () => {
- const {
- depositVault,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(stableCoins.dai, owner, 100_000);
- await approveBase18(owner, stableCoins.dai, depositVault, 100_000);
-
- await setMinAmountToDepositTest({ depositVault, owner }, 100_000);
- await setInstantDailyLimitTest({ vault: depositVault, owner }, 150_000);
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'DV: mint amount < min',
- },
- );
- });
-
- it('should fail: call for amount < minAmount', async () => {
- const {
- depositVault,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(stableCoins.dai, owner, 100_000);
- await approveBase18(owner, stableCoins.dai, depositVault, 100_000);
-
- await setMinAmountToDepositTest({ depositVault, owner }, 100_000);
- await setInstantDailyLimitTest({ vault: depositVault, owner }, 150_000);
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 99,
- {
- revertMessage: 'DV: mToken amount < min',
- },
- );
- });
-
- it('should fail: if exceed allowance of deposit for token', async () => {
- const {
- depositVault,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(stableCoins.dai, owner, 100_000);
- await changeTokenAllowanceTest(
- { vault: depositVault, owner },
- stableCoins.dai.address,
- 100,
- );
- await approveBase18(owner, stableCoins.dai, depositVault, 100_000);
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'MV: exceed allowance',
- },
- );
- });
-
- it('should fail: if mint limit exceeded', async () => {
- const {
- depositVault,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(stableCoins.dai, owner, 100_000);
- await setInstantDailyLimitTest({ vault: depositVault, owner }, 1000);
-
- await approveBase18(owner, stableCoins.dai, depositVault, 100_000);
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'MV: exceed limit',
- },
- );
- });
-
- it('should fail: if min receive amount greater then actual', async () => {
- const {
- depositVault,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(stableCoins.dai, owner, 100_000);
-
- await approveBase18(owner, stableCoins.dai, depositVault, 100_000);
-
- await depositInstantTest(
- {
- depositVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- minAmount: parseUnits('100000'),
- },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'DV: minReceiveAmount > actual',
- },
- );
- });
-
- it('should fail: if some fee = 100%', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 10000,
- true,
- );
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'DV: mToken amount < min',
- },
- );
-
- await removePaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setInstantFeeTest({ vault: depositVault, owner }, 10000);
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- { revertMessage: 'DV: mToken amount < min' },
- );
- });
-
- it('should fail: greenlist enabled and user not in greenlist ', async () => {
- const { owner, depositVault, stableCoins, mTBILL, mTokenToUsdDataFeed } =
- await loadFixture(defaultDeploy);
-
- await depositVault.setGreenlistEnable(true);
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: user in blacklist ', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: user in sanctions list', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- regularAccounts[0],
- );
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('should fail: greenlist enabled and recipient not in greenlist (custom recipient overload)', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await depositVault.setGreenlistEnable(true);
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed, customRecipient },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: recipient in blacklist (custom recipient overload)', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- customRecipient,
- );
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed, customRecipient },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: recipient in sanctions list (custom recipient overload)', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- customRecipient,
- );
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed, customRecipient },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('should fail: when function paused (custom recipient overload)', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- customRecipient,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector(
- 'depositInstant(address,uint256,uint256,bytes32,address)',
- );
- await pauseVaultFn(depositVault, selector);
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed, customRecipient },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: when 0 supply cap is left', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- customRecipient,
- mockedAggregator,
- mockedAggregatorMToken,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setMaxSupplyCapTest({ depositVault, owner }, 99);
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setInstantFeeTest({ vault: depositVault, owner }, 0);
- await setMinAmountTest({ vault: depositVault, owner }, 0);
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed, customRecipient },
- stableCoins.dai,
- 99,
- {
- from: regularAccounts[0],
- },
- );
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed, customRecipient },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'DV: max supply cap exceeded',
- },
- );
- });
-
- it('should fail: when 10 supply cap is left and try to mint 11', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- customRecipient,
- mockedAggregator,
- mockedAggregatorMToken,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, regularAccounts[0], 101);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVault,
- 101,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setMaxSupplyCapTest({ depositVault, owner }, 100);
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setInstantFeeTest({ vault: depositVault, owner }, 0);
- await setMinAmountTest({ vault: depositVault, owner }, 0);
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed, customRecipient },
- stableCoins.dai,
- 90,
- {
- from: regularAccounts[0],
- },
- );
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed, customRecipient },
- stableCoins.dai,
- 11,
- {
- from: regularAccounts[0],
- revertMessage: 'DV: max supply cap exceeded',
- },
- );
- });
-
- it('when 10 supply cap is left and try to mint 10', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- customRecipient,
- mockedAggregator,
- mockedAggregatorMToken,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setMaxSupplyCapTest({ depositVault, owner }, 100);
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setInstantFeeTest({ vault: depositVault, owner }, 0);
- await setMinAmountTest({ vault: depositVault, owner }, 0);
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed, customRecipient },
- stableCoins.dai,
- 90,
- {
- from: regularAccounts[0],
- },
- );
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed, customRecipient },
- stableCoins.dai,
- 10,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit 100 DAI, greenlist enabled and user in greenlist ', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- greenListableTester,
- mTokenToUsdDataFeed,
- accessControl,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await depositVault.setGreenlistEnable(true);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit 100 DAI when 10% growth is applied', async () => {
- const {
- owner,
- depositVault,
- customFeedGrowth,
- stableCoins,
- mTBILL,
- greenListableTester,
- mTokenToUsdDataFeed,
- accessControl,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await depositVault.setGreenlistEnable(true);
-
- await mTokenToUsdDataFeed.changeAggregator(customFeedGrowth.address);
- await setRoundDataGrowth({ owner, customFeedGrowth }, 1, -1000, 10);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositInstantTest(
- {
- depositVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- expectedMintAmount: parseUnits('98.999684191007430686', 18),
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit 100 DAI when -10% growth is applied', async () => {
- const {
- owner,
- depositVault,
- customFeedGrowth,
- stableCoins,
- mTBILL,
- greenListableTester,
- mTokenToUsdDataFeed,
- accessControl,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await depositVault.setGreenlistEnable(true);
-
- await setMinGrowthApr({ owner, customFeedGrowth }, -10);
- await mTokenToUsdDataFeed.changeAggregator(customFeedGrowth.address);
- await setRoundDataGrowth({ owner, customFeedGrowth }, 1, -1000, -10);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositInstantTest(
- {
- depositVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- expectedMintAmount: parseUnits('99.000315811007437113', 18),
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit 100 DAI, greenlist enabled and user in greenlist, tokenIn not stablecoin', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- greenListableTester,
- mTokenToUsdDataFeed,
- accessControl,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await depositVault.setGreenlistEnable(true);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- false,
- );
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit 100 DAI, when price of stable is 1.03$ and mToken price is 5$', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- });
-
- it('deposit 100 DAI, when price of stable is 1.03$ and mToken price is 5$ and token fee 1%', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- });
-
- it('deposit 100 DAI, when price of stable is 1.03$ and mToken price is 5$ without checking of minDepositAmount', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await depositVault.freeFromMinAmount(owner.address, true);
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- });
-
- it('deposit 100 DAI, when price of stable is 1.03$ and mToken price is 5$ and user in waivedFeeRestriction', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await addWaivedFeeAccountTest(
- { vault: depositVault, owner },
- owner.address,
- );
- await setMinAmountTest({ vault: depositVault, owner }, 10);
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed, waivedFee: true },
- stableCoins.dai,
- 100,
- );
- });
-
- it('deposit 100 DAI (custom recipient overload)', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- greenListableTester,
- mTokenToUsdDataFeed,
- accessControl,
- regularAccounts,
- dataFeed,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositInstantTest(
- {
- depositVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit 100 DAI when recipient == msg.sender (custom recipient overload)', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositInstantTest(
- {
- depositVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient: regularAccounts[0],
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit 100 DAI when other overload of depositInstant is paused (custom recipient overload)', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- depositVault,
- encodeFnSelector('depositInstant(address,uint256,uint256,bytes32)'),
- );
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositInstantTest(
- {
- depositVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit 100 DAI when other overload of depositInstant is paused', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- depositVault,
- encodeFnSelector(
- 'depositInstant(address,uint256,uint256,bytes32,address)',
- ),
- );
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositInstantTest(
- {
- depositVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('with permissioned mToken - deposit instant mints mToken to greenlisted user', async () => {
- const baseFixture = await defaultDeploy();
- const {
- owner,
- accessControl,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- mockedAggregator,
- mTokenPermissioned,
- mTokenPermissionedRoles,
- mTokenPermissionedDepositVault,
- } = await loadFixture(mTokenPermissionedFixture.bind(this, baseFixture));
-
- await accessControl.grantRole(
- mTokenPermissionedRoles.greenlisted,
- owner.address,
- );
- await addPaymentTokenTest(
- { vault: mTokenPermissionedDepositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(stableCoins.dai, owner, 100_000);
- await approveBase18(
- owner,
- stableCoins.dai,
- mTokenPermissionedDepositVault,
- 100_000,
- );
-
- await depositInstantTest(
- {
- depositVault: mTokenPermissionedDepositVault,
- owner,
- mTBILL: mTokenPermissioned,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1000,
- );
- });
-
- it('should fail: with permissioned mToken - deposit instant mints mToken to non-greenlisted user', async () => {
- const baseFixture = await defaultDeploy();
- const {
- owner,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- mockedAggregator,
- mTokenPermissioned,
- mTokenPermissionedDepositVault,
- } = await loadFixture(mTokenPermissionedFixture.bind(this, baseFixture));
-
- await addPaymentTokenTest(
- { vault: mTokenPermissionedDepositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(stableCoins.dai, owner, 100_000);
- await approveBase18(
- owner,
- stableCoins.dai,
- mTokenPermissionedDepositVault,
- 100_000,
- );
-
- await depositInstantTest(
- {
- depositVault: mTokenPermissionedDepositVault,
- owner,
- mTBILL: mTokenPermissioned,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1000,
- {
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- },
- );
- });
- });
-
- describe('depositRequest()', async () => {
- it('should fail: when there is no token in vault', async () => {
- const { owner, depositVault, stableCoins, mTBILL, mTokenToUsdDataFeed } =
- await loadFixture(defaultDeploy);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'MV: token not exists',
- },
- );
- });
-
- it('should fail: when trying to deposit 0 amount', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 0,
- {
- revertMessage: 'DV: invalid amount',
- },
- );
- });
-
- it('should fail: when function paused', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector(
- 'depositRequest(address,uint256,bytes32)',
- );
- await pauseVaultFn(depositVault, selector);
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: when function paused (custom recipient overload)', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- customRecipient,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector(
- 'depositRequest(address,uint256,bytes32,address)',
- );
- await pauseVaultFn(depositVault, selector);
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed, customRecipient },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: when rounding is invalid', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVault, owner }, 10);
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100.0000000001,
- {
- revertMessage: 'MV: invalid rounding',
- },
- );
- });
-
- it('should fail: call with insufficient allowance', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVault, owner }, 10);
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'ERC20: insufficient allowance',
- },
- );
- });
-
- it('should fail: call with insufficient balance', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVault, owner }, 10);
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'ERC20: transfer amount exceeds balance',
- },
- );
- });
-
- it('should fail: dataFeed rate 0 ', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mockedAggregator,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, stableCoins.dai, depositVault, 10);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await mintToken(stableCoins.dai, owner, 100_000);
- await setRoundData({ mockedAggregator }, 0);
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'DF: feed is deprecated',
- },
- );
- });
-
- it('should fail: call for amount < minAmountToDepositTest', async () => {
- const {
- depositVault,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(stableCoins.dai, owner, 100_000);
- await approveBase18(owner, stableCoins.dai, depositVault, 100_000);
-
- await setMinAmountToDepositTest({ depositVault, owner }, 100_000);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'DV: mint amount < min',
- },
- );
- });
-
- it('should fail: if exceed allowance of deposit for token', async () => {
- const {
- depositVault,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(stableCoins.dai, owner, 100_000);
- await changeTokenAllowanceTest(
- { vault: depositVault, owner },
- stableCoins.dai.address,
- 100,
- );
- await approveBase18(owner, stableCoins.dai, depositVault, 100_000);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'MV: exceed allowance',
- },
- );
- });
-
- it('should fail: if token fee = 100%', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 10000,
- true,
- );
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'DV: mToken amount < min',
- },
- );
- });
-
- it('should fail: greenlist enabled and user not in greenlist ', async () => {
- const { owner, depositVault, stableCoins, mTBILL, mTokenToUsdDataFeed } =
- await loadFixture(defaultDeploy);
-
- await depositVault.setGreenlistEnable(true);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: user in blacklist ', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: user in sanctionlist ', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- regularAccounts[0],
- );
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('should fail: greenlist enabled and recipient not in greenlist (custom recipient overload)', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await depositVault.setGreenlistEnable(true);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed, customRecipient },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: recipient in blacklist (custom recipient overload)', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- customRecipient,
- );
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed, customRecipient },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: recipient in sanctionlist (custom recipient overload)', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- customRecipient,
- );
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed, customRecipient },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('deposit 100 DAI, greenlist enabled and user in greenlist ', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- greenListableTester,
- mTokenToUsdDataFeed,
- accessControl,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await greenListEnable(
- { greenlistable: greenListableTester, owner },
- true,
- );
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit 100 DAI when 10% growth is applied', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- greenListableTester,
- mTokenToUsdDataFeed,
- accessControl,
- regularAccounts,
- dataFeed,
- customFeedGrowth,
- } = await loadFixture(defaultDeploy);
-
- await mTokenToUsdDataFeed.changeAggregator(customFeedGrowth.address);
- await setRoundDataGrowth({ owner, customFeedGrowth }, 1, -1000, 10);
-
- await greenListEnable(
- { greenlistable: greenListableTester, owner },
- true,
- );
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit 100 DAI when -10% growth is applied', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- greenListableTester,
- mTokenToUsdDataFeed,
- accessControl,
- regularAccounts,
- dataFeed,
- customFeedGrowth,
- } = await loadFixture(defaultDeploy);
-
- await mTokenToUsdDataFeed.changeAggregator(customFeedGrowth.address);
- await setMinGrowthApr({ owner, customFeedGrowth }, -10);
- await setRoundDataGrowth({ owner, customFeedGrowth }, 1, -1000, -10);
-
- await greenListEnable(
- { greenlistable: greenListableTester, owner },
- true,
- );
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit request with 100 DAI, when price of stable is 1.03$ and mToken price is 5$', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVault, owner }, 10);
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- });
-
- it('deposit request with 100 DAI, when price of stable is 1.03$ and mToken price is 5$ and token fee 1%', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
- await setMinAmountTest({ vault: depositVault, owner }, 10);
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- });
-
- it('deposit request with 100 DAI, when price of stable is 1.03$ and mToken price is 5$ without checking of minDepositAmount', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await depositVault.freeFromMinAmount(owner.address, true);
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- });
-
- it('deposit request with 100 DAI, when price of stable is 1.03$ and mToken price is 5$ and user in waivedFeeRestriction', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
- await setMinAmountTest({ vault: depositVault, owner }, 10);
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await addWaivedFeeAccountTest(
- { vault: depositVault, owner },
- owner.address,
- );
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed, waivedFee: true },
- stableCoins.dai,
- 100,
- );
- });
-
- it('deposit 100 (custom recipient overload)', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed, customRecipient },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit 100 when recipient == msg.sender (custom recipient overload)', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient: regularAccounts[0],
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit 100 DAI when other overload of depositRequest is paused (custom recipient overload)', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- depositVault,
- encodeFnSelector('depositRequest(address,uint256,bytes32)'),
- );
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed, customRecipient },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit 100 DAI when other overload of depositRequest is paused', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- depositVault,
- encodeFnSelector('depositRequest(address,uint256,bytes32,address)'),
- );
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
- });
-
- describe('approveRequest()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const { depositVault, regularAccounts, mTokenToUsdDataFeed, mTBILL } =
- await loadFixture(defaultDeploy);
- await approveRequestTest(
- {
- depositVault,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 1,
- parseUnits('5'),
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await approveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- 1,
- parseUnits('5'),
- {
- revertMessage: 'DV: request not exist',
- },
- );
- });
-
- it('should fail: request already precessed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await approveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- requestId,
- parseUnits('5'),
- );
- await approveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- requestId,
- parseUnits('5'),
- { revertMessage: 'DV: request not pending' },
- );
- });
-
- it('approve request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await approveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- requestId,
- parseUnits('5'),
- );
- });
- });
-
- describe('safeApproveRequest()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const { depositVault, regularAccounts, mTokenToUsdDataFeed, mTBILL } =
- await loadFixture(defaultDeploy);
- await safeApproveRequestTest(
- {
- depositVault,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 1,
- parseUnits('1'),
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await safeApproveRequestTest(
- {
- depositVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 1,
- parseUnits('1'),
- {
- revertMessage: 'DV: request not exist',
- },
- );
- });
-
- it('should fail: if new rate greater then variabilityTolerance', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
- await safeApproveRequestTest(
- {
- depositVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- requestId,
- parseUnits('6'),
- {
- revertMessage: 'MV: exceed price diviation',
- },
- );
- });
-
- it('should fail: if new rate lower then variabilityTolerance', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
- await safeApproveRequestTest(
- {
- depositVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- requestId,
- parseUnits('4'),
- {
- revertMessage: 'MV: exceed price diviation',
- },
- );
- });
-
- it('should fail: request already precessed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- requestId,
- parseUnits('5.000001'),
- );
- await safeApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- requestId,
- parseUnits('5.000001'),
- { revertMessage: 'DV: request not pending' },
- );
- });
-
- it('should fail: when 0 supply cap is left', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- customRecipient,
- mockedAggregator,
- mockedAggregatorMToken,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setMaxSupplyCapTest({ depositVault, owner }, 99);
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setMinAmountTest({ vault: depositVault, owner }, 0);
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed, customRecipient },
- stableCoins.dai,
- 99,
- {
- from: regularAccounts[0],
- },
- );
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed, customRecipient },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- },
- );
-
- await safeApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- 0,
- parseUnits('1'),
- {
- revertMessage: 'DV: max supply cap exceeded',
- },
- );
- });
-
- it('should fail: when 10 supply cap is left and try to mint 11', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- customRecipient,
- mockedAggregator,
- mockedAggregatorMToken,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, regularAccounts[0], 101);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVault,
- 101,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setMaxSupplyCapTest({ depositVault, owner }, 100);
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setMinAmountTest({ vault: depositVault, owner }, 0);
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed, customRecipient },
- stableCoins.dai,
- 90,
- {
- from: regularAccounts[0],
- },
- );
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed, customRecipient },
- stableCoins.dai,
- 11,
- {
- from: regularAccounts[0],
- },
- );
-
- await safeApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- 0,
- parseUnits('1'),
- {
- revertMessage: 'DV: max supply cap exceeded',
- },
- );
- });
-
- it('when 10 supply cap is left and try to mint 10', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- customRecipient,
- mockedAggregator,
- mockedAggregatorMToken,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setMaxSupplyCapTest({ depositVault, owner }, 100);
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setInstantFeeTest({ vault: depositVault, owner }, 0);
- await setMinAmountTest({ vault: depositVault, owner }, 0);
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed, customRecipient },
- stableCoins.dai,
- 90,
- {
- from: regularAccounts[0],
- },
- );
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed, customRecipient },
- stableCoins.dai,
- 10,
- {
- from: regularAccounts[0],
- },
- );
-
- await safeApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- 0,
- parseUnits('1'),
- );
- });
-
- it('approve request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- requestId,
- parseUnits('5.000000001'),
- );
- });
- });
-
- describe('safeBulkApproveRequestAtSavedRate()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const { depositVault, regularAccounts, mTokenToUsdDataFeed, mTBILL } =
- await loadFixture(defaultDeploy);
- await safeBulkApproveRequestTest(
- {
- depositVault,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: 1 }],
- 'request-rate',
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await safeBulkApproveRequestTest(
- {
- depositVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: 1 }],
- 'request-rate',
- {
- revertMessage: 'DV: request not exist',
- },
- );
- });
-
- it('should fail: request already precessed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeBulkApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: requestId }],
- 'request-rate',
- );
- await safeBulkApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: requestId }],
- 'request-rate',
- { revertMessage: 'DV: request not pending' },
- );
- });
-
- it('should fail: process multiple requests, when one of them already precessed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 200);
- await approveBase18(owner, stableCoins.dai, depositVault, 200);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await safeApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- 0,
- parseUnits('5.000001'),
- );
-
- await safeBulkApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 1 }, { id: 0 }],
- 'request-rate',
- { revertMessage: 'DV: request not pending' },
- );
- });
-
- it('should fail: process multiple requests, when couple of them have equal id', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 200);
- await approveBase18(owner, stableCoins.dai, depositVault, 200);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await safeApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- 0,
- parseUnits('5.000001'),
- );
-
- await safeBulkApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 1 }, { id: 1 }],
- 'request-rate',
- { revertMessage: 'DV: request not pending' },
- );
- });
-
- it('approve 2 requests when second one exceeds supply cap', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setMinAmountTest({ vault: depositVault, owner }, 0);
- await setMaxSupplyCapTest({ depositVault, owner }, 50);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 50,
- );
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 50,
- );
-
- await safeBulkApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 0 }, { id: 1, expectedToExecute: false }],
- 'request-rate',
- );
- });
-
- it('approve 1 request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeBulkApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: requestId }],
- 'request-rate',
- );
- });
-
- it('approve 2 requests from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 200);
- await approveBase18(owner, stableCoins.dai, depositVault, 200);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await safeBulkApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 0 }, { id: 1 }],
- 'request-rate',
- );
- });
-
- it('approve 10 requests from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 1000);
- await approveBase18(owner, stableCoins.dai, depositVault, 1000);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- for (let i = 0; i < 10; i++) {
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- }
-
- await safeBulkApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- Array.from({ length: 10 }, (_, i) => ({ id: i })),
- 'request-rate',
- );
- });
- });
-
- describe('safeBulkApproveRequest() (custom price overload)', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const { depositVault, regularAccounts, mTokenToUsdDataFeed, mTBILL } =
- await loadFixture(defaultDeploy);
- await safeBulkApproveRequestTest(
- {
- depositVault,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: 1 }],
- parseUnits('1'),
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await safeBulkApproveRequestTest(
- {
- depositVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: 1 }],
- parseUnits('1'),
- {
- revertMessage: 'DV: request not exist',
- },
- );
- });
-
- it('should fail: if new rate greater then variabilityTolerance', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
- await safeBulkApproveRequestTest(
- {
- depositVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: requestId }],
- parseUnits('6'),
- {
- revertMessage: 'MV: exceed price diviation',
- },
- );
- });
-
- it('should fail: if new rate lower then variabilityTolerance', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
- await safeBulkApproveRequestTest(
- {
- depositVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: requestId }],
- parseUnits('4'),
- {
- revertMessage: 'MV: exceed price diviation',
- },
- );
- });
-
- it('should fail: request already precessed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeBulkApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: requestId }],
- parseUnits('5.000001'),
- );
- await safeBulkApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: requestId }],
- parseUnits('5.000001'),
- { revertMessage: 'DV: request not pending' },
- );
- });
-
- it('should fail: process multiple requests, when one of them already precessed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 200);
- await approveBase18(owner, stableCoins.dai, depositVault, 200);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await safeApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- 0,
- parseUnits('5.000001'),
- );
-
- await safeBulkApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 1 }, { id: 0 }],
- parseUnits('5.000001'),
- { revertMessage: 'DV: request not pending' },
- );
- });
-
- it('should fail: process multiple requests, when couple of them have equal id', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 200);
- await approveBase18(owner, stableCoins.dai, depositVault, 200);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await safeApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- 0,
- parseUnits('5.000001'),
- );
-
- await safeBulkApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 1 }, { id: 1 }],
- parseUnits('5.000001'),
- { revertMessage: 'DV: request not pending' },
- );
- });
-
- it('approve 2 requests when second one exceeds supply cap', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setMinAmountTest({ vault: depositVault, owner }, 0);
- await setMaxSupplyCapTest({ depositVault, owner }, 50);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 50,
- );
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 50,
- );
-
- await safeBulkApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 0 }, { id: 1, expectedToExecute: false }],
- parseUnits('1'),
- );
- });
-
- it('approve 1 request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeBulkApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: requestId }],
- parseUnits('5.000000001'),
- );
- });
-
- it('approve 2 requests from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 200);
- await approveBase18(owner, stableCoins.dai, depositVault, 200);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await safeBulkApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 0 }, { id: 1 }],
- parseUnits('5.000000001'),
- );
- });
-
- it('approve 10 requests from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 1000);
- await approveBase18(owner, stableCoins.dai, depositVault, 1000);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- for (let i = 0; i < 10; i++) {
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- }
-
- await safeBulkApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- Array.from({ length: 10 }, (_, i) => ({ id: i })),
- parseUnits('5.000000001'),
- );
- });
- });
-
- describe('safeBulkApproveRequest() (current price overload)', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const { depositVault, regularAccounts, mTokenToUsdDataFeed, mTBILL } =
- await loadFixture(defaultDeploy);
- await safeBulkApproveRequestTest(
- {
- depositVault,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: 1 }],
- undefined,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await safeBulkApproveRequestTest(
- {
- depositVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: 1 }],
- undefined,
- {
- revertMessage: 'DV: request not exist',
- },
- );
- });
-
- it('should fail: if new rate greater then variabilityTolerance', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 10);
-
- const requestId = 0;
- await safeBulkApproveRequestTest(
- {
- depositVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: requestId }],
- undefined,
- {
- revertMessage: 'MV: exceed price diviation',
- },
- );
- });
-
- it('should fail: if new rate lower then variabilityTolerance', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 3);
-
- const requestId = 0;
- await safeBulkApproveRequestTest(
- {
- depositVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: requestId }],
- undefined,
- {
- revertMessage: 'MV: exceed price diviation',
- },
- );
- });
-
- it('should fail: request already precessed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeBulkApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: requestId }],
- undefined,
- );
- await safeBulkApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: requestId }],
- undefined,
- { revertMessage: 'DV: request not pending' },
- );
- });
-
- it('should fail: process multiple requests, when one of them already precessed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 200);
- await approveBase18(owner, stableCoins.dai, depositVault, 200);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await safeBulkApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 0 }],
- undefined,
- );
-
- await safeBulkApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 1 }, { id: 0 }],
- undefined,
- { revertMessage: 'DV: request not pending' },
- );
- });
-
- it('should fail: process multiple requests, when couple of the have equal id', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 200);
- await approveBase18(owner, stableCoins.dai, depositVault, 200);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await safeBulkApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 0 }],
- undefined,
- );
-
- await safeBulkApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 1 }, { id: 1 }],
- undefined,
- { revertMessage: 'DV: request not pending' },
- );
- });
-
- it('approve 1 request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeBulkApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: requestId }],
- undefined,
- );
- });
-
- it('approve 1 request from vaut admin account when growth is applied', async () => {
- const {
- owner,
- mockedAggregator,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- customFeedGrowth,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await setRoundDataGrowth({ owner, customFeedGrowth }, 1, -1000, 5);
- await mTokenToUsdDataFeed.changeAggregator(customFeedGrowth.address);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeBulkApproveRequestTest(
- {
- depositVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: requestId }],
- undefined,
- );
- });
-
- it('approve 2 requests from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 200);
- await approveBase18(owner, stableCoins.dai, depositVault, 200);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await safeBulkApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 0 }, { id: 1 }],
- undefined,
- );
- });
-
- it('approve 10 requests from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 1000);
- await approveBase18(owner, stableCoins.dai, depositVault, 1000);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- for (let i = 0; i < 10; i++) {
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- }
-
- await safeBulkApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- Array.from({ length: 10 }, (_, i) => ({ id: i })),
- undefined,
- );
- });
-
- it('approve 10 requests from vaut admin account when different users are recievers', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 1000);
- await approveBase18(owner, stableCoins.dai, depositVault, 1000);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- for (let i = 0; i < 10; i++) {
- await depositRequestTest(
- {
- depositVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient: regularAccounts[i],
- },
- stableCoins.dai,
- 100,
- );
- }
-
- await safeBulkApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- Array.from({ length: 10 }, (_, i) => ({ id: i })),
- undefined,
- );
- });
-
- it('approve 2 requests from vaut admin account when each request has different token', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await approveBase18(owner, stableCoins.usdc, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.usdc,
- 100,
- );
-
- await safeBulkApproveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 0 }, { id: 1 }],
- undefined,
- );
- });
- });
-
- describe('rejectRequest()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const { depositVault, regularAccounts, mTokenToUsdDataFeed, mTBILL } =
- await loadFixture(defaultDeploy);
- await rejectRequestTest(
- {
- depositVault,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await rejectRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- 1,
- {
- revertMessage: 'DV: request not exist',
- },
- );
- });
-
- it('should fail: request is already rejected', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await rejectRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- requestId,
- );
-
- await rejectRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- requestId,
- {
- revertMessage: 'DV: request not pending',
- },
- );
- });
-
- it('reject request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await rejectRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- requestId,
- );
- });
- });
-
- describe('depositInstant() complex', () => {
- it('should fail: when is paused', async () => {
- const {
- depositVault,
- owner,
- mTBILL,
- stableCoins,
- regularAccounts,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVault(depositVault);
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: paused',
- },
- );
- });
-
- it('is on pause, but admin can use everything', async () => {
- const {
- depositVault,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVault(depositVault);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'Pausable: paused',
- },
- );
- });
-
- it('call for amount == minAmountToDepositTest', async () => {
- const {
- depositVault,
- mockedAggregator,
- mockedAggregatorMToken,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(stableCoins.dai, owner, 102_000);
- await approveBase18(owner, stableCoins.dai, depositVault, 102_000);
-
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setMinAmountToDepositTest({ depositVault, owner }, 100_000);
- await setInstantDailyLimitTest(
- { vault: depositVault, owner },
- parseUnits('150000'),
- );
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 102_000,
- );
- });
-
- it('call for amount == minAmountToDepositTest+1, then deposit with amount 100', async () => {
- const {
- depositVault,
- mockedAggregator,
- mockedAggregatorMToken,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(stableCoins.dai, owner, 103_101);
- await approveBase18(owner, stableCoins.dai, depositVault, 103_101);
-
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setMinAmountToDepositTest({ depositVault, owner }, 100_000);
- await setInstantDailyLimitTest(
- { vault: depositVault, owner },
- parseUnits('150000'),
- );
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 103_001,
- );
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- });
-
- it('deposit 100 DAI, when price is 5$, 25 USDC when price is 5.1$, 14 USDT when price is 5.4$', async () => {
- const {
- owner,
- mockedAggregator,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await mintToken(stableCoins.usdc, owner, 125);
- await mintToken(stableCoins.usdt, owner, 114);
-
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await approveBase18(owner, stableCoins.usdc, depositVault, 125);
- await approveBase18(owner, stableCoins.usdt, depositVault, 114);
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.usdt,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.04);
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await setRoundData({ mockedAggregator }, 1);
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.usdc,
- 125,
- );
-
- await setRoundData({ mockedAggregator }, 1.01);
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.usdt,
- 114,
- );
- });
- });
-
- describe('depositRequest() complex', () => {
- it('should fail: when is paused', async () => {
- const {
- depositVault,
- owner,
- mTBILL,
- stableCoins,
- regularAccounts,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVault(depositVault);
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: paused',
- },
- );
- });
-
- it('is on pause, but admin can use everything', async () => {
- const {
- depositVault,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVault(depositVault);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'Pausable: paused',
- },
- );
- });
-
- it('call for amount == minAmountToDepositTest', async () => {
- const {
- depositVault,
- mockedAggregator,
- mockedAggregatorMToken,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(stableCoins.dai, owner, 105_000);
- await approveBase18(owner, stableCoins.dai, depositVault, 105_000);
-
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setMinAmountToDepositTest({ depositVault, owner }, 100_000);
- await setInstantDailyLimitTest({ vault: depositVault, owner }, 150_000);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 102_000,
- );
- const requestId = 0;
-
- await approveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- requestId,
- parseUnits('5'),
- );
- });
-
- it('call for amount == minAmountToDepositTest+1, then deposit with amount 1', async () => {
- const {
- depositVault,
- mockedAggregator,
- mockedAggregatorMToken,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(stableCoins.dai, owner, 105_101);
- await approveBase18(owner, stableCoins.dai, depositVault, 105_101);
-
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setMinAmountToDepositTest({ depositVault, owner }, 100_000);
- await setInstantDailyLimitTest({ vault: depositVault, owner }, 150_000);
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 102_001,
- );
- let requestId = 0;
-
- await approveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- requestId,
- parseUnits('5'),
- );
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- requestId = 1;
-
- await approveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- requestId,
- parseUnits('5'),
- );
- });
-
- it('deposit 100 DAI, when price is 5$, 25 USDC when price is 5.1$, 14 USDT when price is 5.4$', async () => {
- const {
- owner,
- mockedAggregator,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await mintToken(stableCoins.usdc, owner, 125);
- await mintToken(stableCoins.usdt, owner, 114);
-
- await approveBase18(owner, stableCoins.dai, depositVault, 100);
- await approveBase18(owner, stableCoins.usdc, depositVault, 125);
- await approveBase18(owner, stableCoins.usdt, depositVault, 114);
-
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.usdt,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVault, owner }, 10);
-
- await setRoundData({ mockedAggregator }, 1.04);
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- let requestId = 0;
-
- await approveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- requestId,
- parseUnits('5'),
- );
-
- await setRoundData({ mockedAggregator }, 1);
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.usdc,
- 125,
- );
- requestId = 1;
-
- await approveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- requestId,
- parseUnits('5'),
- );
-
- await setRoundData({ mockedAggregator }, 1.01);
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.usdt,
- 114,
- );
- requestId = 2;
-
- await approveRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
- requestId,
- parseUnits('5'),
- );
- });
-
- it('when 10 supply cap is left and try to mint request 100', async () => {
- const {
- owner,
- depositVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- customRecipient,
- mockedAggregator,
- mockedAggregatorMToken,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, regularAccounts[0], 190);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVault,
- 190,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setMaxSupplyCapTest({ depositVault, owner }, 100);
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setMinAmountTest({ vault: depositVault, owner }, 0);
-
- await depositInstantTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed, customRecipient },
- stableCoins.dai,
- 90,
- {
- from: regularAccounts[0],
- },
- );
-
- await depositRequestTest(
- { depositVault, owner, mTBILL, mTokenToUsdDataFeed, customRecipient },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
- });
-
- describe('ManageableVault internal functions', () => {
- it('should fail: invalid rounding tokenTransferFromToTester()', async () => {
- const { depositVault, stableCoins, owner } = await loadFixture(
- defaultDeploy,
- );
-
- await mintToken(stableCoins.usdc, owner, 1000);
-
- await approveBase18(owner, stableCoins.usdc, depositVault, 1000);
-
- await expect(
- depositVault.tokenTransferFromToTester(
- stableCoins.usdc.address,
- owner.address,
- depositVault.address,
- parseUnits('999.999999999'),
- 8,
- ),
- ).revertedWith('MV: invalid rounding');
- });
-
- it('should fail: invalid rounding tokenTransferToUserTester()', async () => {
- const { depositVault, stableCoins, owner } = await loadFixture(
- defaultDeploy,
- );
-
- await mintToken(stableCoins.usdc, depositVault, 1000);
-
- await expect(
- depositVault.tokenTransferToUserTester(
- stableCoins.usdc.address,
- owner.address,
- parseUnits('999.999999999'),
- 8,
- ),
- ).revertedWith('MV: invalid rounding');
- });
- });
-
- describe('_convertUsdToToken', () => {
- it('should fail: when amountUsd == 0', async () => {
- const { depositVault } = await loadFixture(defaultDeploy);
-
- await expect(
- depositVault.convertTokenToUsdTest(constants.AddressZero, 0),
- ).revertedWith('DV: amount zero');
- });
-
- it('should fail: when tokenRate == 0', async () => {
- const { depositVault } = await loadFixture(defaultDeploy);
-
- await depositVault.setOverrideGetTokenRate(true);
- await depositVault.setGetTokenRateValue(0);
-
- await expect(
- depositVault.convertTokenToUsdTest(constants.AddressZero, 1),
- ).revertedWith('DV: rate zero');
- });
- });
-
- describe('_convertUsdToMToken', () => {
- it('should fail: when rate == 0', async () => {
- const { depositVault } = await loadFixture(defaultDeploy);
-
- await depositVault.setOverrideGetTokenRate(true);
- await depositVault.setGetTokenRateValue(0);
-
- await expect(depositVault.convertUsdToMTokenTest(1)).revertedWith(
- 'DV: rate zero',
- );
- });
- });
-
- describe('_calcAndValidateDeposit', () => {
- it('should fail: when tokenOut is not MANUAL_FULLFILMENT_TOKEN but isFiat = true', async () => {
- const { depositVault, stableCoins, owner, dataFeed } = await loadFixture(
- defaultDeploy,
- );
-
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- parseUnits('100', 2),
- true,
- );
-
- await setMinAmountTest({ vault: depositVault, owner }, 0);
-
- await expect(
- depositVault.calcAndValidateDeposit(
- constants.AddressZero,
- stableCoins.dai.address,
- parseUnits('100'),
- true,
- ),
- ).revertedWith('DV: invalid mint amount');
- });
- });
-});
+ },
+);
diff --git a/test/unit/DepositVaultWithAave.test.ts b/test/unit/DepositVaultWithAave.test.ts
index c7b47a55..4a4503df 100644
--- a/test/unit/DepositVaultWithAave.test.ts
+++ b/test/unit/DepositVaultWithAave.test.ts
@@ -1,19 +1,23 @@
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
+import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { expect } from 'chai';
-import { constants } from 'ethers';
-import { parseUnits } from 'ethers/lib/utils';
import { ethers } from 'hardhat';
-import { encodeFnSelector } from '../../helpers/utils';
-import { ManageableVaultTester__factory } from '../../typechain-types';
-import { acErrors, blackList, greenList } from '../common/ac.helpers';
+import {
+ baseInitParamsDv,
+ depositVaultSuits,
+} from './suits/deposit-vault.suits';
+
+import {
+ DepositVaultWithAave__factory,
+ DepositVaultWithAaveTest__factory,
+} from '../../typechain-types';
+import { acErrors } from '../common/ac.helpers';
import {
approveBase18,
mintToken,
- pauseVault,
- pauseVaultFn,
+ validateImplementation,
} from '../common/common.helpers';
-import { setRoundData } from '../common/data-feed.helpers';
import {
depositInstantWithAaveTest,
depositRequestWithAaveTest,
@@ -22,2654 +26,708 @@ import {
setAavePoolTest,
setAutoInvestFallbackEnabledAaveTest,
} from '../common/deposit-vault-aave.helpers';
-import {
- approveRequestTest,
- depositRequestTest,
- rejectRequestTest,
- safeApproveRequestTest,
- safeBulkApproveRequestTest,
-} from '../common/deposit-vault.helpers';
import { defaultDeploy } from '../common/fixtures';
import {
addPaymentTokenTest,
- addWaivedFeeAccountTest,
- changeTokenAllowanceTest,
- removePaymentTokenTest,
- removeWaivedFeeAccountTest,
- setInstantFeeTest,
- setInstantDailyLimitTest,
- setMinAmountToDepositTest,
setMinAmountTest,
- setVariabilityToleranceTest,
- withdrawTest,
- changeTokenFeeTest,
} from '../common/manageable-vault.helpers';
-import { sanctionUser } from '../common/with-sanctions-list.helpers';
-
-describe('DepositVaultWithAave', function () {
- it('deployment', async () => {
- const {
- depositVaultWithAave,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- roles,
- aavePoolMock,
- stableCoins,
- } = await loadFixture(defaultDeploy);
-
- expect(await depositVaultWithAave.mToken()).eq(mTBILL.address);
- expect(await depositVaultWithAave.paused()).eq(false);
- expect(await depositVaultWithAave.tokensReceiver()).eq(
- tokensReceiver.address,
- );
- expect(await depositVaultWithAave.feeReceiver()).eq(feeReceiver.address);
- expect(await depositVaultWithAave.ONE_HUNDRED_PERCENT()).eq('10000');
- expect(await depositVaultWithAave.minMTokenAmountForFirstDeposit()).eq('0');
- expect(await depositVaultWithAave.minAmount()).eq(parseUnits('100'));
- expect(await depositVaultWithAave.instantFee()).eq('100');
- expect(await depositVaultWithAave.instantDailyLimit()).eq(
- parseUnits('100000'),
- );
- expect(await depositVaultWithAave.mTokenDataFeed()).eq(
- mTokenToUsdDataFeed.address,
- );
- expect(await depositVaultWithAave.variationTolerance()).eq(1);
- expect(await depositVaultWithAave.vaultRole()).eq(
- roles.tokenRoles.mTBILL.depositVaultAdmin,
- );
- expect(await depositVaultWithAave.MANUAL_FULLFILMENT_TOKEN()).eq(
- ethers.constants.AddressZero,
- );
+import { initializeDvWithAave } from '../common/vault-initializer.helpers';
+
+depositVaultSuits(
+ 'DepositVaultWithAave',
+ defaultDeploy,
+ {
+ createNew: async (owner: SignerWithAddress) =>
+ new DepositVaultWithAaveTest__factory(owner).deploy(),
+ key: 'depositVaultWithAave',
+ },
+ async (fixture) => {
+ const { depositVaultWithAave, stableCoins, aavePoolMock } = fixture;
expect(await depositVaultWithAave.aavePools(stableCoins.usdc.address)).eq(
aavePoolMock.address,
);
expect(await depositVaultWithAave.aaveDepositsEnabled()).eq(false);
- });
-
- describe('initialization', () => {
- it('should fail: call initialize() when already initialized', async () => {
- const { depositVaultWithAave } = await loadFixture(defaultDeploy);
-
- await expect(
- depositVaultWithAave.initialize(
- constants.AddressZero,
- {
- mToken: constants.AddressZero,
- mTokenDataFeed: constants.AddressZero,
- },
- {
- feeReceiver: constants.AddressZero,
- tokensReceiver: constants.AddressZero,
- },
- {
- instantFee: 0,
- instantDailyLimit: 0,
- },
- constants.AddressZero,
- 0,
- 0,
- 0,
- 0,
- ),
- ).revertedWith('Initializable: contract is already initialized');
- });
-
- it('should fail: call with initializing == false', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initializeWithoutInitializer(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- ),
- ).revertedWith('Initializable: contract is not initializing');
- });
- });
-
- describe('setAavePool()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const {
- depositVaultWithAave,
- owner,
- regularAccounts,
- stableCoins,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
-
- await setAavePoolTest(
- { depositVaultWithAave, owner },
- stableCoins.usdc.address,
- aavePoolMock.address,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: zero address', async () => {
- const { depositVaultWithAave, owner, stableCoins } = await loadFixture(
- defaultDeploy,
- );
-
- await setAavePoolTest(
- { depositVaultWithAave, owner },
- stableCoins.usdc.address,
- ethers.constants.AddressZero,
- {
- revertMessage: 'zero address',
- },
- );
- });
-
- it('should fail: token not in pool', async () => {
- const { depositVaultWithAave, owner, stableCoins, aavePoolMock } =
- await loadFixture(defaultDeploy);
-
- await setAavePoolTest(
- { depositVaultWithAave, owner },
- stableCoins.dai.address,
- aavePoolMock.address,
- {
- revertMessage: 'DVA: token not in pool',
- },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, owner, stableCoins, aavePoolMock } =
- await loadFixture(defaultDeploy);
-
- await setAavePoolTest(
- { depositVaultWithAave, owner },
- stableCoins.usdc.address,
- aavePoolMock.address,
- );
- });
- });
-
- describe('removeAavePool()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, owner, regularAccounts, stableCoins } =
- await loadFixture(defaultDeploy);
-
- await removeAavePoolTest(
- { depositVaultWithAave, owner },
- stableCoins.usdc.address,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: pool not set', async () => {
- const { depositVaultWithAave, owner, stableCoins } = await loadFixture(
- defaultDeploy,
- );
-
- await removeAavePoolTest(
- { depositVaultWithAave, owner },
- stableCoins.dai.address,
- {
- revertMessage: 'DVA: pool not set',
- },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, owner, stableCoins, aavePoolMock } =
- await loadFixture(defaultDeploy);
-
- await setAavePoolTest(
- { depositVaultWithAave, owner },
- stableCoins.usdc.address,
- aavePoolMock.address,
- );
-
- await removeAavePoolTest(
- { depositVaultWithAave, owner },
- stableCoins.usdc.address,
- );
- });
- });
-
- describe('setAaveDepositsEnabled()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, owner, regularAccounts } =
- await loadFixture(defaultDeploy);
-
- await setAaveDepositsEnabledTest({ depositVaultWithAave, owner }, true, {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
+ await validateImplementation(DepositVaultWithAave__factory);
+ },
+ {
+ deployUninitialized: (fixture) =>
+ new DepositVaultWithAaveTest__factory(fixture.owner).deploy(),
+ initialize: async (fixture, params, opt) => {
+ await initializeDvWithAave(
+ { ...baseInitParamsDv(fixture), ...params },
+ opt?.contract,
+ opt,
+ );
+ },
+ },
+ async (defaultDeploy) => {
+ describe('DepositVaultWithAave', () => {
+ describe('setAavePool()', () => {
+ it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
+ const {
+ depositVaultWithAave,
+ owner,
+ regularAccounts,
+ stableCoins,
+ aavePoolMock,
+ } = await loadFixture(defaultDeploy);
+
+ await setAavePoolTest(
+ { depositVaultWithAave, owner },
+ stableCoins.usdc.address,
+ aavePoolMock.address,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('should fail: zero address', async () => {
+ const { depositVaultWithAave, owner, stableCoins } =
+ await loadFixture(defaultDeploy);
+
+ await setAavePoolTest(
+ { depositVaultWithAave, owner },
+ stableCoins.usdc.address,
+ ethers.constants.AddressZero,
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidAddress',
+ },
+ },
+ );
+ });
+
+ it('should fail: token not in pool', async () => {
+ const { depositVaultWithAave, owner, stableCoins, aavePoolMock } =
+ await loadFixture(defaultDeploy);
+
+ await setAavePoolTest(
+ { depositVaultWithAave, owner },
+ stableCoins.dai.address,
+ aavePoolMock.address,
+ {
+ revertCustomError: {
+ customErrorName: 'TokenNotInPool',
+ },
+ },
+ );
+ });
+
+ it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
+ const { depositVaultWithAave, owner, stableCoins, aavePoolMock } =
+ await loadFixture(defaultDeploy);
+
+ await setAavePoolTest(
+ { depositVaultWithAave, owner },
+ stableCoins.usdc.address,
+ aavePoolMock.address,
+ );
+ });
});
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, owner } = await loadFixture(defaultDeploy);
-
- await setAaveDepositsEnabledTest({ depositVaultWithAave, owner }, true);
- });
-
- it('toggle on and off', async () => {
- const { depositVaultWithAave, owner } = await loadFixture(defaultDeploy);
-
- await setAaveDepositsEnabledTest({ depositVaultWithAave, owner }, true);
-
- await setAaveDepositsEnabledTest({ depositVaultWithAave, owner }, false);
- });
- });
-
- describe('setAutoInvestFallbackEnabled()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, owner, regularAccounts } =
- await loadFixture(defaultDeploy);
-
- await setAutoInvestFallbackEnabledAaveTest(
- { depositVaultWithAave, owner },
- true,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, owner } = await loadFixture(defaultDeploy);
-
- await setAutoInvestFallbackEnabledAaveTest(
- { depositVaultWithAave, owner },
- true,
- );
- });
-
- it('toggle on and off', async () => {
- const { depositVaultWithAave, owner } = await loadFixture(defaultDeploy);
- await setAutoInvestFallbackEnabledAaveTest(
- { depositVaultWithAave, owner },
- true,
- );
-
- await setAutoInvestFallbackEnabledAaveTest(
- { depositVaultWithAave, owner },
- false,
- );
- });
- });
-
- describe('setMinAmount()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10, {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
+ describe('removeAavePool()', () => {
+ it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
+ const { depositVaultWithAave, owner, regularAccounts, stableCoins } =
+ await loadFixture(defaultDeploy);
+
+ await removeAavePoolTest(
+ { depositVaultWithAave, owner },
+ stableCoins.usdc.address,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('should fail: pool not set', async () => {
+ const { depositVaultWithAave, owner, stableCoins } =
+ await loadFixture(defaultDeploy);
+
+ await removeAavePoolTest(
+ { depositVaultWithAave, owner },
+ stableCoins.dai.address,
+ {
+ revertCustomError: {
+ customErrorName: 'PoolNotSet',
+ },
+ },
+ );
+ });
+
+ it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
+ const { depositVaultWithAave, owner, stableCoins, aavePoolMock } =
+ await loadFixture(defaultDeploy);
+
+ await setAavePoolTest(
+ { depositVaultWithAave, owner },
+ stableCoins.usdc.address,
+ aavePoolMock.address,
+ );
+
+ await removeAavePoolTest(
+ { depositVaultWithAave, owner },
+ stableCoins.usdc.address,
+ );
+ });
});
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, owner } = await loadFixture(defaultDeploy);
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
- });
- });
-
- describe('setMinMTokenAmountForFirstDeposit()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
-
- await setMinAmountToDepositTest(
- { depositVault: depositVaultWithAave, owner },
- 10,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, owner } = await loadFixture(defaultDeploy);
-
- await setMinAmountToDepositTest(
- { depositVault: depositVaultWithAave, owner },
- 10,
- );
- });
- });
-
- describe('setVariabilityTolerance()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
-
- await setVariabilityToleranceTest(
- { vault: depositVaultWithAave, owner },
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, owner } = await loadFixture(defaultDeploy);
-
- await setVariabilityToleranceTest(
- { vault: depositVaultWithAave, owner },
- 100,
- );
- });
- });
-
- describe('setInstantDailyLimit()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await setInstantDailyLimitTest(
- { vault: depositVaultWithAave, owner },
- 10,
- { from: regularAccounts[0], revertMessage: 'WMAC: hasnt role' },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, owner } = await loadFixture(defaultDeploy);
- await setInstantDailyLimitTest(
- { vault: depositVaultWithAave, owner },
- 10,
- );
- });
- });
-
- describe('addPaymentToken()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const {
- depositVaultWithAave,
- regularAccounts,
- owner,
- stableCoins,
- dataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- constants.MaxUint256,
- { from: regularAccounts[0], revertMessage: 'WMAC: hasnt role' },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- });
- });
-
- describe('removePaymentToken()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, regularAccounts, owner, stableCoins } =
- await loadFixture(defaultDeploy);
- await removePaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- { from: regularAccounts[0], revertMessage: 'WMAC: hasnt role' },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await removePaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- );
- });
- });
-
- describe('addWaivedFeeAccount()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: depositVaultWithAave, owner },
- regularAccounts[0].address,
- { from: regularAccounts[0], revertMessage: 'WMAC: hasnt role' },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: depositVaultWithAave, owner },
- regularAccounts[0].address,
- );
- });
- });
-
- describe('removeWaivedFeeAccount()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await removeWaivedFeeAccountTest(
- { vault: depositVaultWithAave, owner },
- regularAccounts[0].address,
- { from: regularAccounts[0], revertMessage: 'WMAC: hasnt role' },
- );
- });
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: depositVaultWithAave, owner },
- regularAccounts[0].address,
- );
- await removeWaivedFeeAccountTest(
- { vault: depositVaultWithAave, owner },
- regularAccounts[0].address,
- );
- });
- });
-
- describe('setFee()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await setInstantFeeTest({ vault: depositVaultWithAave, owner }, 100, {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
+ describe('setAaveDepositsEnabled()', () => {
+ it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
+ const { depositVaultWithAave, owner, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await setAaveDepositsEnabledTest(
+ { depositVaultWithAave, owner },
+ true,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
+ const { depositVaultWithAave, owner } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await setAaveDepositsEnabledTest(
+ { depositVaultWithAave, owner },
+ true,
+ );
+ });
+
+ it('toggle on and off', async () => {
+ const { depositVaultWithAave, owner } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await setAaveDepositsEnabledTest(
+ { depositVaultWithAave, owner },
+ true,
+ );
+
+ await setAaveDepositsEnabledTest(
+ { depositVaultWithAave, owner },
+ false,
+ );
+ });
});
- });
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, owner } = await loadFixture(defaultDeploy);
- await setInstantFeeTest({ vault: depositVaultWithAave, owner }, 100);
- });
- });
-
- describe('withdrawToken()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, regularAccounts, owner, stableCoins } =
- await loadFixture(defaultDeploy);
- await withdrawTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc.address,
- 0,
- owner,
- { from: regularAccounts[0], revertMessage: 'WMAC: hasnt role' },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, owner, stableCoins } = await loadFixture(
- defaultDeploy,
- );
- await mintToken(stableCoins.usdc, depositVaultWithAave, 1);
- await withdrawTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- 1,
- owner,
- );
- });
- });
-
- describe('changeTokenAllowance()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, regularAccounts, owner, stableCoins } =
- await loadFixture(defaultDeploy);
- await changeTokenAllowanceTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc.address,
- 100,
- { from: regularAccounts[0], revertMessage: 'WMAC: hasnt role' },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc.address,
- parseUnits('200'),
- );
- });
- });
-
- describe('changeTokenFee()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, regularAccounts, owner, stableCoins } =
- await loadFixture(defaultDeploy);
- await changeTokenFeeTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc.address,
- 100,
- { from: regularAccounts[0], revertMessage: 'WMAC: hasnt role' },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithAave, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenFeeTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc.address,
- 100,
- );
- });
- });
-
- describe('freeFromMinAmount()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const { depositVaultWithAave, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- depositVaultWithAave
- .connect(regularAccounts[0])
- .freeFromMinAmount(regularAccounts[1].address, true),
- ).to.be.revertedWith('WMAC: hasnt role');
- });
- it('should not fail', async () => {
- const { depositVaultWithAave, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- depositVaultWithAave.freeFromMinAmount(
- regularAccounts[0].address,
- true,
- ),
- ).to.not.reverted;
-
- expect(
- await depositVaultWithAave.isFreeFromMinAmount(
- regularAccounts[0].address,
- ),
- ).to.eq(true);
- });
- it('should fail: already in list', async () => {
- const { depositVaultWithAave, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- depositVaultWithAave.freeFromMinAmount(
- regularAccounts[0].address,
- true,
- ),
- ).to.not.reverted;
-
- expect(
- await depositVaultWithAave.isFreeFromMinAmount(
- regularAccounts[0].address,
- ),
- ).to.eq(true);
-
- await expect(
- depositVaultWithAave.freeFromMinAmount(
- regularAccounts[0].address,
- true,
- ),
- ).to.revertedWith('DV: already free');
- });
- });
-
- describe('depositInstant()', async () => {
- it('should fail: when there is no token in vault', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithAave,
- 100,
- );
-
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'MV: token not exists',
- },
- );
- });
-
- it('should fail: when function paused', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithAave,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
-
- await pauseVault(depositVaultWithAave, {
- from: owner,
+ describe('setAutoInvestFallbackEnabled()', () => {
+ it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
+ const { depositVaultWithAave, owner, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await setAutoInvestFallbackEnabledAaveTest(
+ { depositVaultWithAave, owner },
+ true,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
+ const { depositVaultWithAave, owner } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await setAutoInvestFallbackEnabledAaveTest(
+ { depositVaultWithAave, owner },
+ true,
+ );
+ });
+
+ it('toggle on and off', async () => {
+ const { depositVaultWithAave, owner } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await setAutoInvestFallbackEnabledAaveTest(
+ { depositVaultWithAave, owner },
+ true,
+ );
+
+ await setAutoInvestFallbackEnabledAaveTest(
+ { depositVaultWithAave, owner },
+ false,
+ );
+ });
});
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: paused',
- },
- );
- });
-
- it('should fail: user in blacklist', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- accessControl,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithAave,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
-
- await blackList(
- { blacklistable: depositVaultWithAave, accessControl, owner },
- regularAccounts[0],
- );
-
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: user in sanctions list', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- mockedSanctionsList,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithAave,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- regularAccounts[0],
- );
-
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('should fail: when trying to deposit 0 amount', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- },
- stableCoins.dai,
- 0,
- {
- revertMessage: 'DV: invalid amount',
- },
- );
- });
-
- it('should fail: when rounding is invalid', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- },
- stableCoins.dai,
- 100.0000000001,
- {
- revertMessage: 'MV: invalid rounding',
- },
- );
- });
-
- it('should fail: call with insufficient allowance', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'ERC20: insufficient allowance',
- },
- );
- });
-
- it('should fail: call with insufficient balance', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, stableCoins.dai, depositVaultWithAave, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'ERC20: transfer amount exceeds balance',
- },
- );
- });
-
- it('should fail: dataFeed rate 0 ', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- dataFeed,
- mockedAggregator,
- mTokenToUsdDataFeed,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, stableCoins.dai, depositVaultWithAave, 10);
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await mintToken(stableCoins.dai, owner, 100_000);
- await setRoundData({ mockedAggregator }, 0);
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'DF: feed is deprecated',
- },
- );
- });
-
- it('should fail: call for amount < minAmountToDepositTest', async () => {
- const {
- depositVaultWithAave,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(stableCoins.dai, owner, 100_000);
- await approveBase18(
- owner,
- stableCoins.dai,
- depositVaultWithAave,
- 100_000,
- );
-
- await setMinAmountToDepositTest(
- { depositVault: depositVaultWithAave, owner },
- 100_000,
- );
- await setInstantDailyLimitTest(
- { vault: depositVaultWithAave, owner },
- 150_000,
- );
-
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'DV: mint amount < min',
- },
- );
- });
-
- it('should fail: call for amount < minAmount', async () => {
- const {
- depositVaultWithAave,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(stableCoins.dai, owner, 100_000);
- await approveBase18(
- owner,
- stableCoins.dai,
- depositVaultWithAave,
- 100_000,
- );
-
- await setMinAmountToDepositTest(
- { depositVault: depositVaultWithAave, owner },
- 100_000,
- );
- await setInstantDailyLimitTest(
- { vault: depositVaultWithAave, owner },
- 150_000,
- );
-
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- },
- stableCoins.dai,
- 99,
- {
- revertMessage: 'DV: mToken amount < min',
- },
- );
- });
-
- it('should fail: if exceed allowance of deposit for token', async () => {
- const {
- depositVaultWithAave,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(stableCoins.dai, owner, 100_000);
- await changeTokenAllowanceTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.dai.address,
- 100,
- );
- await approveBase18(
- owner,
- stableCoins.dai,
- depositVaultWithAave,
- 100_000,
- );
-
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'MV: exceed allowance',
- },
- );
- });
-
- it('should fail: if mint limit exceeded', async () => {
- const {
- depositVaultWithAave,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(stableCoins.dai, owner, 100_000);
- await setInstantDailyLimitTest(
- { vault: depositVaultWithAave, owner },
- 1000,
- );
-
- await approveBase18(
- owner,
- stableCoins.dai,
- depositVaultWithAave,
- 100_000,
- );
-
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'MV: exceed limit',
- },
- );
- });
-
- it('should fail: if min receive amount greater then actual', async () => {
- const {
- depositVaultWithAave,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(stableCoins.dai, owner, 100_000);
-
- await approveBase18(
- owner,
- stableCoins.dai,
- depositVaultWithAave,
- 100_000,
- );
-
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- minAmount: parseUnits('100000'),
- },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'DV: minReceiveAmount > actual',
- },
- );
- });
-
- it('should fail: if some fee = 100%', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithAave, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.dai,
- dataFeed.address,
- 10000,
- true,
- );
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'DV: mToken amount < min',
- },
- );
-
- await removePaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.dai,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setInstantFeeTest({ vault: depositVaultWithAave, owner }, 10000);
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- },
- stableCoins.dai,
- 100,
- { revertMessage: 'DV: mToken amount < min' },
- );
- });
-
- it('deposit 100 USDC when aaveDepositsEnabled is true', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
-
- await setAaveDepositsEnabledTest({ depositVaultWithAave, owner }, true);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithAave,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
-
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('when aaveDepositsEnabled is false, normal DV flow', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithAave,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
-
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- expectedAaveDeposited: false,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit with waived fee', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
-
- await setAaveDepositsEnabledTest({ depositVaultWithAave, owner }, true);
-
- await addWaivedFeeAccountTest(
- { vault: depositVaultWithAave, owner },
- regularAccounts[0].address,
- );
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithAave,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
-
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- waivedFee: true,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit 100 DAI with aave enabled (non-stablecoin feed)', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- aavePoolMock,
- aUSDC,
- } = await loadFixture(defaultDeploy);
-
- const aDAI = aUSDC; // reuse the aToken mock for DAI in tests
- await aavePoolMock.setReserveAToken(
- stableCoins.dai.address,
- aDAI.address,
- );
-
- await setAavePoolTest(
- { depositVaultWithAave, owner },
- stableCoins.dai.address,
- aavePoolMock.address,
- );
-
- await setAaveDepositsEnabledTest({ depositVaultWithAave, owner }, true);
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVaultWithAave,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- false,
- );
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
-
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit with greenlist enabled and user in greenlist', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- greenListableTester,
- mTokenToUsdDataFeed,
- accessControl,
- regularAccounts,
- dataFeed,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
-
- await depositVaultWithAave.setGreenlistEnable(true);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await setAaveDepositsEnabledTest({ depositVaultWithAave, owner }, true);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithAave,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
-
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit with custom recipient, aaveDepositsEnabled is true', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
-
- await setAaveDepositsEnabledTest({ depositVaultWithAave, owner }, true);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithAave,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
-
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- customRecipient: regularAccounts[1],
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit with custom recipient, aaveDepositsEnabled is false', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithAave,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
-
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- expectedAaveDeposited: false,
- customRecipient: regularAccounts[1],
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('should fail: greenlist enabled and user not in greenlist', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
-
- await depositVaultWithAave.setGreenlistEnable(true);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithAave,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
-
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- },
- );
- });
-
- it('should fail: first deposit mint amount below configured minimum', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
-
- await setAaveDepositsEnabledTest({ depositVaultWithAave, owner }, true);
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 0);
- await setMinAmountToDepositTest(
- { depositVault: depositVaultWithAave, owner },
- 200,
- );
-
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithAave, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- },
- stableCoins.usdc,
- 100,
- {
- revertMessage: 'DV: mint amount < min',
- },
- );
- });
-
- it('aaveDepositsEnabled but no pool for token: fallback to normal flow', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- aavePoolMock,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
-
- await setAaveDepositsEnabledTest({ depositVaultWithAave, owner }, true);
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVaultWithAave,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- false,
- );
-
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- expectedAaveDeposited: false,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('aaveDepositsEnabled, pool configured but supply reverts, fallback enabled: fallback to normal flow', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- aavePoolMock,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
-
- await setAaveDepositsEnabledTest({ depositVaultWithAave, owner }, true);
- await setAutoInvestFallbackEnabledAaveTest(
- { depositVaultWithAave, owner },
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithAave,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await aavePoolMock.setShouldRevertSupply(true);
-
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- expectedAaveDeposited: false,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('should fail: aaveDepositsEnabled, pool configured but supply reverts, fallback disabled', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- aavePoolMock,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
-
- await setAaveDepositsEnabledTest({ depositVaultWithAave, owner }, true);
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithAave,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await aavePoolMock.setShouldRevertSupply(true);
-
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- expectedAaveDeposited: false,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'DVA: auto-invest failed',
- },
- );
- });
-
- it('should fail: greenlist enabled and recipient not in greenlist (custom recipient overload)', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- customRecipient,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await depositVaultWithAave.setGreenlistEnable(true);
-
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: recipient in blacklist (custom recipient overload)', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- customRecipient,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- customRecipient,
- );
-
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: recipient in sanctions list (custom recipient overload)', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- customRecipient,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- customRecipient,
- );
-
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('should fail: when function paused (custom recipient overload)', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- customRecipient,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVaultWithAave,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector(
- 'depositInstant(address,uint256,uint256,bytes32,address)',
- );
- await pauseVaultFn(depositVaultWithAave, selector);
- await depositInstantWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- customRecipient,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
- });
-
- describe('depositRequest()', () => {
- it('deposit request 100 USDC', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithAave,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit request 100 USDC with aave auto-invest', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
-
- await setAaveDepositsEnabledTest({ depositVaultWithAave, owner }, true);
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithAave,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await depositRequestWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit request with aave auto-invest, supply reverts, fallback enabled: fallback to normal flow', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
-
- await setAaveDepositsEnabledTest({ depositVaultWithAave, owner }, true);
- await setAutoInvestFallbackEnabledAaveTest(
- { depositVaultWithAave, owner },
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithAave,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await aavePoolMock.setShouldRevertSupply(true);
-
- await depositRequestWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- expectedAaveDeposited: false,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('should fail: deposit request with aave auto-invest, supply reverts, fallback disabled', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
-
- await setAaveDepositsEnabledTest({ depositVaultWithAave, owner }, true);
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithAave,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await aavePoolMock.setShouldRevertSupply(true);
-
- await depositRequestWithAaveTest(
- {
- depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- aavePoolMock,
- expectedAaveDeposited: false,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'DVA: auto-invest failed',
- },
- );
- });
- });
-
- describe('approveRequest()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const {
- owner,
- regularAccounts,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithAave, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
-
- const request = await depositRequestTest(
- {
- depositVault: depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
-
- await approveRequestTest(
- {
- depositVault: depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- request.requestId!,
- request.rate!,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('approve request: happy path', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithAave, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
-
- const request = await depositRequestTest(
- {
- depositVault: depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
-
- await approveRequestTest(
- {
- depositVault: depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- request.requestId!,
- request.rate!,
- );
- });
- });
-
- describe('safeApproveRequest()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const {
- owner,
- regularAccounts,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithAave, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
-
- const request = await depositRequestTest(
- {
- depositVault: depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
-
- await safeApproveRequestTest(
- {
- depositVault: depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- request.requestId!,
- request.rate!,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('safe approve request: happy path', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithAave, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
-
- const request = await depositRequestTest(
- {
- depositVault: depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
-
- await safeApproveRequestTest(
- {
- depositVault: depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- request.requestId!,
- request.rate!,
- );
- });
- });
-
- describe('safeBulkApproveRequest()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const {
- owner,
- regularAccounts,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithAave, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
-
- const request = await depositRequestTest(
- {
- depositVault: depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
-
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: request.requestId! }],
- request.rate!,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('safe bulk approve request: happy path', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithAave, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
-
- const request = await depositRequestTest(
- {
- depositVault: depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
-
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: request.requestId! }],
- request.rate!,
- );
- });
- });
-
- describe('rejectRequest()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const {
- owner,
- regularAccounts,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithAave, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
-
- const request = await depositRequestTest(
- {
- depositVault: depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
-
- await rejectRequestTest(
- {
- depositVault: depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- request.requestId!,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('reject request: happy path', async () => {
- const {
- owner,
- depositVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithAave, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
-
- const request = await depositRequestTest(
- {
- depositVault: depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
+ describe('depositInstant()', () => {
+ it('deposit 100 USDC when aaveDepositsEnabled is true', async () => {
+ const {
+ owner,
+ depositVaultWithAave,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ aavePoolMock,
+ } = await loadFixture(defaultDeploy);
+
+ await setAaveDepositsEnabledTest(
+ { depositVaultWithAave, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithAave,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithAave, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
+
+ await depositInstantWithAaveTest(
+ {
+ depositVaultWithAave,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ aavePoolMock,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('when aaveDepositsEnabled is false, normal DV flow', async () => {
+ const {
+ owner,
+ depositVaultWithAave,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ aavePoolMock,
+ } = await loadFixture(defaultDeploy);
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithAave,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithAave, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
+
+ await depositInstantWithAaveTest(
+ {
+ depositVaultWithAave,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ aavePoolMock,
+ expectedAaveDeposited: false,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('deposit with custom recipient, aaveDepositsEnabled is true', async () => {
+ const {
+ owner,
+ depositVaultWithAave,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ aavePoolMock,
+ } = await loadFixture(defaultDeploy);
+
+ await setAaveDepositsEnabledTest(
+ { depositVaultWithAave, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithAave,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithAave, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
+
+ await depositInstantWithAaveTest(
+ {
+ depositVaultWithAave,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ aavePoolMock,
+ customRecipient: regularAccounts[1],
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('aaveDepositsEnabled but no pool for token: fallback to normal flow', async () => {
+ const {
+ owner,
+ depositVaultWithAave,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ aavePoolMock,
+ regularAccounts,
+ } = await loadFixture(defaultDeploy);
+
+ await setAaveDepositsEnabledTest(
+ { depositVaultWithAave, owner },
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVaultWithAave,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithAave, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ false,
+ );
+
+ await depositInstantWithAaveTest(
+ {
+ depositVaultWithAave,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ aavePoolMock,
+ expectedAaveDeposited: false,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('aaveDepositsEnabled, pool configured but supply reverts, fallback enabled: fallback to normal flow', async () => {
+ const {
+ owner,
+ depositVaultWithAave,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ aavePoolMock,
+ regularAccounts,
+ } = await loadFixture(defaultDeploy);
+
+ await setAaveDepositsEnabledTest(
+ { depositVaultWithAave, owner },
+ true,
+ );
+ await setAutoInvestFallbackEnabledAaveTest(
+ { depositVaultWithAave, owner },
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithAave,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithAave, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await aavePoolMock.setShouldRevertSupply(true);
+
+ await depositInstantWithAaveTest(
+ {
+ depositVaultWithAave,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ aavePoolMock,
+ expectedAaveDeposited: false,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: aaveDepositsEnabled, pool configured but supply reverts, fallback disabled', async () => {
+ const {
+ owner,
+ depositVaultWithAave,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ aavePoolMock,
+ regularAccounts,
+ } = await loadFixture(defaultDeploy);
+
+ await setAaveDepositsEnabledTest(
+ { depositVaultWithAave, owner },
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithAave,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithAave, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await aavePoolMock.setShouldRevertSupply(true);
+
+ await depositInstantWithAaveTest(
+ {
+ depositVaultWithAave,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ aavePoolMock,
+ expectedAaveDeposited: false,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'AutoInvestFailed',
+ },
+ },
+ );
+ });
+ });
- await rejectRequestTest(
- {
- depositVault: depositVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- request.requestId!,
- );
+ describe('depositRequest()', () => {
+ it('deposit request 100 USDC with aave auto-invest', async () => {
+ const {
+ owner,
+ depositVaultWithAave,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ aavePoolMock,
+ } = await loadFixture(defaultDeploy);
+
+ await setAaveDepositsEnabledTest(
+ { depositVaultWithAave, owner },
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithAave,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithAave, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await depositRequestWithAaveTest(
+ {
+ depositVaultWithAave,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ aavePoolMock,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('deposit request with aave auto-invest, supply reverts, fallback enabled: fallback to normal flow', async () => {
+ const {
+ owner,
+ depositVaultWithAave,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ aavePoolMock,
+ } = await loadFixture(defaultDeploy);
+
+ await setAaveDepositsEnabledTest(
+ { depositVaultWithAave, owner },
+ true,
+ );
+ await setAutoInvestFallbackEnabledAaveTest(
+ { depositVaultWithAave, owner },
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithAave,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithAave, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await aavePoolMock.setShouldRevertSupply(true);
+
+ await depositRequestWithAaveTest(
+ {
+ depositVaultWithAave,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ aavePoolMock,
+ expectedAaveDeposited: false,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: deposit request with aave auto-invest, supply reverts, fallback disabled', async () => {
+ const {
+ owner,
+ depositVaultWithAave,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ aavePoolMock,
+ } = await loadFixture(defaultDeploy);
+
+ await setAaveDepositsEnabledTest(
+ { depositVaultWithAave, owner },
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithAave, owner }, 10);
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithAave,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithAave, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await aavePoolMock.setShouldRevertSupply(true);
+
+ await depositRequestWithAaveTest(
+ {
+ depositVaultWithAave,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ aavePoolMock,
+ expectedAaveDeposited: false,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'AutoInvestFailed',
+ },
+ },
+ );
+ });
+ });
});
- });
-});
+ },
+);
diff --git a/test/unit/DepositVaultWithMToken.test.ts b/test/unit/DepositVaultWithMToken.test.ts
index 788e6f4a..1d2b0437 100644
--- a/test/unit/DepositVaultWithMToken.test.ts
+++ b/test/unit/DepositVaultWithMToken.test.ts
@@ -1,19 +1,26 @@
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
+import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { expect } from 'chai';
import { constants } from 'ethers';
-import { parseUnits } from 'ethers/lib/utils';
import { ethers } from 'hardhat';
-import { encodeFnSelector } from '../../helpers/utils';
-import { ManageableVaultTester__factory } from '../../typechain-types';
-import { acErrors, blackList, greenList } from '../common/ac.helpers';
+import {
+ baseInitParamsDv,
+ depositVaultSuits,
+} from './suits/deposit-vault.suits';
+
+import {
+ DepositVaultWithMToken__factory,
+ DepositVaultWithMTokenTest,
+ DepositVaultWithMTokenTest__factory,
+} from '../../typechain-types';
+import { acErrors } from '../common/ac.helpers';
import {
approveBase18,
+ InitializeParamCase,
mintToken,
- pauseVault,
- pauseVaultFn,
+ validateImplementation,
} from '../common/common.helpers';
-import { setRoundData } from '../common/data-feed.helpers';
import {
depositInstantWithMTokenTest,
depositRequestWithMTokenTest,
@@ -21,2628 +28,744 @@ import {
setMTokenDepositsEnabledTest,
setMTokenDepositVaultTest,
} from '../common/deposit-vault-mtoken.helpers';
-import {
- approveRequestTest,
- depositRequestTest,
- rejectRequestTest,
- safeApproveRequestTest,
- safeBulkApproveRequestTest,
-} from '../common/deposit-vault.helpers';
import { defaultDeploy } from '../common/fixtures';
import {
addPaymentTokenTest,
- addWaivedFeeAccountTest,
- changeTokenAllowanceTest,
- removePaymentTokenTest,
- removeWaivedFeeAccountTest,
- setInstantFeeTest,
- setInstantDailyLimitTest,
- setMinAmountToDepositTest,
setMinAmountTest,
- setVariabilityToleranceTest,
- withdrawTest,
- changeTokenFeeTest,
+ setWaivedFeeAccountTest,
} from '../common/manageable-vault.helpers';
-import { sanctionUser } from '../common/with-sanctions-list.helpers';
-
-describe('DepositVaultWithMToken', function () {
- it('deployment', async () => {
- const {
- depositVaultWithMToken,
- depositVault,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- roles,
- } = await loadFixture(defaultDeploy);
-
- expect(await depositVaultWithMToken.mToken()).eq(mTBILL.address);
- expect(await depositVaultWithMToken.paused()).eq(false);
- expect(await depositVaultWithMToken.tokensReceiver()).eq(
- tokensReceiver.address,
- );
- expect(await depositVaultWithMToken.feeReceiver()).eq(feeReceiver.address);
- expect(await depositVaultWithMToken.ONE_HUNDRED_PERCENT()).eq('10000');
- expect(await depositVaultWithMToken.minMTokenAmountForFirstDeposit()).eq(
- '0',
- );
- expect(await depositVaultWithMToken.minAmount()).eq(parseUnits('100'));
- expect(await depositVaultWithMToken.instantFee()).eq('100');
- expect(await depositVaultWithMToken.instantDailyLimit()).eq(
- parseUnits('100000'),
- );
- expect(await depositVaultWithMToken.mTokenDataFeed()).eq(
- mTokenToUsdDataFeed.address,
- );
- expect(await depositVaultWithMToken.variationTolerance()).eq(1);
- expect(await depositVaultWithMToken.vaultRole()).eq(
- roles.tokenRoles.mTBILL.depositVaultAdmin,
- );
- expect(await depositVaultWithMToken.MANUAL_FULLFILMENT_TOKEN()).eq(
- ethers.constants.AddressZero,
- );
+import {
+ initializeDvWithMToken,
+ InitializerParamsDvWithMToken,
+} from '../common/vault-initializer.helpers';
+
+const baseInitParamsDvWithMToken = (
+ fixture: Parameters[0],
+): InitializerParamsDvWithMToken => ({
+ ...baseInitParamsDv(fixture),
+ depositVault: fixture.depositVault,
+});
+
+const dvWithMTokenInitializeParamCases: InitializeParamCase[] =
+ [
+ {
+ title: 'depositVault is zero address',
+ params: { depositVault: constants.AddressZero },
+ revertCustomError: {
+ customErrorName: 'InvalidAddress',
+ args: [constants.AddressZero],
+ },
+ },
+ ];
+
+depositVaultSuits(
+ 'DepositVaultWithMToken',
+ defaultDeploy,
+ {
+ createNew: async (owner: SignerWithAddress) =>
+ new DepositVaultWithMTokenTest__factory(owner).deploy(),
+ key: 'depositVaultWithMToken',
+ },
+ async (fixture) => {
+ const { depositVaultWithMToken, depositVault } = fixture;
expect(await depositVaultWithMToken.mTokenDepositVault()).eq(
depositVault.address,
);
expect(await depositVaultWithMToken.mTokenDepositsEnabled()).eq(false);
- });
-
- describe('initialization', () => {
- it('should fail: call initialize() when already initialized', async () => {
- const { depositVaultWithMToken } = await loadFixture(defaultDeploy);
-
- await expect(
- depositVaultWithMToken[
- 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,uint256,uint256,address)'
- ](
- constants.AddressZero,
- {
- mToken: constants.AddressZero,
- mTokenDataFeed: constants.AddressZero,
- },
- {
- feeReceiver: constants.AddressZero,
- tokensReceiver: constants.AddressZero,
- },
- {
- instantFee: 0,
- instantDailyLimit: 0,
- },
- constants.AddressZero,
- 0,
- 0,
- 0,
- 0,
- constants.AddressZero,
- ),
- ).revertedWith('Initializable: contract is already initialized');
- });
-
- it('should fail: call with initializing == false', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initializeWithoutInitializer(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- ),
- ).revertedWith('Initializable: contract is not initializing');
- });
- });
-
- describe('setMTokenDepositVault()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, owner, regularAccounts, depositVault } =
- await loadFixture(defaultDeploy);
-
- await setMTokenDepositVaultTest(
- { depositVaultWithMToken, owner },
- depositVault.address,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: zero address', async () => {
- const { depositVaultWithMToken, owner } = await loadFixture(
- defaultDeploy,
- );
-
- await setMTokenDepositVaultTest(
- { depositVaultWithMToken, owner },
- ethers.constants.AddressZero,
- {
- revertMessage: 'zero address',
- },
- );
- });
-
- it('should fail: already set to same address', async () => {
- const { depositVaultWithMToken, owner, depositVault } = await loadFixture(
- defaultDeploy,
- );
-
- await setMTokenDepositVaultTest(
- { depositVaultWithMToken, owner },
- depositVault.address,
- {
- revertMessage: 'DVMT: already set',
- },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, owner, regularAccounts } =
- await loadFixture(defaultDeploy);
-
- await setMTokenDepositVaultTest(
- { depositVaultWithMToken, owner },
- regularAccounts[1].address,
- );
- });
- });
-
- describe('setMTokenDepositsEnabled()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, owner, regularAccounts } =
- await loadFixture(defaultDeploy);
-
- await setMTokenDepositsEnabledTest(
- { depositVaultWithMToken, owner },
- true,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, owner } = await loadFixture(
- defaultDeploy,
- );
-
- await setMTokenDepositsEnabledTest(
- { depositVaultWithMToken, owner },
- true,
- );
- });
-
- it('toggle on and off', async () => {
- const { depositVaultWithMToken, owner } = await loadFixture(
- defaultDeploy,
- );
-
- await setMTokenDepositsEnabledTest(
- { depositVaultWithMToken, owner },
- true,
- );
-
- await setMTokenDepositsEnabledTest(
- { depositVaultWithMToken, owner },
- false,
- );
- });
- });
-
- describe('setAutoInvestFallbackEnabled()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, owner, regularAccounts } =
- await loadFixture(defaultDeploy);
-
- await setAutoInvestFallbackEnabledMTokenTest(
- { depositVaultWithMToken, owner },
- true,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, owner } = await loadFixture(
- defaultDeploy,
- );
-
- await setAutoInvestFallbackEnabledMTokenTest(
- { depositVaultWithMToken, owner },
- true,
- );
- });
-
- it('toggle on and off', async () => {
- const { depositVaultWithMToken, owner } = await loadFixture(
- defaultDeploy,
- );
-
- await setAutoInvestFallbackEnabledMTokenTest(
- { depositVaultWithMToken, owner },
- true,
- );
-
- await setAutoInvestFallbackEnabledMTokenTest(
- { depositVaultWithMToken, owner },
- false,
- );
- });
- });
-
- describe('setMinAmount()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10, {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
+ await validateImplementation(DepositVaultWithMToken__factory);
+ },
+ {
+ deployUninitialized: (fixture) =>
+ new DepositVaultWithMTokenTest__factory(fixture.owner).deploy(),
+ initialize: async (fixture, params, opt) => {
+ await initializeDvWithMToken(
+ { ...baseInitParamsDvWithMToken(fixture), ...params },
+ opt?.contract as DepositVaultWithMTokenTest,
+ opt,
+ );
+ },
+ extraParamCases: dvWithMTokenInitializeParamCases,
+ },
+ async (defaultDeploy) => {
+ describe('DepositVaultWithMToken', function () {
+ describe('setMTokenDepositVault()', () => {
+ it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
+ const {
+ depositVaultWithMToken,
+ owner,
+ regularAccounts,
+ depositVault,
+ } = await loadFixture(defaultDeploy);
+
+ await setMTokenDepositVaultTest(
+ { depositVaultWithMToken, owner },
+ depositVault.address,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ },
+ );
+ });
+
+ it('should fail: zero address', async () => {
+ const { depositVaultWithMToken, owner } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await setMTokenDepositVaultTest(
+ { depositVaultWithMToken, owner },
+ ethers.constants.AddressZero,
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidAddress',
+ },
+ },
+ );
+ });
+
+ it('should fail: already set to same address', async () => {
+ const { depositVaultWithMToken, owner, depositVault } =
+ await loadFixture(defaultDeploy);
+
+ await setMTokenDepositVaultTest(
+ { depositVaultWithMToken, owner },
+ depositVault.address,
+ {
+ revertCustomError: {
+ customErrorName: 'SameAddressValue',
+ },
+ },
+ );
+ });
+
+ it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
+ const { depositVaultWithMToken, owner, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await setMTokenDepositVaultTest(
+ { depositVaultWithMToken, owner },
+ regularAccounts[1].address,
+ );
+ });
});
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, owner } = await loadFixture(
- defaultDeploy,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
- });
- });
-
- describe('setMinMTokenAmountForFirstDeposit()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
-
- await setMinAmountToDepositTest(
- { depositVault: depositVaultWithMToken, owner },
- 10,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, owner } = await loadFixture(
- defaultDeploy,
- );
- await setMinAmountToDepositTest(
- { depositVault: depositVaultWithMToken, owner },
- 10,
- );
- });
- });
-
- describe('setVariabilityTolerance()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
-
- await setVariabilityToleranceTest(
- { vault: depositVaultWithMToken, owner },
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, owner } = await loadFixture(
- defaultDeploy,
- );
-
- await setVariabilityToleranceTest(
- { vault: depositVaultWithMToken, owner },
- 100,
- );
- });
- });
-
- describe('setInstantDailyLimit()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await setInstantDailyLimitTest(
- { vault: depositVaultWithMToken, owner },
- 10,
- { from: regularAccounts[0], revertMessage: 'WMAC: hasnt role' },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, owner } = await loadFixture(
- defaultDeploy,
- );
- await setInstantDailyLimitTest(
- { vault: depositVaultWithMToken, owner },
- 10,
- );
- });
- });
-
- describe('addPaymentToken()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const {
- depositVaultWithMToken,
- regularAccounts,
- owner,
- stableCoins,
- dataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- constants.MaxUint256,
- { from: regularAccounts[0], revertMessage: 'WMAC: hasnt role' },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- });
- });
-
- describe('removePaymentToken()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, regularAccounts, owner, stableCoins } =
- await loadFixture(defaultDeploy);
- await removePaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- { from: regularAccounts[0], revertMessage: 'WMAC: hasnt role' },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await removePaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- );
- });
- });
-
- describe('addWaivedFeeAccount()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: depositVaultWithMToken, owner },
- regularAccounts[0].address,
- { from: regularAccounts[0], revertMessage: 'WMAC: hasnt role' },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: depositVaultWithMToken, owner },
- regularAccounts[0].address,
- );
- });
- });
-
- describe('removeWaivedFeeAccount()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await removeWaivedFeeAccountTest(
- { vault: depositVaultWithMToken, owner },
- regularAccounts[0].address,
- { from: regularAccounts[0], revertMessage: 'WMAC: hasnt role' },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: depositVaultWithMToken, owner },
- regularAccounts[0].address,
- );
- await removeWaivedFeeAccountTest(
- { vault: depositVaultWithMToken, owner },
- regularAccounts[0].address,
- );
- });
- });
-
- describe('setFee()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await setInstantFeeTest({ vault: depositVaultWithMToken, owner }, 100, {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
+ describe('setMTokenDepositsEnabled()', () => {
+ it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
+ const { depositVaultWithMToken, owner, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await setMTokenDepositsEnabledTest(
+ { depositVaultWithMToken, owner },
+ true,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ },
+ );
+ });
+
+ it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
+ const { depositVaultWithMToken, owner } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await setMTokenDepositsEnabledTest(
+ { depositVaultWithMToken, owner },
+ true,
+ );
+ });
+
+ it('toggle on and off', async () => {
+ const { depositVaultWithMToken, owner } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await setMTokenDepositsEnabledTest(
+ { depositVaultWithMToken, owner },
+ true,
+ );
+
+ await setMTokenDepositsEnabledTest(
+ { depositVaultWithMToken, owner },
+ false,
+ );
+ });
});
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, owner } = await loadFixture(
- defaultDeploy,
- );
- await setInstantFeeTest({ vault: depositVaultWithMToken, owner }, 100);
- });
- });
-
- describe('withdrawToken()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, regularAccounts, owner, stableCoins } =
- await loadFixture(defaultDeploy);
- await withdrawTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc.address,
- 0,
- owner,
- { from: regularAccounts[0], revertMessage: 'WMAC: hasnt role' },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, owner, stableCoins } = await loadFixture(
- defaultDeploy,
- );
- await mintToken(stableCoins.usdc, depositVaultWithMToken, 1);
- await withdrawTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- 1,
- owner,
- );
- });
- });
-
- describe('changeTokenAllowance()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, regularAccounts, owner, stableCoins } =
- await loadFixture(defaultDeploy);
- await changeTokenAllowanceTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc.address,
- 100,
- { from: regularAccounts[0], revertMessage: 'WMAC: hasnt role' },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc.address,
- parseUnits('200'),
- );
- });
- });
-
- describe('changeTokenFee()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, regularAccounts, owner, stableCoins } =
- await loadFixture(defaultDeploy);
- await changeTokenFeeTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc.address,
- 100,
- { from: regularAccounts[0], revertMessage: 'WMAC: hasnt role' },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMToken, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenFeeTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc.address,
- 100,
- );
- });
- });
-
- describe('freeFromMinAmount()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const { depositVaultWithMToken, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- depositVaultWithMToken
- .connect(regularAccounts[0])
- .freeFromMinAmount(regularAccounts[1].address, true),
- ).to.be.revertedWith('WMAC: hasnt role');
- });
- it('should not fail', async () => {
- const { depositVaultWithMToken, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- depositVaultWithMToken.freeFromMinAmount(
- regularAccounts[0].address,
- true,
- ),
- ).to.not.reverted;
-
- expect(
- await depositVaultWithMToken.isFreeFromMinAmount(
- regularAccounts[0].address,
- ),
- ).to.eq(true);
- });
- it('should fail: already in list', async () => {
- const { depositVaultWithMToken, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- depositVaultWithMToken.freeFromMinAmount(
- regularAccounts[0].address,
- true,
- ),
- ).to.not.reverted;
-
- expect(
- await depositVaultWithMToken.isFreeFromMinAmount(
- regularAccounts[0].address,
- ),
- ).to.eq(true);
-
- await expect(
- depositVaultWithMToken.freeFromMinAmount(
- regularAccounts[0].address,
- true,
- ),
- ).to.revertedWith('DV: already free');
- });
- });
-
- describe('depositInstant()', async () => {
- it('should fail: when there is no token in vault', async () => {
- const {
- owner,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMToken,
- 100,
- );
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'MV: token not exists',
- },
- );
- });
-
- it('should fail: when function paused', async () => {
- const {
- owner,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMToken,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
-
- await pauseVault(depositVaultWithMToken, {
- from: owner,
+ describe('setAutoInvestFallbackEnabled()', () => {
+ it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
+ const { depositVaultWithMToken, owner, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await setAutoInvestFallbackEnabledMTokenTest(
+ { depositVaultWithMToken, owner },
+ true,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ },
+ );
+ });
+
+ it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
+ const { depositVaultWithMToken, owner } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await setAutoInvestFallbackEnabledMTokenTest(
+ { depositVaultWithMToken, owner },
+ true,
+ );
+ });
+
+ it('toggle on and off', async () => {
+ const { depositVaultWithMToken, owner } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await setAutoInvestFallbackEnabledMTokenTest(
+ { depositVaultWithMToken, owner },
+ true,
+ );
+
+ await setAutoInvestFallbackEnabledMTokenTest(
+ { depositVaultWithMToken, owner },
+ false,
+ );
+ });
});
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: paused',
- },
- );
- });
-
- it('should fail: user in blacklist', async () => {
- const {
- owner,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMToken,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
-
- await blackList(
- { blacklistable: depositVaultWithMToken, accessControl, owner },
- regularAccounts[0],
- );
-
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: user in sanctions list', async () => {
- const {
- owner,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMToken,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- regularAccounts[0],
- );
-
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('should fail: when trying to deposit 0 amount', async () => {
- const {
- owner,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 0,
- {
- revertMessage: 'DV: invalid amount',
- },
- );
- });
-
- it('should fail: when rounding is invalid', async () => {
- const {
- owner,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100.0000000001,
- {
- revertMessage: 'MV: invalid rounding',
- },
- );
- });
-
- it('should fail: call with insufficient allowance', async () => {
- const {
- owner,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'ERC20: insufficient allowance',
- },
- );
- });
-
- it('should fail: call with insufficient balance', async () => {
- const {
- owner,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, stableCoins.dai, depositVaultWithMToken, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'ERC20: transfer amount exceeds balance',
- },
- );
- });
-
- it('should fail: dataFeed rate 0', async () => {
- const {
- owner,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- dataFeed,
- mockedAggregator,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, stableCoins.dai, depositVaultWithMToken, 10);
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await mintToken(stableCoins.dai, owner, 100_000);
- await setRoundData({ mockedAggregator }, 0);
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'DF: feed is deprecated',
- },
- );
- });
-
- it('should fail: call for amount < minAmountToDepositTest', async () => {
- const {
- depositVaultWithMToken,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(stableCoins.dai, owner, 100_000);
- await approveBase18(
- owner,
- stableCoins.dai,
- depositVaultWithMToken,
- 100_000,
- );
-
- await setMinAmountToDepositTest(
- { depositVault: depositVaultWithMToken, owner },
- 100_000,
- );
- await setInstantDailyLimitTest(
- { vault: depositVaultWithMToken, owner },
- 150_000,
- );
-
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'DV: mint amount < min',
- },
- );
- });
-
- it('should fail: call for amount < minAmount', async () => {
- const {
- depositVaultWithMToken,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(stableCoins.dai, owner, 100_000);
- await approveBase18(
- owner,
- stableCoins.dai,
- depositVaultWithMToken,
- 100_000,
- );
-
- await setMinAmountToDepositTest(
- { depositVault: depositVaultWithMToken, owner },
- 100_000,
- );
- await setInstantDailyLimitTest(
- { vault: depositVaultWithMToken, owner },
- 150_000,
- );
-
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 99,
- {
- revertMessage: 'DV: mToken amount < min',
- },
- );
- });
-
- it('should fail: if exceed allowance of deposit for token', async () => {
- const {
- depositVaultWithMToken,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(stableCoins.dai, owner, 100_000);
- await changeTokenAllowanceTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.dai.address,
- 100,
- );
- await approveBase18(
- owner,
- stableCoins.dai,
- depositVaultWithMToken,
- 100_000,
- );
-
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'MV: exceed allowance',
- },
- );
- });
-
- it('should fail: if mint limit exceeded', async () => {
- const {
- depositVaultWithMToken,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(stableCoins.dai, owner, 100_000);
- await setInstantDailyLimitTest(
- { vault: depositVaultWithMToken, owner },
- 1000,
- );
-
- await approveBase18(
- owner,
- stableCoins.dai,
- depositVaultWithMToken,
- 100_000,
- );
-
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'MV: exceed limit',
- },
- );
- });
-
- it('should fail: if min receive amount greater then actual', async () => {
- const {
- depositVaultWithMToken,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(stableCoins.dai, owner, 100_000);
-
- await approveBase18(
- owner,
- stableCoins.dai,
- depositVaultWithMToken,
- 100_000,
- );
-
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- minAmount: parseUnits('100000'),
- },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'DV: minReceiveAmount > actual',
- },
- );
- });
-
- it('should fail: if some fee = 100%', async () => {
- const {
- owner,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithMToken, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 10000,
- true,
- );
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'DV: mToken amount < min',
- },
- );
-
- await removePaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.dai,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setInstantFeeTest({ vault: depositVaultWithMToken, owner }, 10000);
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- { revertMessage: 'DV: mToken amount < min' },
- );
- });
-
- it('deposit 100 USDC when mTokenDepositsEnabled is true', async () => {
- const {
- owner,
- depositVaultWithMToken,
- depositVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await setMTokenDepositsEnabledTest(
- { depositVaultWithMToken, owner },
- true,
- );
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMToken,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
- await setMinAmountTest({ vault: depositVault, owner }, 0);
-
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('when mTokenDepositsEnabled is false, normal DV flow', async () => {
- const {
- owner,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMToken,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
-
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- expectedMTokenDeposited: false,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit with waived fee', async () => {
- const {
- owner,
- depositVaultWithMToken,
- depositVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await setMTokenDepositsEnabledTest(
- { depositVaultWithMToken, owner },
- true,
- );
-
- await addWaivedFeeAccountTest(
- { vault: depositVaultWithMToken, owner },
- regularAccounts[0].address,
- );
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMToken,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
- await setMinAmountTest({ vault: depositVault, owner }, 0);
-
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- waivedFee: true,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit 100 DAI with mToken enabled (non-stablecoin feed)', async () => {
- const {
- owner,
- depositVaultWithMToken,
- depositVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await setMTokenDepositsEnabledTest(
- { depositVaultWithMToken, owner },
- true,
- );
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVaultWithMToken,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- false,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- false,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
- await setMinAmountTest({ vault: depositVault, owner }, 0);
-
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit with greenlist enabled and user in greenlist', async () => {
- const {
- owner,
- depositVaultWithMToken,
- depositVault,
- stableCoins,
- mTBILL,
- greenListableTester,
- mTokenToUsdDataFeed,
- accessControl,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await depositVaultWithMToken.setGreenlistEnable(true);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await setMTokenDepositsEnabledTest(
- { depositVaultWithMToken, owner },
- true,
- );
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMToken,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
- await setMinAmountTest({ vault: depositVault, owner }, 0);
-
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit with custom recipient, mTokenDepositsEnabled is true', async () => {
- const {
- owner,
- depositVaultWithMToken,
- depositVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await setMTokenDepositsEnabledTest(
- { depositVaultWithMToken, owner },
- true,
- );
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMToken,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
- await setMinAmountTest({ vault: depositVault, owner }, 0);
-
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient: regularAccounts[1],
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit with custom recipient, mTokenDepositsEnabled is false', async () => {
- const {
- owner,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMToken,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
-
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- expectedMTokenDeposited: false,
- customRecipient: regularAccounts[1],
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('should fail: greenlist enabled and user not in greenlist', async () => {
- const {
- owner,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await depositVaultWithMToken.setGreenlistEnable(true);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMToken,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
-
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- },
- );
- });
-
- it('should fail: first deposit mint amount below configured minimum', async () => {
- const {
- owner,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await setMTokenDepositsEnabledTest(
- { depositVaultWithMToken, owner },
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 0);
- await setMinAmountToDepositTest(
- { depositVault: depositVaultWithMToken, owner },
- 200,
- );
-
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithMToken, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- revertMessage: 'DV: mint amount < min',
- },
- );
- });
-
- it('should fail: mToken deposit enabled with token not in target DV', async () => {
- const {
- owner,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await setMTokenDepositsEnabledTest(
- { depositVaultWithMToken, owner },
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithMToken, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- false,
- );
-
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'DVMT: auto-invest failed',
- },
- );
- });
-
- it('should fail: greenlist enabled and recipient not in greenlist (custom recipient overload)', async () => {
- const {
- owner,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await depositVaultWithMToken.setGreenlistEnable(true);
-
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: recipient in blacklist (custom recipient overload)', async () => {
- const {
- owner,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- customRecipient,
- );
-
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: recipient in sanctions list (custom recipient overload)', async () => {
- const {
- owner,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- customRecipient,
- );
-
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('should fail: when function paused (custom recipient overload)', async () => {
- const {
- owner,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- customRecipient,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVaultWithMToken,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector(
- 'depositInstant(address,uint256,uint256,bytes32,address)',
- );
- await pauseVaultFn(depositVaultWithMToken, selector);
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('mTokenDepositsEnabled, auto-invest fails, fallback enabled: fallback to normal flow', async () => {
- const {
- owner,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await setMTokenDepositsEnabledTest(
- { depositVaultWithMToken, owner },
- true,
- );
- await setAutoInvestFallbackEnabledMTokenTest(
- { depositVaultWithMToken, owner },
- true,
- );
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMToken,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
-
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- expectedMTokenDeposited: false,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('should fail: mTokenDepositsEnabled, auto-invest fails, fallback disabled', async () => {
- const {
- owner,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await setMTokenDepositsEnabledTest(
- { depositVaultWithMToken, owner },
- true,
- );
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMToken,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
-
- await depositInstantWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- expectedMTokenDeposited: false,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'DVMT: auto-invest failed',
- },
- );
- });
- });
-
- describe('depositRequest()', () => {
- it('deposit request 100 USDC', async () => {
- const {
- owner,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMToken,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit request 100 USDC with mToken auto-invest', async () => {
- const {
- owner,
- depositVaultWithMToken,
- depositVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await setMTokenDepositsEnabledTest(
- { depositVaultWithMToken, owner },
- true,
- );
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMToken,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: depositVault, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
- await setMinAmountTest({ vault: depositVault, owner }, 0);
-
- await depositRequestWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit request with mToken auto-invest fails, fallback enabled: fallback to normal flow', async () => {
- const {
- owner,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await setMTokenDepositsEnabledTest(
- { depositVaultWithMToken, owner },
- true,
- );
- await setAutoInvestFallbackEnabledMTokenTest(
- { depositVaultWithMToken, owner },
- true,
- );
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMToken,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
-
- await depositRequestWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- expectedMTokenDeposited: false,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('should fail: deposit request with mToken auto-invest fails, fallback disabled', async () => {
- const {
- owner,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await setMTokenDepositsEnabledTest(
- { depositVaultWithMToken, owner },
- true,
- );
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMToken,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
-
- await depositRequestWithMTokenTest(
- {
- depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- expectedMTokenDeposited: false,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'DVMT: auto-invest failed',
- },
- );
- });
- });
-
- describe('approveRequest()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const {
- owner,
- regularAccounts,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithMToken, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
-
- const request = await depositRequestTest(
- {
- depositVault: depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
-
- await approveRequestTest(
- {
- depositVault: depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- request.requestId!,
- request.rate!,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('approve request: happy path', async () => {
- const {
- owner,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithMToken, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
-
- const request = await depositRequestTest(
- {
- depositVault: depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
-
- await approveRequestTest(
- {
- depositVault: depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- request.requestId!,
- request.rate!,
- );
- });
- });
-
- describe('safeApproveRequest()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const {
- owner,
- regularAccounts,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithMToken, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
-
- const request = await depositRequestTest(
- {
- depositVault: depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
-
- await safeApproveRequestTest(
- {
- depositVault: depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- request.requestId!,
- request.rate!,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('safe approve request: happy path', async () => {
- const {
- owner,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithMToken, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
-
- const request = await depositRequestTest(
- {
- depositVault: depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
-
- await safeApproveRequestTest(
- {
- depositVault: depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- request.requestId!,
- request.rate!,
- );
- });
- });
-
- describe('safeBulkApproveRequest()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const {
- owner,
- regularAccounts,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithMToken, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
-
- const request = await depositRequestTest(
- {
- depositVault: depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
-
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: request.requestId! }],
- request.rate!,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('safe bulk approve request: happy path', async () => {
- const {
- owner,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithMToken, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
-
- const request = await depositRequestTest(
- {
- depositVault: depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
-
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: request.requestId! }],
- request.rate!,
- );
- });
- });
-
- describe('rejectRequest()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const {
- owner,
- regularAccounts,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithMToken, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
-
- const request = await depositRequestTest(
- {
- depositVault: depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
-
- await rejectRequestTest(
- {
- depositVault: depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- request.requestId!,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('reject request: happy path', async () => {
- const {
- owner,
- depositVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithMToken, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithMToken, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
-
- const request = await depositRequestTest(
- {
- depositVault: depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
+ describe('depositInstant()', async () => {
+ it('deposit 100 USDC when mTokenDepositsEnabled is true', async () => {
+ const {
+ owner,
+ depositVaultWithMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadFixture(defaultDeploy);
+
+ await setMTokenDepositsEnabledTest(
+ { depositVaultWithMToken, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithMToken,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithMToken, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositInstantWithMTokenTest(
+ {
+ depositVaultWithMToken,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('when mTokenDepositsEnabled is false, normal DV flow', async () => {
+ const {
+ owner,
+ depositVaultWithMToken,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadFixture(defaultDeploy);
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithMToken,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithMToken, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
+
+ await depositInstantWithMTokenTest(
+ {
+ depositVaultWithMToken,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ expectedMTokenDeposited: false,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('deposit with waived fee', async () => {
+ const {
+ owner,
+ depositVaultWithMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadFixture(defaultDeploy);
+
+ await setMTokenDepositsEnabledTest(
+ { depositVaultWithMToken, owner },
+ true,
+ );
+
+ await setWaivedFeeAccountTest(
+ { vault: depositVaultWithMToken, owner },
+ regularAccounts[0].address,
+ true,
+ );
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithMToken,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithMToken, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositInstantWithMTokenTest(
+ {
+ depositVaultWithMToken,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ waivedFee: true,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('deposit 100 DAI with mToken enabled (non-stablecoin feed)', async () => {
+ const {
+ owner,
+ depositVaultWithMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadFixture(defaultDeploy);
+
+ await setMTokenDepositsEnabledTest(
+ { depositVaultWithMToken, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVaultWithMToken,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithMToken, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ false,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ false,
+ );
+ await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositInstantWithMTokenTest(
+ {
+ depositVaultWithMToken,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: mToken deposit enabled with token not in target DV', async () => {
+ const {
+ owner,
+ depositVaultWithMToken,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ } = await loadFixture(defaultDeploy);
+
+ await setMTokenDepositsEnabledTest(
+ { depositVaultWithMToken, owner },
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(
+ owner,
+ stableCoins.dai,
+ depositVaultWithMToken,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithMToken, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ false,
+ );
+
+ await depositInstantWithMTokenTest(
+ {
+ depositVaultWithMToken,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ revertCustomError: {
+ customErrorName: 'AutoInvestFailed',
+ },
+ },
+ );
+ });
+
+ it('mTokenDepositsEnabled, auto-invest fails, fallback enabled: fallback to normal flow', async () => {
+ const {
+ owner,
+ depositVaultWithMToken,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadFixture(defaultDeploy);
+
+ await setMTokenDepositsEnabledTest(
+ { depositVaultWithMToken, owner },
+ true,
+ );
+ await setAutoInvestFallbackEnabledMTokenTest(
+ { depositVaultWithMToken, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithMToken,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithMToken, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
+
+ await depositInstantWithMTokenTest(
+ {
+ depositVaultWithMToken,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ expectedMTokenDeposited: false,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: mTokenDepositsEnabled, auto-invest fails, fallback disabled', async () => {
+ const {
+ owner,
+ depositVaultWithMToken,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadFixture(defaultDeploy);
+
+ await setMTokenDepositsEnabledTest(
+ { depositVaultWithMToken, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithMToken,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithMToken, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
+
+ await depositInstantWithMTokenTest(
+ {
+ depositVaultWithMToken,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ expectedMTokenDeposited: false,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'AutoInvestFailed',
+ },
+ },
+ );
+ });
+ });
- await rejectRequestTest(
- {
- depositVault: depositVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- request.requestId!,
- );
+ describe('depositRequest()', () => {
+ it('deposit request 100 USDC with mToken auto-invest', async () => {
+ const {
+ owner,
+ depositVaultWithMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadFixture(defaultDeploy);
+
+ await setMTokenDepositsEnabledTest(
+ { depositVaultWithMToken, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithMToken,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithMToken, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestWithMTokenTest(
+ {
+ depositVaultWithMToken,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('deposit request with mToken auto-invest fails, fallback enabled: fallback to normal flow', async () => {
+ const {
+ owner,
+ depositVaultWithMToken,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadFixture(defaultDeploy);
+
+ await setMTokenDepositsEnabledTest(
+ { depositVaultWithMToken, owner },
+ true,
+ );
+ await setAutoInvestFallbackEnabledMTokenTest(
+ { depositVaultWithMToken, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithMToken,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithMToken, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
+
+ await depositRequestWithMTokenTest(
+ {
+ depositVaultWithMToken,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ expectedMTokenDeposited: false,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: deposit request with mToken auto-invest fails, fallback disabled', async () => {
+ const {
+ owner,
+ depositVaultWithMToken,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadFixture(defaultDeploy);
+
+ await setMTokenDepositsEnabledTest(
+ { depositVaultWithMToken, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithMToken,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithMToken, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithMToken, owner }, 10);
+
+ await depositRequestWithMTokenTest(
+ {
+ depositVaultWithMToken,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ expectedMTokenDeposited: false,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'AutoInvestFailed',
+ },
+ },
+ );
+ });
+ });
});
- });
-});
+ },
+);
diff --git a/test/unit/DepositVaultWithMorpho.test.ts b/test/unit/DepositVaultWithMorpho.test.ts
index 7d57e174..3be05234 100644
--- a/test/unit/DepositVaultWithMorpho.test.ts
+++ b/test/unit/DepositVaultWithMorpho.test.ts
@@ -1,22 +1,25 @@
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
+import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { expect } from 'chai';
-import { constants } from 'ethers';
import { parseUnits } from 'ethers/lib/utils';
import { ethers } from 'hardhat';
-import { encodeFnSelector } from '../../helpers/utils';
import {
- ManageableVaultTester__factory,
+ baseInitParamsDv,
+ depositVaultSuits,
+} from './suits/deposit-vault.suits';
+
+import {
+ DepositVaultWithMorpho__factory,
+ DepositVaultWithMorphoTest__factory,
MorphoVaultMock__factory,
} from '../../typechain-types';
-import { acErrors, blackList, greenList } from '../common/ac.helpers';
+import { acErrors } from '../common/ac.helpers';
import {
approveBase18,
mintToken,
- pauseVault,
- pauseVaultFn,
+ validateImplementation,
} from '../common/common.helpers';
-import { setRoundData } from '../common/data-feed.helpers';
import {
depositInstantWithMorphoTest,
depositRequestWithMorphoTest,
@@ -25,2795 +28,975 @@ import {
setMorphoDepositsEnabledTest,
setMorphoVaultTest,
} from '../common/deposit-vault-morpho.helpers';
-import {
- approveRequestTest,
- depositRequestTest,
- rejectRequestTest,
- safeApproveRequestTest,
- safeBulkApproveRequestTest,
-} from '../common/deposit-vault.helpers';
import { defaultDeploy } from '../common/fixtures';
import {
addPaymentTokenTest,
- addWaivedFeeAccountTest,
- changeTokenAllowanceTest,
- changeTokenFeeTest,
- removePaymentTokenTest,
- removeWaivedFeeAccountTest,
- setInstantDailyLimitTest,
- setMinAmountToDepositTest,
- setInstantFeeTest,
setMinAmountTest,
- setVariabilityToleranceTest,
- withdrawTest,
} from '../common/manageable-vault.helpers';
-import { sanctionUser } from '../common/with-sanctions-list.helpers';
-
-describe('DepositVaultWithMorpho', function () {
- it('deployment', async () => {
- const {
- depositVaultWithMorpho,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- roles,
- } = await loadFixture(defaultDeploy);
-
- expect(await depositVaultWithMorpho.mToken()).eq(mTBILL.address);
- expect(await depositVaultWithMorpho.paused()).eq(false);
- expect(await depositVaultWithMorpho.tokensReceiver()).eq(
- tokensReceiver.address,
- );
- expect(await depositVaultWithMorpho.feeReceiver()).eq(feeReceiver.address);
- expect(await depositVaultWithMorpho.ONE_HUNDRED_PERCENT()).eq('10000');
- expect(await depositVaultWithMorpho.minMTokenAmountForFirstDeposit()).eq(
- '0',
- );
- expect(await depositVaultWithMorpho.minAmount()).eq(parseUnits('100'));
- expect(await depositVaultWithMorpho.instantFee()).eq('100');
- expect(await depositVaultWithMorpho.instantDailyLimit()).eq(
- parseUnits('100000'),
- );
- expect(await depositVaultWithMorpho.mTokenDataFeed()).eq(
- mTokenToUsdDataFeed.address,
- );
- expect(await depositVaultWithMorpho.variationTolerance()).eq(1);
- expect(await depositVaultWithMorpho.vaultRole()).eq(
- roles.tokenRoles.mTBILL.depositVaultAdmin,
- );
- expect(await depositVaultWithMorpho.MANUAL_FULLFILMENT_TOKEN()).eq(
- ethers.constants.AddressZero,
- );
+import { initializeDvWithMorpho } from '../common/vault-initializer.helpers';
+
+depositVaultSuits(
+ 'DepositVaultWithMorpho',
+ defaultDeploy,
+ {
+ createNew: async (owner: SignerWithAddress) =>
+ new DepositVaultWithMorphoTest__factory(owner).deploy(),
+ key: 'depositVaultWithMorpho',
+ },
+ async (fixture) => {
+ const { depositVaultWithMorpho } = fixture;
expect(await depositVaultWithMorpho.morphoDepositsEnabled()).eq(false);
- });
-
- describe('initialization', () => {
- it('should fail: call initialize() when already initialized', async () => {
- const { depositVaultWithMorpho } = await loadFixture(defaultDeploy);
-
- await expect(
- depositVaultWithMorpho.initialize(
- constants.AddressZero,
- {
- mToken: constants.AddressZero,
- mTokenDataFeed: constants.AddressZero,
- },
- {
- feeReceiver: constants.AddressZero,
- tokensReceiver: constants.AddressZero,
- },
- {
- instantFee: 0,
- instantDailyLimit: 0,
- },
- constants.AddressZero,
- 0,
- 0,
- 0,
- 0,
- ),
- ).revertedWith('Initializable: contract is already initialized');
- });
-
- it('should fail: call with initializing == false', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initializeWithoutInitializer(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- ),
- ).revertedWith('Initializable: contract is not initializing');
- });
- });
-
- describe('setMorphoVault()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const {
- depositVaultWithMorpho,
- owner,
- regularAccounts,
- stableCoins,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await setMorphoVaultTest(
- { depositVaultWithMorpho, owner },
- stableCoins.usdc.address,
- morphoVaultMock.address,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: zero token address', async () => {
- const { depositVaultWithMorpho, owner, morphoVaultMock } =
- await loadFixture(defaultDeploy);
-
- await setMorphoVaultTest(
- { depositVaultWithMorpho, owner },
- ethers.constants.AddressZero,
- morphoVaultMock.address,
- {
- revertMessage: 'zero address',
- },
- );
- });
-
- it('should fail: zero vault address', async () => {
- const { depositVaultWithMorpho, owner, stableCoins } = await loadFixture(
- defaultDeploy,
- );
-
- await setMorphoVaultTest(
- { depositVaultWithMorpho, owner },
- stableCoins.usdc.address,
- ethers.constants.AddressZero,
- {
- revertMessage: 'zero address',
- },
- );
- });
-
- it('should fail: asset mismatch', async () => {
- const { depositVaultWithMorpho, owner, stableCoins, morphoVaultMock } =
- await loadFixture(defaultDeploy);
-
- // morphoVaultMock is configured for USDC; passing DAI should fail
- await setMorphoVaultTest(
- { depositVaultWithMorpho, owner },
- stableCoins.dai.address,
- morphoVaultMock.address,
- {
- revertMessage: 'DVM: asset mismatch',
- },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMorpho, owner, stableCoins, morphoVaultMock } =
- await loadFixture(defaultDeploy);
-
- await setMorphoVaultTest(
- { depositVaultWithMorpho, owner },
- stableCoins.usdc.address,
- morphoVaultMock.address,
- );
- });
- });
-
- describe('removeMorphoVault()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMorpho, owner, regularAccounts, stableCoins } =
- await loadFixture(defaultDeploy);
-
- await removeMorphoVaultTest(
- { depositVaultWithMorpho, owner },
- stableCoins.usdc.address,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: vault not set', async () => {
- const { depositVaultWithMorpho, owner, stableCoins } = await loadFixture(
- defaultDeploy,
- );
-
- await removeMorphoVaultTest(
- { depositVaultWithMorpho, owner },
- stableCoins.usdc.address,
- {
- revertMessage: 'DVM: vault not set',
- },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMorpho, owner, stableCoins, morphoVaultMock } =
- await loadFixture(defaultDeploy);
-
- await setMorphoVaultTest(
- { depositVaultWithMorpho, owner },
- stableCoins.usdc.address,
- morphoVaultMock.address,
- );
-
- await removeMorphoVaultTest(
- { depositVaultWithMorpho, owner },
- stableCoins.usdc.address,
- );
- });
- });
-
- describe('setMorphoDepositsEnabled()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMorpho, owner, regularAccounts } =
- await loadFixture(defaultDeploy);
-
- await setMorphoDepositsEnabledTest(
- { depositVaultWithMorpho, owner },
- true,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMorpho, owner } = await loadFixture(
- defaultDeploy,
- );
-
- await setMorphoDepositsEnabledTest(
- { depositVaultWithMorpho, owner },
- true,
- );
- });
-
- it('toggle on and off', async () => {
- const { depositVaultWithMorpho, owner } = await loadFixture(
- defaultDeploy,
- );
-
- await setMorphoDepositsEnabledTest(
- { depositVaultWithMorpho, owner },
- true,
- );
-
- await setMorphoDepositsEnabledTest(
- { depositVaultWithMorpho, owner },
- false,
- );
- });
- });
-
- describe('setAutoInvestFallbackEnabled()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMorpho, owner, regularAccounts } =
- await loadFixture(defaultDeploy);
-
- await setAutoInvestFallbackEnabledMorphoTest(
- { depositVaultWithMorpho, owner },
- true,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMorpho, owner } = await loadFixture(
- defaultDeploy,
- );
-
- await setAutoInvestFallbackEnabledMorphoTest(
- { depositVaultWithMorpho, owner },
- true,
- );
- });
-
- it('toggle on and off', async () => {
- const { depositVaultWithMorpho, owner } = await loadFixture(
- defaultDeploy,
- );
-
- await setAutoInvestFallbackEnabledMorphoTest(
- { depositVaultWithMorpho, owner },
- true,
- );
-
- await setAutoInvestFallbackEnabledMorphoTest(
- { depositVaultWithMorpho, owner },
- false,
- );
- });
- });
-
- describe('setMinAmount()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMorpho, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10, {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
+ await validateImplementation(DepositVaultWithMorpho__factory);
+ },
+ {
+ deployUninitialized: (fixture) =>
+ new DepositVaultWithMorphoTest__factory(fixture.owner).deploy(),
+ initialize: async (fixture, params, opt) => {
+ await initializeDvWithMorpho(
+ { ...baseInitParamsDv(fixture), ...params },
+ opt?.contract,
+ opt,
+ );
+ },
+ },
+ async (defaultDeploy) => {
+ describe('DepositVaultWithMorpho', function () {
+ describe('setMorphoVault()', () => {
+ it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
+ const {
+ depositVaultWithMorpho,
+ owner,
+ regularAccounts,
+ stableCoins,
+ morphoVaultMock,
+ } = await loadFixture(defaultDeploy);
+
+ await setMorphoVaultTest(
+ { depositVaultWithMorpho, owner },
+ stableCoins.usdc.address,
+ morphoVaultMock.address,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ },
+ );
+ });
+
+ it('should fail: zero token address', async () => {
+ const { depositVaultWithMorpho, owner, morphoVaultMock } =
+ await loadFixture(defaultDeploy);
+
+ await setMorphoVaultTest(
+ { depositVaultWithMorpho, owner },
+ ethers.constants.AddressZero,
+ morphoVaultMock.address,
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidAddress',
+ },
+ },
+ );
+ });
+
+ it('should fail: zero vault address', async () => {
+ const { depositVaultWithMorpho, owner, stableCoins } =
+ await loadFixture(defaultDeploy);
+
+ await setMorphoVaultTest(
+ { depositVaultWithMorpho, owner },
+ stableCoins.usdc.address,
+ ethers.constants.AddressZero,
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidAddress',
+ },
+ },
+ );
+ });
+
+ it('should fail: asset mismatch', async () => {
+ const {
+ depositVaultWithMorpho,
+ owner,
+ stableCoins,
+ morphoVaultMock,
+ } = await loadFixture(defaultDeploy);
+
+ // morphoVaultMock is configured for USDC; passing DAI should fail
+ await setMorphoVaultTest(
+ { depositVaultWithMorpho, owner },
+ stableCoins.dai.address,
+ morphoVaultMock.address,
+ {
+ revertCustomError: {
+ customErrorName: 'AssetMismatch',
+ },
+ },
+ );
+ });
+
+ it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
+ const {
+ depositVaultWithMorpho,
+ owner,
+ stableCoins,
+ morphoVaultMock,
+ } = await loadFixture(defaultDeploy);
+
+ await setMorphoVaultTest(
+ { depositVaultWithMorpho, owner },
+ stableCoins.usdc.address,
+ morphoVaultMock.address,
+ );
+ });
});
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMorpho, owner } = await loadFixture(
- defaultDeploy,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
- });
- });
-
- describe('setMinMTokenAmountForFirstDeposit()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMorpho, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
-
- await setMinAmountToDepositTest(
- { depositVault: depositVaultWithMorpho, owner },
- 10,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMorpho, owner } = await loadFixture(
- defaultDeploy,
- );
-
- await setMinAmountToDepositTest(
- { depositVault: depositVaultWithMorpho, owner },
- 10,
- );
- });
- });
-
- describe('setInstantDailyLimit()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMorpho, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
-
- await setInstantDailyLimitTest(
- { vault: depositVaultWithMorpho, owner },
- 10,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMorpho, owner } = await loadFixture(
- defaultDeploy,
- );
-
- await setInstantDailyLimitTest(
- { vault: depositVaultWithMorpho, owner },
- 10,
- );
- });
- });
-
- describe('setVariabilityTolerance()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMorpho, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
-
- await setVariabilityToleranceTest(
- { vault: depositVaultWithMorpho, owner },
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMorpho, owner } = await loadFixture(
- defaultDeploy,
- );
-
- await setVariabilityToleranceTest(
- { vault: depositVaultWithMorpho, owner },
- 100,
- );
- });
- });
-
- describe('addPaymentToken()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const {
- depositVaultWithMorpho,
- regularAccounts,
- owner,
- stableCoins,
- dataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- constants.MaxUint256,
- { from: regularAccounts[0], revertMessage: 'WMAC: hasnt role' },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMorpho, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- });
- });
-
- describe('removePaymentToken()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMorpho, regularAccounts, owner, stableCoins } =
- await loadFixture(defaultDeploy);
-
- await removePaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMorpho, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await removePaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- );
- });
- });
-
- describe('addWaivedFeeAccount()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMorpho, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: depositVaultWithMorpho, owner },
- regularAccounts[1].address,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMorpho, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: depositVaultWithMorpho, owner },
- regularAccounts[0].address,
- );
- });
- });
-
- describe('removeWaivedFeeAccount()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMorpho, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
-
- await addWaivedFeeAccountTest(
- { vault: depositVaultWithMorpho, owner },
- regularAccounts[1].address,
- );
-
- await removeWaivedFeeAccountTest(
- { vault: depositVaultWithMorpho, owner },
- regularAccounts[1].address,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMorpho, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
-
- await addWaivedFeeAccountTest(
- { vault: depositVaultWithMorpho, owner },
- regularAccounts[0].address,
- );
- await removeWaivedFeeAccountTest(
- { vault: depositVaultWithMorpho, owner },
- regularAccounts[0].address,
- );
- });
- });
-
- describe('withdrawToken()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMorpho, owner, stableCoins, regularAccounts } =
- await loadFixture(defaultDeploy);
- await mintToken(stableCoins.usdc, depositVaultWithMorpho, 1);
- await withdrawTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc.address,
- 1,
- owner,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMorpho, owner, stableCoins } = await loadFixture(
- defaultDeploy,
- );
- await mintToken(stableCoins.usdc, depositVaultWithMorpho, 100);
- const usdcDecimals = await stableCoins.usdc.decimals();
- await withdrawTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- parseUnits('100', usdcDecimals),
- owner,
- );
- });
- });
-
- describe('changeTokenAllowance()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMorpho, owner, stableCoins, regularAccounts } =
- await loadFixture(defaultDeploy);
- await changeTokenAllowanceTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc.address,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMorpho, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc.address,
- parseUnits('200'),
- );
- });
- });
-
- describe('changeTokenFee()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMorpho, owner, stableCoins, regularAccounts } =
- await loadFixture(defaultDeploy);
- await changeTokenFeeTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc.address,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithMorpho, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenFeeTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc.address,
- 100,
- );
- });
- });
-
- describe('freeFromMinAmount()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const { depositVaultWithMorpho, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- depositVaultWithMorpho
- .connect(regularAccounts[0])
- .freeFromMinAmount(regularAccounts[1].address, true),
- ).to.be.revertedWith('WMAC: hasnt role');
- });
- it('should not fail', async () => {
- const { depositVaultWithMorpho, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- depositVaultWithMorpho.freeFromMinAmount(
- regularAccounts[0].address,
- true,
- ),
- ).to.not.reverted;
-
- expect(
- await depositVaultWithMorpho.isFreeFromMinAmount(
- regularAccounts[0].address,
- ),
- ).to.eq(true);
- });
- it('should fail: already in list', async () => {
- const { depositVaultWithMorpho, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- depositVaultWithMorpho.freeFromMinAmount(
- regularAccounts[0].address,
- true,
- ),
- ).to.not.reverted;
-
- expect(
- await depositVaultWithMorpho.isFreeFromMinAmount(
- regularAccounts[0].address,
- ),
- ).to.eq(true);
-
- await expect(
- depositVaultWithMorpho.freeFromMinAmount(
- regularAccounts[0].address,
- true,
- ),
- ).to.revertedWith('DV: already free');
- });
- });
-
- describe('depositInstant()', async () => {
- it('should fail: when there is no token in vault', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMorpho,
- 100,
- );
-
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'MV: token not exists',
- },
- );
- });
-
- it('should fail: when function paused', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMorpho,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
-
- await pauseVault(depositVaultWithMorpho, {
- from: owner,
+ describe('removeMorphoVault()', () => {
+ it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
+ const {
+ depositVaultWithMorpho,
+ owner,
+ regularAccounts,
+ stableCoins,
+ } = await loadFixture(defaultDeploy);
+
+ await removeMorphoVaultTest(
+ { depositVaultWithMorpho, owner },
+ stableCoins.usdc.address,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ },
+ );
+ });
+
+ it('should fail: vault not set', async () => {
+ const { depositVaultWithMorpho, owner, stableCoins } =
+ await loadFixture(defaultDeploy);
+
+ await removeMorphoVaultTest(
+ { depositVaultWithMorpho, owner },
+ stableCoins.usdc.address,
+ {
+ revertCustomError: {
+ customErrorName: 'VaultNotSet',
+ },
+ },
+ );
+ });
+
+ it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
+ const {
+ depositVaultWithMorpho,
+ owner,
+ stableCoins,
+ morphoVaultMock,
+ } = await loadFixture(defaultDeploy);
+
+ await setMorphoVaultTest(
+ { depositVaultWithMorpho, owner },
+ stableCoins.usdc.address,
+ morphoVaultMock.address,
+ );
+
+ await removeMorphoVaultTest(
+ { depositVaultWithMorpho, owner },
+ stableCoins.usdc.address,
+ );
+ });
});
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: paused',
- },
- );
- });
-
- it('should fail: user in blacklist', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- accessControl,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMorpho,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
-
- await blackList(
- { blacklistable: depositVaultWithMorpho, accessControl, owner },
- regularAccounts[0],
- );
-
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: when trying to deposit 0 amount', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- },
- stableCoins.usdc,
- 0,
- {
- revertMessage: 'DV: invalid amount',
- },
- );
- });
-
- it('should fail: when rounding is invalid', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- },
- stableCoins.usdc,
- 100.0000000001,
- {
- revertMessage: 'MV: invalid rounding',
- },
- );
- });
-
- it('should fail: call with insufficient allowance', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, owner, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- },
- stableCoins.usdc,
- 100,
- {
- revertMessage: 'ERC20: insufficient allowance',
- },
- );
- });
-
- it('should fail: call with insufficient balance', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, stableCoins.usdc, depositVaultWithMorpho, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- },
- stableCoins.usdc,
- 100,
- {
- revertMessage: 'ERC20: transfer amount exceeds balance',
- },
- );
- });
-
- it('should fail: dataFeed rate 0', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- dataFeed,
- mockedAggregator,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, stableCoins.usdc, depositVaultWithMorpho, 10);
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await mintToken(stableCoins.usdc, owner, 100_000);
- await setRoundData({ mockedAggregator }, 0);
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- },
- stableCoins.usdc,
- 1,
- {
- revertMessage: 'DF: feed is deprecated',
- },
- );
- });
-
- it('should fail: call for amount < minAmountToDepositTest', async () => {
- const {
- depositVaultWithMorpho,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(stableCoins.usdc, owner, 100_000);
- await approveBase18(
- owner,
- stableCoins.usdc,
- depositVaultWithMorpho,
- 100_000,
- );
-
- await setMinAmountToDepositTest(
- { depositVault: depositVaultWithMorpho, owner },
- 100_000,
- );
- await setInstantDailyLimitTest(
- { vault: depositVaultWithMorpho, owner },
- 150_000,
- );
-
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- },
- stableCoins.usdc,
- 99_999,
- {
- revertMessage: 'DV: mint amount < min',
- },
- );
- });
-
- it('should fail: call for amount < minAmount', async () => {
- const {
- depositVaultWithMorpho,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(stableCoins.usdc, owner, 100_000);
- await approveBase18(
- owner,
- stableCoins.usdc,
- depositVaultWithMorpho,
- 100_000,
- );
-
- await setMinAmountToDepositTest(
- { depositVault: depositVaultWithMorpho, owner },
- 100_000,
- );
- await setInstantDailyLimitTest(
- { vault: depositVaultWithMorpho, owner },
- 150_000,
- );
-
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- },
- stableCoins.usdc,
- 99,
- {
- revertMessage: 'DV: mToken amount < min',
- },
- );
- });
-
- it('should fail: if exceed allowance of deposit for token', async () => {
- const {
- depositVaultWithMorpho,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(stableCoins.usdc, owner, 100_000);
- await changeTokenAllowanceTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc.address,
- 100,
- );
- await approveBase18(
- owner,
- stableCoins.usdc,
- depositVaultWithMorpho,
- 100_000,
- );
-
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- },
- stableCoins.usdc,
- 99_999,
- {
- revertMessage: 'MV: exceed allowance',
- },
- );
- });
-
- it('should fail: if mint limit exceeded', async () => {
- const {
- depositVaultWithMorpho,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(stableCoins.usdc, owner, 100_000);
- await setInstantDailyLimitTest(
- { vault: depositVaultWithMorpho, owner },
- 1000,
- );
-
- await approveBase18(
- owner,
- stableCoins.usdc,
- depositVaultWithMorpho,
- 100_000,
- );
-
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- },
- stableCoins.usdc,
- 99_999,
- {
- revertMessage: 'MV: exceed limit',
- },
- );
- });
-
- it('should fail: if min receive amount greater then actual', async () => {
- const {
- depositVaultWithMorpho,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(stableCoins.usdc, owner, 100_000);
-
- await approveBase18(
- owner,
- stableCoins.usdc,
- depositVaultWithMorpho,
- 100_000,
- );
-
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- minAmount: parseUnits('100000'),
- },
- stableCoins.usdc,
- 99_999,
- {
- revertMessage: 'DV: minReceiveAmount > actual',
- },
- );
- });
-
- it('should fail: if some fee = 100%', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithMorpho, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 10000,
- true,
- );
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- },
- stableCoins.usdc,
- 100,
- {
- revertMessage: 'DV: mToken amount < min',
- },
- );
-
- await removePaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setInstantFeeTest({ vault: depositVaultWithMorpho, owner }, 10000);
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- },
- stableCoins.usdc,
- 100,
- { revertMessage: 'DV: mToken amount < min' },
- );
- });
-
- it('should fail: greenlist enabled and user not in greenlist', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await depositVaultWithMorpho.setGreenlistEnable(true);
-
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- },
- stableCoins.usdc,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: user in sanctions list', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- regularAccounts[0],
- );
-
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- },
- stableCoins.usdc,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('morphoDepositsEnabled but no vault for token: fallback to normal flow', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await setMorphoDepositsEnabledTest(
- { depositVaultWithMorpho, owner },
- true,
- );
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMorpho,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
-
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- expectedMorphoDeposited: false,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('morphoDepositsEnabled, vault configured but deposit reverts, fallback enabled: fallback to normal flow', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await setMorphoDepositsEnabledTest(
- { depositVaultWithMorpho, owner },
- true,
- );
- await setAutoInvestFallbackEnabledMorphoTest(
- { depositVaultWithMorpho, owner },
- true,
- );
-
- await setMorphoVaultTest(
- { depositVaultWithMorpho, owner },
- stableCoins.usdc.address,
- morphoVaultMock.address,
- );
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMorpho,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
-
- await morphoVaultMock.setShouldRevertDeposit(true);
-
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- expectedMorphoDeposited: false,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('should fail: morphoDepositsEnabled, vault configured but deposit reverts, fallback disabled', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await setMorphoDepositsEnabledTest(
- { depositVaultWithMorpho, owner },
- true,
- );
-
- await setMorphoVaultTest(
- { depositVaultWithMorpho, owner },
- stableCoins.usdc.address,
- morphoVaultMock.address,
- );
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMorpho,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
-
- await morphoVaultMock.setShouldRevertDeposit(true);
-
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- expectedMorphoDeposited: false,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'DVM: auto-invest failed',
- },
- );
- });
-
- it('should fail: when Morpho deposit mints zero shares', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await setMorphoDepositsEnabledTest(
- { depositVaultWithMorpho, owner },
- true,
- );
- await setMorphoVaultTest(
- { depositVaultWithMorpho, owner },
- stableCoins.usdc.address,
- morphoVaultMock.address,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 0);
- await morphoVaultMock.setExchangeRate(parseUnits('1000000000000'));
-
- await mintToken(stableCoins.usdc, owner, 1);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithMorpho, 1);
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- expectedMorphoDeposited: false,
- },
- stableCoins.usdc,
- 0.000001,
- {
- revertMessage: 'DVM: zero shares',
- },
- );
- });
-
- it('deposit 100 USDC when morphoDepositsEnabled is true', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await setMorphoDepositsEnabledTest(
- { depositVaultWithMorpho, owner },
- true,
- );
-
- await setMorphoVaultTest(
- { depositVaultWithMorpho, owner },
- stableCoins.usdc.address,
- morphoVaultMock.address,
- );
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMorpho,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
-
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('when morphoDepositsEnabled is false, normal DV flow', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMorpho,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
-
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- expectedMorphoDeposited: false,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit with waived fee', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await setMorphoDepositsEnabledTest(
- { depositVaultWithMorpho, owner },
- true,
- );
-
- await setMorphoVaultTest(
- { depositVaultWithMorpho, owner },
- stableCoins.usdc.address,
- morphoVaultMock.address,
- );
-
- await addWaivedFeeAccountTest(
- { vault: depositVaultWithMorpho, owner },
- regularAccounts[0].address,
- );
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMorpho,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
-
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- waivedFee: true,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit with greenlist enabled and user in greenlist', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- greenListableTester,
- mTokenToUsdDataFeed,
- accessControl,
- regularAccounts,
- dataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await depositVaultWithMorpho.setGreenlistEnable(true);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await setMorphoDepositsEnabledTest(
- { depositVaultWithMorpho, owner },
- true,
- );
-
- await setMorphoVaultTest(
- { depositVaultWithMorpho, owner },
- stableCoins.usdc.address,
- morphoVaultMock.address,
- );
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMorpho,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
-
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit with custom recipient, morphoDepositsEnabled is true', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await setMorphoDepositsEnabledTest(
- { depositVaultWithMorpho, owner },
- true,
- );
-
- await setMorphoVaultTest(
- { depositVaultWithMorpho, owner },
- stableCoins.usdc.address,
- morphoVaultMock.address,
- );
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMorpho,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
-
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- customRecipient: regularAccounts[1],
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit 100 DAI with morpho enabled (per-asset vault mapping)', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- const daiMorphoVault = await new MorphoVaultMock__factory(owner).deploy(
- stableCoins.dai.address,
- );
- await stableCoins.dai.mint(daiMorphoVault.address, parseUnits('1000000'));
-
- await setMorphoDepositsEnabledTest(
- { depositVaultWithMorpho, owner },
- true,
- );
-
- await setMorphoVaultTest(
- { depositVaultWithMorpho, owner },
- stableCoins.dai.address,
- daiMorphoVault.address,
- );
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVaultWithMorpho,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- false,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
-
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock: daiMorphoVault,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('toggle mid-flight: morpho enabled then disabled', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await setMorphoVaultTest(
- { depositVaultWithMorpho, owner },
- stableCoins.usdc.address,
- morphoVaultMock.address,
- );
-
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
-
- // Deposit 1: morpho enabled
- await setMorphoDepositsEnabledTest(
- { depositVaultWithMorpho, owner },
- true,
- );
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 200);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMorpho,
- 200,
- );
-
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
-
- // Deposit 2: morpho disabled
- await setMorphoDepositsEnabledTest(
- { depositVaultWithMorpho, owner },
- false,
- );
-
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- expectedMorphoDeposited: false,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('should fail: greenlist enabled and recipient not in greenlist (custom recipient overload)', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- customRecipient,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await depositVaultWithMorpho.setGreenlistEnable(true);
-
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- customRecipient,
- },
- stableCoins.usdc,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: recipient in blacklist (custom recipient overload)', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- accessControl,
- regularAccounts,
- customRecipient,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: depositVaultWithMorpho, accessControl, owner },
- customRecipient,
- );
-
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- customRecipient,
- },
- stableCoins.usdc,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: recipient in sanctions list (custom recipient overload)', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- customRecipient,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- customRecipient,
- );
-
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- customRecipient,
- },
- stableCoins.usdc,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('should fail: when function paused (custom recipient overload)', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- customRecipient,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMorpho,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector(
- 'depositInstant(address,uint256,uint256,bytes32,address)',
- );
- await pauseVaultFn(depositVaultWithMorpho, selector);
- await depositInstantWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- customRecipient,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
- });
-
- describe('depositRequest()', () => {
- it('deposit request 100 USDC', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMorpho,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit request 100 USDC with morpho auto-invest', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await setMorphoDepositsEnabledTest(
- { depositVaultWithMorpho, owner },
- true,
- );
- await setMorphoVaultTest(
- { depositVaultWithMorpho, owner },
- stableCoins.usdc.address,
- morphoVaultMock.address,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMorpho,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await depositRequestWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit request with morpho auto-invest, deposit reverts, fallback enabled: fallback to normal flow', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await setMorphoDepositsEnabledTest(
- { depositVaultWithMorpho, owner },
- true,
- );
- await setAutoInvestFallbackEnabledMorphoTest(
- { depositVaultWithMorpho, owner },
- true,
- );
- await setMorphoVaultTest(
- { depositVaultWithMorpho, owner },
- stableCoins.usdc.address,
- morphoVaultMock.address,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMorpho,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await morphoVaultMock.setShouldRevertDeposit(true);
-
- await depositRequestWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- expectedMorphoDeposited: false,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('should fail: deposit request with morpho auto-invest, deposit reverts, fallback disabled', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await setMorphoDepositsEnabledTest(
- { depositVaultWithMorpho, owner },
- true,
- );
- await setMorphoVaultTest(
- { depositVaultWithMorpho, owner },
- stableCoins.usdc.address,
- morphoVaultMock.address,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithMorpho,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await morphoVaultMock.setShouldRevertDeposit(true);
-
- await depositRequestWithMorphoTest(
- {
- depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- expectedMorphoDeposited: false,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'DVM: auto-invest failed',
- },
- );
- });
- });
-
- describe('approveRequest()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const {
- owner,
- regularAccounts,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithMorpho, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
-
- const request = await depositRequestTest(
- {
- depositVault: depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
-
- await approveRequestTest(
- {
- depositVault: depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- request.requestId!,
- request.rate!,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('approve request: happy path', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithMorpho, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
-
- const request = await depositRequestTest(
- {
- depositVault: depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
-
- await approveRequestTest(
- {
- depositVault: depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- request.requestId!,
- request.rate!,
- );
- });
- });
-
- describe('safeApproveRequest()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const {
- owner,
- regularAccounts,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithMorpho, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
-
- const request = await depositRequestTest(
- {
- depositVault: depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
-
- await safeApproveRequestTest(
- {
- depositVault: depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- request.requestId!,
- request.rate!,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('safe approve request: happy path', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithMorpho, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
-
- const request = await depositRequestTest(
- {
- depositVault: depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
-
- await safeApproveRequestTest(
- {
- depositVault: depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- request.requestId!,
- request.rate!,
- );
- });
- });
-
- describe('safeBulkApproveRequest()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const {
- owner,
- regularAccounts,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithMorpho, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
-
- const request = await depositRequestTest(
- {
- depositVault: depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
-
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: request.requestId! }],
- request.rate!,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('safe bulk approve request: happy path', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithMorpho, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
-
- const request = await depositRequestTest(
- {
- depositVault: depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
-
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: request.requestId! }],
- request.rate!,
- );
- });
- });
-
- describe('rejectRequest()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const {
- owner,
- regularAccounts,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithMorpho, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
-
- const request = await depositRequestTest(
- {
- depositVault: depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
+ describe('setMorphoDepositsEnabled()', () => {
+ it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
+ const { depositVaultWithMorpho, owner, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await setMorphoDepositsEnabledTest(
+ { depositVaultWithMorpho, owner },
+ true,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ },
+ );
+ });
+
+ it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
+ const { depositVaultWithMorpho, owner } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await setMorphoDepositsEnabledTest(
+ { depositVaultWithMorpho, owner },
+ true,
+ );
+ });
+
+ it('toggle on and off', async () => {
+ const { depositVaultWithMorpho, owner } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await setMorphoDepositsEnabledTest(
+ { depositVaultWithMorpho, owner },
+ true,
+ );
+
+ await setMorphoDepositsEnabledTest(
+ { depositVaultWithMorpho, owner },
+ false,
+ );
+ });
+ });
- await rejectRequestTest(
- {
- depositVault: depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- request.requestId!,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
+ describe('setAutoInvestFallbackEnabled()', () => {
+ it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
+ const { depositVaultWithMorpho, owner, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await setAutoInvestFallbackEnabledMorphoTest(
+ { depositVaultWithMorpho, owner },
+ true,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ },
+ );
+ });
+
+ it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
+ const { depositVaultWithMorpho, owner } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await setAutoInvestFallbackEnabledMorphoTest(
+ { depositVaultWithMorpho, owner },
+ true,
+ );
+ });
+
+ it('toggle on and off', async () => {
+ const { depositVaultWithMorpho, owner } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await setAutoInvestFallbackEnabledMorphoTest(
+ { depositVaultWithMorpho, owner },
+ true,
+ );
+
+ await setAutoInvestFallbackEnabledMorphoTest(
+ { depositVaultWithMorpho, owner },
+ false,
+ );
+ });
+ });
- it('reject request: happy path', async () => {
- const {
- owner,
- depositVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithMorpho, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
-
- const request = await depositRequestTest(
- {
- depositVault: depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
+ describe('depositInstant()', async () => {
+ it('morphoDepositsEnabled but no vault for token: fallback to normal flow', async () => {
+ const {
+ owner,
+ depositVaultWithMorpho,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ morphoVaultMock,
+ } = await loadFixture(defaultDeploy);
+
+ await setMorphoDepositsEnabledTest(
+ { depositVaultWithMorpho, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithMorpho,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithMorpho, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
+
+ await depositInstantWithMorphoTest(
+ {
+ depositVaultWithMorpho,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ morphoVaultMock,
+ expectedMorphoDeposited: false,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('morphoDepositsEnabled, vault configured but deposit reverts, fallback enabled: fallback to normal flow', async () => {
+ const {
+ owner,
+ depositVaultWithMorpho,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ morphoVaultMock,
+ } = await loadFixture(defaultDeploy);
+
+ await setMorphoDepositsEnabledTest(
+ { depositVaultWithMorpho, owner },
+ true,
+ );
+ await setAutoInvestFallbackEnabledMorphoTest(
+ { depositVaultWithMorpho, owner },
+ true,
+ );
+
+ await setMorphoVaultTest(
+ { depositVaultWithMorpho, owner },
+ stableCoins.usdc.address,
+ morphoVaultMock.address,
+ );
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithMorpho,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithMorpho, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
+
+ await morphoVaultMock.setShouldRevertDeposit(true);
+
+ await depositInstantWithMorphoTest(
+ {
+ depositVaultWithMorpho,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ morphoVaultMock,
+ expectedMorphoDeposited: false,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: morphoDepositsEnabled, vault configured but deposit reverts, fallback disabled', async () => {
+ const {
+ owner,
+ depositVaultWithMorpho,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ morphoVaultMock,
+ } = await loadFixture(defaultDeploy);
+
+ await setMorphoDepositsEnabledTest(
+ { depositVaultWithMorpho, owner },
+ true,
+ );
+
+ await setMorphoVaultTest(
+ { depositVaultWithMorpho, owner },
+ stableCoins.usdc.address,
+ morphoVaultMock.address,
+ );
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithMorpho,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithMorpho, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
+
+ await morphoVaultMock.setShouldRevertDeposit(true);
+
+ await depositInstantWithMorphoTest(
+ {
+ depositVaultWithMorpho,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ morphoVaultMock,
+ expectedMorphoDeposited: false,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'AutoInvestFailed',
+ },
+ },
+ );
+ });
+
+ it('should fail: when Morpho deposit mints zero shares', async () => {
+ const {
+ owner,
+ depositVaultWithMorpho,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ morphoVaultMock,
+ } = await loadFixture(defaultDeploy);
+
+ await setMorphoDepositsEnabledTest(
+ { depositVaultWithMorpho, owner },
+ true,
+ );
+ await setMorphoVaultTest(
+ { depositVaultWithMorpho, owner },
+ stableCoins.usdc.address,
+ morphoVaultMock.address,
+ );
+ await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 0);
+ await morphoVaultMock.setExchangeRate(parseUnits('1000000000000'));
+
+ await mintToken(stableCoins.usdc, owner, 1);
+ await approveBase18(
+ owner,
+ stableCoins.usdc,
+ depositVaultWithMorpho,
+ 1,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithMorpho, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await depositInstantWithMorphoTest(
+ {
+ depositVaultWithMorpho,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ morphoVaultMock,
+ expectedMorphoDeposited: false,
+ },
+ stableCoins.usdc,
+ 0.000001,
+ {
+ revertCustomError: {
+ customErrorName: 'ZeroShares',
+ },
+ },
+ );
+ });
+
+ it('deposit 100 USDC when morphoDepositsEnabled is true', async () => {
+ const {
+ owner,
+ depositVaultWithMorpho,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ morphoVaultMock,
+ } = await loadFixture(defaultDeploy);
+
+ await setMorphoDepositsEnabledTest(
+ { depositVaultWithMorpho, owner },
+ true,
+ );
+
+ await setMorphoVaultTest(
+ { depositVaultWithMorpho, owner },
+ stableCoins.usdc.address,
+ morphoVaultMock.address,
+ );
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithMorpho,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithMorpho, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
+
+ await depositInstantWithMorphoTest(
+ {
+ depositVaultWithMorpho,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ morphoVaultMock,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('when morphoDepositsEnabled is false, normal DV flow', async () => {
+ const {
+ owner,
+ depositVaultWithMorpho,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ morphoVaultMock,
+ } = await loadFixture(defaultDeploy);
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithMorpho,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithMorpho, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
+
+ await depositInstantWithMorphoTest(
+ {
+ depositVaultWithMorpho,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ morphoVaultMock,
+ expectedMorphoDeposited: false,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('deposit with custom recipient, morphoDepositsEnabled is true', async () => {
+ const {
+ owner,
+ depositVaultWithMorpho,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ morphoVaultMock,
+ } = await loadFixture(defaultDeploy);
+
+ await setMorphoDepositsEnabledTest(
+ { depositVaultWithMorpho, owner },
+ true,
+ );
+
+ await setMorphoVaultTest(
+ { depositVaultWithMorpho, owner },
+ stableCoins.usdc.address,
+ morphoVaultMock.address,
+ );
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithMorpho,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithMorpho, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
+
+ await depositInstantWithMorphoTest(
+ {
+ depositVaultWithMorpho,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ morphoVaultMock,
+ customRecipient: regularAccounts[1],
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('deposit 100 DAI with morpho enabled (per-asset vault mapping)', async () => {
+ const {
+ owner,
+ depositVaultWithMorpho,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadFixture(defaultDeploy);
+
+ const daiMorphoVault = await new MorphoVaultMock__factory(
+ owner,
+ ).deploy(stableCoins.dai.address);
+ await stableCoins.dai.mint(
+ daiMorphoVault.address,
+ parseUnits('1000000'),
+ );
+
+ await setMorphoDepositsEnabledTest(
+ { depositVaultWithMorpho, owner },
+ true,
+ );
+
+ await setMorphoVaultTest(
+ { depositVaultWithMorpho, owner },
+ stableCoins.dai.address,
+ daiMorphoVault.address,
+ );
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVaultWithMorpho,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithMorpho, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ false,
+ );
+ await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
+
+ await depositInstantWithMorphoTest(
+ {
+ depositVaultWithMorpho,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ morphoVaultMock: daiMorphoVault,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('toggle mid-flight: morpho enabled then disabled', async () => {
+ const {
+ owner,
+ depositVaultWithMorpho,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ morphoVaultMock,
+ } = await loadFixture(defaultDeploy);
+
+ await setMorphoVaultTest(
+ { depositVaultWithMorpho, owner },
+ stableCoins.usdc.address,
+ morphoVaultMock.address,
+ );
+
+ await addPaymentTokenTest(
+ { vault: depositVaultWithMorpho, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
+
+ // Deposit 1: morpho enabled
+ await setMorphoDepositsEnabledTest(
+ { depositVaultWithMorpho, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 200);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithMorpho,
+ 200,
+ );
+
+ await depositInstantWithMorphoTest(
+ {
+ depositVaultWithMorpho,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ morphoVaultMock,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+
+ // Deposit 2: morpho disabled
+ await setMorphoDepositsEnabledTest(
+ { depositVaultWithMorpho, owner },
+ false,
+ );
+
+ await depositInstantWithMorphoTest(
+ {
+ depositVaultWithMorpho,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ morphoVaultMock,
+ expectedMorphoDeposited: false,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+ });
- await rejectRequestTest(
- {
- depositVault: depositVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- request.requestId!,
- );
+ describe('depositRequest()', () => {
+ it('deposit request 100 USDC with morpho auto-invest', async () => {
+ const {
+ owner,
+ depositVaultWithMorpho,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ morphoVaultMock,
+ } = await loadFixture(defaultDeploy);
+
+ await setMorphoDepositsEnabledTest(
+ { depositVaultWithMorpho, owner },
+ true,
+ );
+ await setMorphoVaultTest(
+ { depositVaultWithMorpho, owner },
+ stableCoins.usdc.address,
+ morphoVaultMock.address,
+ );
+ await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithMorpho,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithMorpho, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await depositRequestWithMorphoTest(
+ {
+ depositVaultWithMorpho,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ morphoVaultMock,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('deposit request with morpho auto-invest, deposit reverts, fallback enabled: fallback to normal flow', async () => {
+ const {
+ owner,
+ depositVaultWithMorpho,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ morphoVaultMock,
+ } = await loadFixture(defaultDeploy);
+
+ await setMorphoDepositsEnabledTest(
+ { depositVaultWithMorpho, owner },
+ true,
+ );
+ await setAutoInvestFallbackEnabledMorphoTest(
+ { depositVaultWithMorpho, owner },
+ true,
+ );
+ await setMorphoVaultTest(
+ { depositVaultWithMorpho, owner },
+ stableCoins.usdc.address,
+ morphoVaultMock.address,
+ );
+ await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithMorpho,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithMorpho, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await morphoVaultMock.setShouldRevertDeposit(true);
+
+ await depositRequestWithMorphoTest(
+ {
+ depositVaultWithMorpho,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ morphoVaultMock,
+ expectedMorphoDeposited: false,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: deposit request with morpho auto-invest, deposit reverts, fallback disabled', async () => {
+ const {
+ owner,
+ depositVaultWithMorpho,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ morphoVaultMock,
+ } = await loadFixture(defaultDeploy);
+
+ await setMorphoDepositsEnabledTest(
+ { depositVaultWithMorpho, owner },
+ true,
+ );
+ await setMorphoVaultTest(
+ { depositVaultWithMorpho, owner },
+ stableCoins.usdc.address,
+ morphoVaultMock.address,
+ );
+ await setMinAmountTest({ vault: depositVaultWithMorpho, owner }, 10);
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithMorpho,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithMorpho, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await morphoVaultMock.setShouldRevertDeposit(true);
+
+ await depositRequestWithMorphoTest(
+ {
+ depositVaultWithMorpho,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ morphoVaultMock,
+ expectedMorphoDeposited: false,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'AutoInvestFailed',
+ },
+ },
+ );
+ });
+ });
});
- });
-});
+ },
+);
diff --git a/test/unit/DepositVaultWithUSTB.test.ts b/test/unit/DepositVaultWithUSTB.test.ts
index d3b1d001..fcbcbfd8 100644
--- a/test/unit/DepositVaultWithUSTB.test.ts
+++ b/test/unit/DepositVaultWithUSTB.test.ts
@@ -1,6157 +1,349 @@
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
+import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { expect } from 'chai';
import { constants } from 'ethers';
-import { parseUnits } from 'ethers/lib/utils';
-import { ethers } from 'hardhat';
-import { encodeFnSelector } from '../../helpers/utils';
import {
- DepositVaultTest__factory,
- EUsdDepositVault__factory,
- ManageableVaultTester__factory,
- MBasisDepositVault__factory,
+ baseInitParamsDv,
+ depositVaultSuits,
+} from './suits/deposit-vault.suits';
+
+import {
+ DepositVaultWithUSTB__factory,
+ DepositVaultWithUSTBTest__factory,
} from '../../typechain-types';
-import { acErrors, blackList, greenList } from '../common/ac.helpers';
+import { acErrors } from '../common/ac.helpers';
import {
approveBase18,
+ InitializeParamCase,
mintToken,
- pauseVault,
- pauseVaultFn,
+ validateImplementation,
} from '../common/common.helpers';
-import { setRoundData } from '../common/data-feed.helpers';
import {
- depositInstantWithUstbTest,
setMockUstbStablecoinConfig,
setUstbDepositsEnabledTest,
+ depositInstantWithUstbTest,
} from '../common/deposit-vault-ustb.helpers';
-import {
- approveRequestTest,
- depositInstantTest,
- depositRequestTest,
- rejectRequestTest,
- safeApproveRequestTest,
- safeBulkApproveRequestTest,
-} from '../common/deposit-vault.helpers';
import { defaultDeploy } from '../common/fixtures';
-import { greenListEnable } from '../common/greenlist.helpers';
import {
addPaymentTokenTest,
- addWaivedFeeAccountTest,
- changeTokenAllowanceTest,
- removePaymentTokenTest,
- removeWaivedFeeAccountTest,
- setInstantFeeTest,
- setInstantDailyLimitTest,
setMinAmountTest,
- setMinAmountToDepositTest,
- setVariabilityToleranceTest,
- withdrawTest,
- changeTokenFeeTest,
} from '../common/manageable-vault.helpers';
-import { sanctionUser } from '../common/with-sanctions-list.helpers';
-
-describe('DepositVaultWithUSTB', function () {
- it('deployment', async () => {
- const {
- depositVaultWithUSTB,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- roles,
- ustbToken,
- } = await loadFixture(defaultDeploy);
-
- expect(await depositVaultWithUSTB.mToken()).eq(mTBILL.address);
-
- expect(await depositVaultWithUSTB.paused()).eq(false);
-
- expect(await depositVaultWithUSTB.tokensReceiver()).eq(
- tokensReceiver.address,
- );
- expect(await depositVaultWithUSTB.feeReceiver()).eq(feeReceiver.address);
-
- expect(await depositVaultWithUSTB.ONE_HUNDRED_PERCENT()).eq('10000');
-
- expect(await depositVaultWithUSTB.minMTokenAmountForFirstDeposit()).eq('0');
- expect(await depositVaultWithUSTB.minAmount()).eq(parseUnits('100'));
-
- expect(await depositVaultWithUSTB.instantFee()).eq('100');
-
- expect(await depositVaultWithUSTB.instantDailyLimit()).eq(
- parseUnits('100000'),
- );
-
- expect(await depositVaultWithUSTB.mTokenDataFeed()).eq(
- mTokenToUsdDataFeed.address,
- );
- expect(await depositVaultWithUSTB.variationTolerance()).eq(1);
-
- expect(await depositVaultWithUSTB.vaultRole()).eq(
- roles.tokenRoles.mTBILL.depositVaultAdmin,
- );
+import {
+ initializeDvWithUstb,
+ InitializerParamsDvWithUstb,
+} from '../common/vault-initializer.helpers';
+
+const baseInitParamsDvWithUstb = (
+ fixture: Parameters[0],
+): InitializerParamsDvWithUstb => ({
+ ...baseInitParamsDv(fixture),
+ ustbToken: fixture.ustbToken,
+});
- expect(await depositVaultWithUSTB.MANUAL_FULLFILMENT_TOKEN()).eq(
- ethers.constants.AddressZero,
- );
+const dvWithUstbInitializeParamCases: InitializeParamCase[] =
+ [
+ {
+ title: 'ustbToken is zero address',
+ params: { ustbToken: constants.AddressZero },
+ revertCustomError: {
+ customErrorName: 'InvalidAddress',
+ args: [constants.AddressZero],
+ },
+ },
+ ];
+
+depositVaultSuits(
+ 'DepositVaultWithUSTB',
+ defaultDeploy,
+ {
+ createNew: async (owner: SignerWithAddress) =>
+ new DepositVaultWithUSTBTest__factory(owner).deploy(),
+ key: 'depositVaultWithUSTB',
+ },
+ async (fixture) => {
+ const { depositVaultWithUSTB, ustbToken } = fixture;
expect(await depositVaultWithUSTB.ustb()).eq(ustbToken.address);
- });
-
- it('failing deployment', async () => {
- const {
- accessControl,
- mTBILL,
- owner,
- mTokenToUsdDataFeed,
- feeReceiver,
- tokensReceiver,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
- const depositVaultWithUSTB = await new DepositVaultTest__factory(
- owner,
- ).deploy();
-
- await expect(
- depositVaultWithUSTB.initialize(
- ethers.constants.AddressZero,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- parseUnits('100'),
- constants.MaxUint256,
- ),
- ).to.be.reverted;
- await expect(
- depositVaultWithUSTB.initialize(
- accessControl.address,
- {
- mToken: ethers.constants.AddressZero,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- parseUnits('100'),
- constants.MaxUint256,
- ),
- ).to.be.reverted;
- await expect(
- depositVaultWithUSTB.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: ethers.constants.AddressZero,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- parseUnits('100'),
- constants.MaxUint256,
- ),
- ).to.be.reverted;
- await expect(
- depositVaultWithUSTB.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: ethers.constants.AddressZero,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- parseUnits('100'),
- constants.MaxUint256,
- ),
- ).to.be.reverted;
- await expect(
- depositVaultWithUSTB.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: ethers.constants.AddressZero,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- parseUnits('100'),
- constants.MaxUint256,
- ),
- ).to.be.reverted;
- await expect(
- depositVaultWithUSTB.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100001,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- parseUnits('100'),
- constants.MaxUint256,
- ),
- ).to.be.reverted;
- });
-
- it('MBasisDepositVault', async () => {
- const fixture = await loadFixture(defaultDeploy);
-
- const tester = await new MBasisDepositVault__factory(
- fixture.owner,
- ).deploy();
-
- expect(await tester.vaultRole()).eq(
- await tester.M_BASIS_DEPOSIT_VAULT_ADMIN_ROLE(),
- );
- });
-
- it('EUsdDepositVault', async () => {
- const fixture = await loadFixture(defaultDeploy);
-
- const tester = await new EUsdDepositVault__factory(fixture.owner).deploy();
-
- expect(await tester.vaultRole()).eq(
- await tester.E_USD_DEPOSIT_VAULT_ADMIN_ROLE(),
- );
- });
-
- describe('initialization', () => {
- it('should fail: cal; initialize() when already initialized', async () => {
- const { depositVaultWithUSTB } = await loadFixture(defaultDeploy);
-
- await expect(
- depositVaultWithUSTB[
- 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,uint256,uint256,address)'
- ](
- constants.AddressZero,
- {
- mToken: constants.AddressZero,
- mTokenDataFeed: constants.AddressZero,
- },
- {
- feeReceiver: constants.AddressZero,
- tokensReceiver: constants.AddressZero,
- },
- {
- instantFee: 0,
- instantDailyLimit: 0,
- },
- constants.AddressZero,
- 0,
- 0,
- 0,
- 0,
- constants.AddressZero,
- ),
- ).revertedWith('Initializable: contract is already initialized');
- });
-
- it('should fail: call with initializing == false', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initializeWithoutInitializer(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- ),
- ).revertedWith('Initializable: contract is not initializing');
- });
-
- it('should fail: when _tokensReceiver == address(this)', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- feeReceiver,
- mTokenToUsdDataFeed,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: vault.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- ),
- ).revertedWith('invalid address');
- });
- it('should fail: when _feeReceiver == address(this)', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- mTokenToUsdDataFeed,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: vault.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- ),
- ).revertedWith('invalid address');
- });
- it('should fail: when limit = 0', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: 0,
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- ),
- ).revertedWith('zero limit');
- });
- it('should fail: when mToken dataFeed address zero', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: constants.AddressZero,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- ),
- ).revertedWith('zero address');
- });
- it('should fail: when variationTolarance zero', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mockedSanctionsList,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 0,
- parseUnits('100'),
- ),
- ).revertedWith('fee == 0');
- });
- });
-
- describe('setMinMTokenAmountForFirstDeposit()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { owner, depositVaultWithUSTB, regularAccounts } =
- await loadFixture(defaultDeploy);
-
- await setMinAmountToDepositTest(
- { depositVault: depositVaultWithUSTB, owner },
- 1.1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { owner, depositVaultWithUSTB } = await loadFixture(defaultDeploy);
- await setMinAmountToDepositTest(
- { depositVault: depositVaultWithUSTB, owner },
- 1.1,
- );
- });
- });
-
- describe('setMinAmount()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { owner, depositVaultWithUSTB, regularAccounts } =
- await loadFixture(defaultDeploy);
-
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 1.1, {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
+ await validateImplementation(DepositVaultWithUSTB__factory);
+ },
+ {
+ deployUninitialized: (fixture) =>
+ new DepositVaultWithUSTBTest__factory(fixture.owner).deploy(),
+ initialize: async (fixture, params, opt) => {
+ await initializeDvWithUstb(
+ { ...baseInitParamsDvWithUstb(fixture), ...params },
+ opt?.contract,
+ opt,
+ );
+ },
+ extraParamCases: dvWithUstbInitializeParamCases,
+ },
+ async (defaultDeploy) => {
+ describe('DepositVaultWithUSTB', function () {
+ describe('depositInstant()', async () => {
+ it('should fail: when ustbDepositsEnabled is true and payment token is not set in USTB contract', async () => {
+ const {
+ owner,
+ depositVaultWithUSTB,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ ustbToken,
+ } = await loadFixture(defaultDeploy);
+
+ await setUstbDepositsEnabledTest(
+ {
+ depositVaultWithUSTB,
+ owner,
+ },
+ true,
+ );
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithUSTB,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithUSTB, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
+
+ await depositInstantWithUstbTest(
+ {
+ depositVaultWithUSTB,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ ustbToken,
+ expectedUstbDeposited: false,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'UnsupportedUSTBToken',
+ },
+ },
+ );
+ });
+
+ it('should fail: when ustbDepositsEnabled is true and payment token is set in USTB contract but fee is not 0', async () => {
+ const {
+ owner,
+ depositVaultWithUSTB,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ ustbToken,
+ } = await loadFixture(defaultDeploy);
+
+ await setUstbDepositsEnabledTest(
+ {
+ depositVaultWithUSTB,
+ owner,
+ },
+ true,
+ );
+
+ await setMockUstbStablecoinConfig({ ustbToken }, stableCoins.usdc, {
+ fee: 100,
+ sweepDestination: ustbToken.address,
+ });
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithUSTB,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithUSTB, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
+
+ await depositInstantWithUstbTest(
+ {
+ depositVaultWithUSTB,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ ustbToken,
+ expectedUstbDeposited: false,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'USTBFeeNotZero',
+ },
+ },
+ );
+ });
+
+ it('deposit 100 USDC when ustbDepositsEnabled is true', async () => {
+ const {
+ owner,
+ depositVaultWithUSTB,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ ustbToken,
+ } = await loadFixture(defaultDeploy);
+
+ await setUstbDepositsEnabledTest(
+ {
+ depositVaultWithUSTB,
+ owner,
+ },
+ true,
+ );
+ await setMockUstbStablecoinConfig({ ustbToken }, stableCoins.usdc);
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithUSTB,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithUSTB, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
+
+ await depositInstantWithUstbTest(
+ {
+ depositVaultWithUSTB,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ ustbToken,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('when ustbDepositsEnabled is false and payment token is not set in USTB contract', async () => {
+ const {
+ owner,
+ depositVaultWithUSTB,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ ustbToken,
+ } = await loadFixture(defaultDeploy);
+
+ await mintToken(stableCoins.usdc, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.usdc,
+ depositVaultWithUSTB,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVaultWithUSTB, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
+
+ await depositInstantWithUstbTest(
+ {
+ depositVaultWithUSTB,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ ustbToken,
+ },
+ stableCoins.usdc,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
});
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { owner, depositVaultWithUSTB } = await loadFixture(defaultDeploy);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 1.1);
- });
- });
-
- describe('setInstantDailyLimit()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { owner, depositVaultWithUSTB, regularAccounts } =
- await loadFixture(defaultDeploy);
-
- await setInstantDailyLimitTest(
- { vault: depositVaultWithUSTB, owner },
- parseUnits('1000'),
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- },
- );
- });
-
- it('should fail: try to set 0 limit', async () => {
- const { owner, depositVaultWithUSTB } = await loadFixture(defaultDeploy);
-
- await setInstantDailyLimitTest(
- { vault: depositVaultWithUSTB, owner },
- constants.Zero,
- {
- revertMessage: 'MV: limit zero',
- },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { owner, depositVaultWithUSTB } = await loadFixture(defaultDeploy);
- await setInstantDailyLimitTest(
- { vault: depositVaultWithUSTB, owner },
- parseUnits('1000'),
- );
- });
- });
-
- describe('addPaymentToken()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithUSTB, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- ethers.constants.AddressZero,
- ethers.constants.AddressZero,
- 0,
- false,
- constants.MaxUint256,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
-
- it('should fail: when token is already added', async () => {
- const { depositVaultWithUSTB, stableCoins, owner, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- false,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- false,
- constants.MaxUint256,
- {
- revertMessage: 'MV: already added',
- },
- );
- });
-
- it('should fail: when token dataFeed address zero', async () => {
- const { depositVaultWithUSTB, stableCoins, owner } = await loadFixture(
- defaultDeploy,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- constants.AddressZero,
- 0,
- false,
- constants.MaxUint256,
- {
- revertMessage: 'zero address',
- },
- );
- });
-
- it('call when allowance is zero', async () => {
- const { depositVaultWithUSTB, stableCoins, owner, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- false,
- constants.Zero,
- );
- });
-
- it('call when allowance is not uint256 max', async () => {
- const { depositVaultWithUSTB, stableCoins, owner, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- false,
- parseUnits('100'),
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithUSTB, stableCoins, owner, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- false,
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role and add 3 options on a row', async () => {
- const { depositVaultWithUSTB, stableCoins, owner, dataFeed } =
- await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.usdt,
- dataFeed.address,
- 0,
- true,
- );
- });
- });
-
- describe('addWaivedFeeAccount()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithUSTB, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: depositVaultWithUSTB, owner },
- ethers.constants.AddressZero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('should fail: if account fee already waived', async () => {
- const { depositVaultWithUSTB, owner } = await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: depositVaultWithUSTB, owner },
- owner.address,
- );
- await addWaivedFeeAccountTest(
- { vault: depositVaultWithUSTB, owner },
- owner.address,
- { revertMessage: 'MV: already added' },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithUSTB, owner } = await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: depositVaultWithUSTB, owner },
- owner.address,
- );
- });
- });
-
- describe('removeWaivedFeeAccount()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithUSTB, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await removeWaivedFeeAccountTest(
- { vault: depositVaultWithUSTB, owner },
- ethers.constants.AddressZero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('should fail: if account not found in restriction', async () => {
- const { depositVaultWithUSTB, owner } = await loadFixture(defaultDeploy);
- await removeWaivedFeeAccountTest(
- { vault: depositVaultWithUSTB, owner },
- owner.address,
- { revertMessage: 'MV: not found' },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithUSTB, owner } = await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: depositVaultWithUSTB, owner },
- owner.address,
- );
- await removeWaivedFeeAccountTest(
- { vault: depositVaultWithUSTB, owner },
- owner.address,
- );
- });
- });
-
- describe('setFee()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithUSTB, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await setInstantFeeTest(
- { vault: depositVaultWithUSTB, owner },
- ethers.constants.Zero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('should fail: if new value greater then 100%', async () => {
- const { depositVaultWithUSTB, owner } = await loadFixture(defaultDeploy);
- await setInstantFeeTest({ vault: depositVaultWithUSTB, owner }, 10001, {
- revertMessage: 'fee > 100%',
+ describe('setUstbDepositsEnabled', () => {
+ it('should fail: call from address without vault admin role', async () => {
+ const { depositVaultWithUSTB, owner, regularAccounts } =
+ await loadFixture(defaultDeploy);
+ await setUstbDepositsEnabledTest(
+ { depositVaultWithUSTB, owner },
+ true,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ },
+ );
+ });
+
+ it('call from address with vault admin role', async () => {
+ const { depositVaultWithUSTB, owner } = await loadFixture(
+ defaultDeploy,
+ );
+ await setUstbDepositsEnabledTest(
+ { depositVaultWithUSTB, owner },
+ true,
+ );
+ });
+
+ it('set true when ustbDepositsEnabled is already true', async () => {
+ const { depositVaultWithUSTB, owner } = await loadFixture(
+ defaultDeploy,
+ );
+ await setUstbDepositsEnabledTest(
+ { depositVaultWithUSTB, owner },
+ true,
+ );
+ await setUstbDepositsEnabledTest(
+ { depositVaultWithUSTB, owner },
+ true,
+ );
+ });
+
+ it('set false when ustbDepositsEnabled is already false', async () => {
+ const { depositVaultWithUSTB, owner } = await loadFixture(
+ defaultDeploy,
+ );
+ await setUstbDepositsEnabledTest(
+ { depositVaultWithUSTB, owner },
+ false,
+ );
+ });
});
});
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithUSTB, owner } = await loadFixture(defaultDeploy);
- await setInstantFeeTest({ vault: depositVaultWithUSTB, owner }, 100);
- });
- });
-
- describe('setVariabilityTolerance()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithUSTB, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await setVariabilityToleranceTest(
- { vault: depositVaultWithUSTB, owner },
- ethers.constants.Zero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('should fail: if new value zero', async () => {
- const { depositVaultWithUSTB, owner } = await loadFixture(defaultDeploy);
- await setVariabilityToleranceTest(
- { vault: depositVaultWithUSTB, owner },
- ethers.constants.Zero,
- { revertMessage: 'fee == 0' },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithUSTB, owner } = await loadFixture(defaultDeploy);
- await setVariabilityToleranceTest(
- { vault: depositVaultWithUSTB, owner },
- 100,
- );
- });
- });
-
- describe('removePaymentToken()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithUSTB, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await removePaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- ethers.constants.AddressZero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
-
- it('should fail: when token is not exists', async () => {
- const { owner, depositVaultWithUSTB, stableCoins } = await loadFixture(
- defaultDeploy,
- );
- await removePaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai.address,
- { revertMessage: 'MV: not exists' },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithUSTB, stableCoins, owner, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await removePaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai.address,
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role and add 3 options on a row', async () => {
- const { depositVaultWithUSTB, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.usdt,
- dataFeed.address,
- 0,
- true,
- );
-
- await removePaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai.address,
- );
- await removePaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.usdc.address,
- );
- await removePaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.usdt.address,
- );
-
- await removePaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.usdt.address,
- { revertMessage: 'MV: not exists' },
- );
- });
- });
-
- describe('withdrawToken()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithUSTB, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await withdrawTest(
- { vault: depositVaultWithUSTB, owner },
- ethers.constants.AddressZero,
- 0,
- ethers.constants.AddressZero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
-
- it('should fail: when there is no token in vault', async () => {
- const { owner, depositVaultWithUSTB, regularAccounts, stableCoins } =
- await loadFixture(defaultDeploy);
- await withdrawTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- 1,
- regularAccounts[0],
- { revertMessage: 'ERC20: transfer amount exceeds balance' },
- );
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithUSTB, regularAccounts, stableCoins, owner } =
- await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, depositVaultWithUSTB, 1);
- await withdrawTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- 1,
- regularAccounts[0],
- );
- });
- });
-
- describe('freeFromMinAmount()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const { depositVaultWithUSTB, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- depositVaultWithUSTB
- .connect(regularAccounts[0])
- .freeFromMinAmount(regularAccounts[1].address, true),
- ).to.be.revertedWith('WMAC: hasnt role');
- });
- it('should not fail', async () => {
- const { depositVaultWithUSTB, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- depositVaultWithUSTB.freeFromMinAmount(
- regularAccounts[0].address,
- true,
- ),
- ).to.not.reverted;
-
- expect(
- await depositVaultWithUSTB.isFreeFromMinAmount(
- regularAccounts[0].address,
- ),
- ).to.eq(true);
- });
- it('should fail: already in list', async () => {
- const { depositVaultWithUSTB, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- depositVaultWithUSTB.freeFromMinAmount(
- regularAccounts[0].address,
- true,
- ),
- ).to.not.reverted;
-
- expect(
- await depositVaultWithUSTB.isFreeFromMinAmount(
- regularAccounts[0].address,
- ),
- ).to.eq(true);
-
- await expect(
- depositVaultWithUSTB.freeFromMinAmount(
- regularAccounts[0].address,
- true,
- ),
- ).to.revertedWith('DV: already free');
- });
- });
-
- describe('changeTokenAllowance()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithUSTB, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await changeTokenAllowanceTest(
- { vault: depositVaultWithUSTB, owner },
- ethers.constants.AddressZero,
- 0,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('should fail: token not exist', async () => {
- const { depositVaultWithUSTB, owner, stableCoins } = await loadFixture(
- defaultDeploy,
- );
- await changeTokenAllowanceTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai.address,
- 0,
- { revertMessage: 'MV: token not exists' },
- );
- });
- it('should fail: allowance zero', async () => {
- const { depositVaultWithUSTB, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai.address,
- 0,
- { revertMessage: 'MV: zero allowance' },
- );
- });
- it('should fail: if mint exceed allowance', async () => {
- const {
- depositVaultWithUSTB,
- stableCoins,
- owner,
- dataFeed,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, owner, 100000);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100000);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai.address,
- 100,
- );
-
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'MV: exceed allowance',
- },
- );
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'MV: exceed allowance',
- },
- );
- });
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithUSTB, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai.address,
- 100000000,
- );
- });
- it('should decrease if allowance < UINT_MAX', async () => {
- const {
- depositVaultWithUSTB,
- stableCoins,
- owner,
- dataFeed,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, owner, 100000);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100000);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai.address,
- parseUnits('1000'),
- );
-
- const tokenConfigBefore = await depositVaultWithUSTB.tokensConfig(
- stableCoins.dai.address,
- );
-
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 999,
- );
-
- const tokenConfigAfter = await depositVaultWithUSTB.tokensConfig(
- stableCoins.dai.address,
- );
-
- expect(tokenConfigBefore.allowance.sub(tokenConfigAfter.allowance)).eq(
- parseUnits('999'),
- );
- });
- it('should not decrease if allowance = UINT_MAX', async () => {
- const {
- depositVaultWithUSTB,
- stableCoins,
- owner,
- dataFeed,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, owner, 100000);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100000);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai.address,
- constants.MaxUint256,
- );
-
- const tokenConfigBefore = await depositVaultWithUSTB.tokensConfig(
- stableCoins.dai.address,
- );
-
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 999,
- );
-
- const tokenConfigAfter = await depositVaultWithUSTB.tokensConfig(
- stableCoins.dai.address,
- );
-
- expect(tokenConfigBefore.allowance).eq(tokenConfigAfter.allowance);
- });
- });
-
- describe('changeTokenFee()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithUSTB, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await changeTokenFeeTest(
- { vault: depositVaultWithUSTB, owner },
- ethers.constants.AddressZero,
- 0,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('should fail: token not exist', async () => {
- const { depositVaultWithUSTB, owner, stableCoins } = await loadFixture(
- defaultDeploy,
- );
- await changeTokenFeeTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai.address,
- 0,
- { revertMessage: 'MV: token not exists' },
- );
- });
- it('should fail: fee > 100%', async () => {
- const { depositVaultWithUSTB, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenFeeTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai.address,
- 10001,
- { revertMessage: 'fee > 100%' },
- );
- });
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { depositVaultWithUSTB, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenFeeTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai.address,
- 100,
- );
- });
- });
-
- describe('depositInstant()', async () => {
- it('should fail: when there is no token in vault', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'MV: token not exists',
- },
- );
- });
-
- it('should fail: when trying to deposit 0 amount', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 0,
- {
- revertMessage: 'DV: invalid amount',
- },
- );
- });
-
- it('should fail: when function paused', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector(
- 'depositInstant(address,uint256,uint256,bytes32)',
- );
- await pauseVaultFn(depositVaultWithUSTB, selector);
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: when rounding is invalid', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100.0000000001,
- {
- revertMessage: 'MV: invalid rounding',
- },
- );
- });
-
- it('should fail: call with insufficient allowance', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'ERC20: insufficient allowance',
- },
- );
- });
-
- it('should fail: call with insufficient balance', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'ERC20: transfer amount exceeds balance',
- },
- );
- });
-
- it('should fail: dataFeed rate 0 ', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mockedAggregator,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 10);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await mintToken(stableCoins.dai, owner, 100_000);
- await setRoundData({ mockedAggregator }, 0);
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'DF: feed is deprecated',
- },
- );
- });
-
- it('should fail: call for amount < minAmountToDepositTest', async () => {
- const {
- depositVaultWithUSTB,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(stableCoins.dai, owner, 100_000);
- await approveBase18(
- owner,
- stableCoins.dai,
- depositVaultWithUSTB,
- 100_000,
- );
-
- await setMinAmountToDepositTest(
- { depositVault: depositVaultWithUSTB, owner },
- 100_000,
- );
- await setInstantDailyLimitTest(
- { vault: depositVaultWithUSTB, owner },
- 150_000,
- );
-
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'DV: mint amount < min',
- },
- );
- });
-
- it('should fail: call for amount < minAmount', async () => {
- const {
- depositVaultWithUSTB,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(stableCoins.dai, owner, 100_000);
- await approveBase18(
- owner,
- stableCoins.dai,
- depositVaultWithUSTB,
- 100_000,
- );
-
- await setMinAmountToDepositTest(
- { depositVault: depositVaultWithUSTB, owner },
- 100_000,
- );
- await setInstantDailyLimitTest(
- { vault: depositVaultWithUSTB, owner },
- 150_000,
- );
-
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 99,
- {
- revertMessage: 'DV: mToken amount < min',
- },
- );
- });
-
- it('should fail: if exceed allowance of deposit for token', async () => {
- const {
- depositVaultWithUSTB,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(stableCoins.dai, owner, 100_000);
- await changeTokenAllowanceTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai.address,
- 100,
- );
- await approveBase18(
- owner,
- stableCoins.dai,
- depositVaultWithUSTB,
- 100_000,
- );
-
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'MV: exceed allowance',
- },
- );
- });
-
- it('should fail: if mint limit exceeded', async () => {
- const {
- depositVaultWithUSTB,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(stableCoins.dai, owner, 100_000);
- await setInstantDailyLimitTest(
- { vault: depositVaultWithUSTB, owner },
- 1000,
- );
-
- await approveBase18(
- owner,
- stableCoins.dai,
- depositVaultWithUSTB,
- 100_000,
- );
-
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'MV: exceed limit',
- },
- );
- });
-
- it('should fail: if min receive amount greater then actual', async () => {
- const {
- depositVaultWithUSTB,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(stableCoins.dai, owner, 100_000);
-
- await approveBase18(
- owner,
- stableCoins.dai,
- depositVaultWithUSTB,
- 100_000,
- );
-
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- minAmount: parseUnits('100000'),
- },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'DV: minReceiveAmount > actual',
- },
- );
- });
-
- it('should fail: if some fee = 100%', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 10000,
- true,
- );
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'DV: mToken amount < min',
- },
- );
-
- await removePaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setInstantFeeTest({ vault: depositVaultWithUSTB, owner }, 10000);
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- { revertMessage: 'DV: mToken amount < min' },
- );
- });
-
- it('should fail: greenlist enabled and user not in greenlist ', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await depositVaultWithUSTB.setGreenlistEnable(true);
-
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: user in blacklist ', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: user in sanctions list', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- regularAccounts[0],
- );
-
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('should fail: greenlist enabled and recipient not in greenlist (custom recipient overload)', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await depositVaultWithUSTB.setGreenlistEnable(true);
-
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: recipient in blacklist (custom recipient overload)', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- customRecipient,
- );
-
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: recipient in sanctions list (custom recipient overload)', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- customRecipient,
- );
-
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('should fail: when function paused (custom recipient overload)', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- customRecipient,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector(
- 'depositInstant(address,uint256,uint256,bytes32,address)',
- );
- await pauseVaultFn(depositVaultWithUSTB, selector);
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: when ustbDepositsEnabled is true and payment token is not set in USTB contract', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- ustbToken,
- } = await loadFixture(defaultDeploy);
-
- await setUstbDepositsEnabledTest(
- {
- depositVaultWithUSTB,
- owner,
- },
- true,
- );
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositInstantWithUstbTest(
- {
- depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- ustbToken,
- expectedUstbDeposited: false,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'DVU: unsupported USTB token',
- },
- );
- });
-
- it('should fail: when ustbDepositsEnabled is true and payment token is set in USTB contract but fee is not 0', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- ustbToken,
- } = await loadFixture(defaultDeploy);
-
- await setUstbDepositsEnabledTest(
- {
- depositVaultWithUSTB,
- owner,
- },
- true,
- );
-
- await setMockUstbStablecoinConfig({ ustbToken }, stableCoins.usdc, {
- fee: 100,
- sweepDestination: ustbToken.address,
- });
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositInstantWithUstbTest(
- {
- depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- ustbToken,
- expectedUstbDeposited: false,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'DVU: USTB fee is not 0',
- },
- );
- });
-
- it('deposit 100 DAI, greenlist enabled and user in greenlist ', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- greenListableTester,
- mTokenToUsdDataFeed,
- accessControl,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await depositVaultWithUSTB.setGreenlistEnable(true);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit 100 USDC when ustbDepositsEnabled is true', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- ustbToken,
- } = await loadFixture(defaultDeploy);
-
- await setUstbDepositsEnabledTest(
- {
- depositVaultWithUSTB,
- owner,
- },
- true,
- );
- await setMockUstbStablecoinConfig({ ustbToken }, stableCoins.usdc);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositInstantWithUstbTest(
- {
- depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- ustbToken,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('when ustbDepositsEnabled is false and payment token is not set in USTB contract', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- ustbToken,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- depositVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositInstantWithUstbTest(
- {
- depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- ustbToken,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit 100 DAI, greenlist enabled and user in greenlist, tokenIn not stablecoin', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- greenListableTester,
- mTokenToUsdDataFeed,
- accessControl,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await depositVaultWithUSTB.setGreenlistEnable(true);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- false,
- );
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit 100 DAI, when price of stable is 1.03$ and mToken price is 5$', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- });
-
- it('deposit 100 DAI, when price of stable is 1.03$ and mToken price is 5$ and token fee 1%', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- });
-
- it('deposit 100 DAI, when price of stable is 1.03$ and mToken price is 5$ without checking of minDepositAmount', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await depositVaultWithUSTB.freeFromMinAmount(owner.address, true);
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- });
-
- it('deposit 100 DAI, when price of stable is 1.03$ and mToken price is 5$ and user in waivedFeeRestriction', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await addWaivedFeeAccountTest(
- { vault: depositVaultWithUSTB, owner },
- owner.address,
- );
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- waivedFee: true,
- },
- stableCoins.dai,
- 100,
- );
- });
-
- it('deposit 100 DAI (custom recipient overload)', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- greenListableTester,
- mTokenToUsdDataFeed,
- accessControl,
- regularAccounts,
- dataFeed,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit 100 DAI when recipient == msg.sender (custom recipient overload)', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient: regularAccounts[0],
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit 100 DAI when other overload of depositInstant is paused (custom recipient overload)', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- depositVaultWithUSTB,
- encodeFnSelector('depositInstant(address,uint256,uint256,bytes32)'),
- );
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit 100 DAI when other overload of depositInstant is paused', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- depositVaultWithUSTB,
- encodeFnSelector(
- 'depositInstant(address,uint256,uint256,bytes32,address)',
- ),
- );
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
- });
-
- describe('depositRequest()', async () => {
- it('should fail: when there is no token in vault', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'MV: token not exists',
- },
- );
- });
-
- it('should fail: when trying to deposit 0 amount', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 0,
- {
- revertMessage: 'DV: invalid amount',
- },
- );
- });
-
- it('should fail: when function paused', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector(
- 'depositRequest(address,uint256,bytes32)',
- );
- await pauseVaultFn(depositVaultWithUSTB, selector);
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: when function paused (custom recipient overload)', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- customRecipient,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector(
- 'depositRequest(address,uint256,bytes32,address)',
- );
- await pauseVaultFn(depositVaultWithUSTB, selector);
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: when rounding is invalid', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100.0000000001,
- {
- revertMessage: 'MV: invalid rounding',
- },
- );
- });
-
- it('should fail: call with insufficient allowance', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'ERC20: insufficient allowance',
- },
- );
- });
-
- it('should fail: call with insufficient balance', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'ERC20: transfer amount exceeds balance',
- },
- );
- });
-
- it('should fail: dataFeed rate 0 ', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mockedAggregator,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 10);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await mintToken(stableCoins.dai, owner, 100_000);
- await setRoundData({ mockedAggregator }, 0);
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'DF: feed is deprecated',
- },
- );
- });
-
- it('should fail: call for amount < minAmountToDepositTest', async () => {
- const {
- depositVaultWithUSTB,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(stableCoins.dai, owner, 100_000);
- await approveBase18(
- owner,
- stableCoins.dai,
- depositVaultWithUSTB,
- 100_000,
- );
-
- await setMinAmountToDepositTest(
- { depositVault: depositVaultWithUSTB, owner },
- 100_000,
- );
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'DV: mint amount < min',
- },
- );
- });
-
- it('should fail: if exceed allowance of deposit for token', async () => {
- const {
- depositVaultWithUSTB,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(stableCoins.dai, owner, 100_000);
- await changeTokenAllowanceTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai.address,
- 100,
- );
- await approveBase18(
- owner,
- stableCoins.dai,
- depositVaultWithUSTB,
- 100_000,
- );
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'MV: exceed allowance',
- },
- );
- });
-
- it('should fail: if token fee = 100%', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 10000,
- true,
- );
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'DV: mToken amount < min',
- },
- );
- });
-
- it('should fail: greenlist enabled and user not in greenlist ', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await depositVaultWithUSTB.setGreenlistEnable(true);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: user in blacklist ', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: user in sanctionlist ', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- regularAccounts[0],
- );
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('should fail: greenlist enabled and recipient not in greenlist (custom recipient overload)', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await depositVaultWithUSTB.setGreenlistEnable(true);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: recipient in blacklist (custom recipient overload)', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- customRecipient,
- );
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: recipient in sanctionlist (custom recipient overload)', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- customRecipient,
- );
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('deposit 100 DAI, greenlist enabled and user in greenlist ', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- greenListableTester,
- mTokenToUsdDataFeed,
- accessControl,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await greenListEnable(
- { greenlistable: greenListableTester, owner },
- true,
- );
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit request with 100 DAI, when price of stable is 1.03$ and mToken price is 5$', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- });
-
- it('deposit request with 100 DAI, when price of stable is 1.03$ and mToken price is 5$ and token fee 1%', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- });
-
- it('deposit request with 100 DAI, when price of stable is 1.03$ and mToken price is 5$ without checking of minDepositAmount', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await depositVaultWithUSTB.freeFromMinAmount(owner.address, true);
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- });
-
- it('deposit request with 100 DAI, when price of stable is 1.03$ and mToken price is 5$ and user in waivedFeeRestriction', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await addWaivedFeeAccountTest(
- { vault: depositVaultWithUSTB, owner },
- owner.address,
- );
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- waivedFee: true,
- },
- stableCoins.dai,
- 100,
- );
- });
-
- it('deposit 100 (custom recipient overload)', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit 100 when recipient == msg.sender (custom recipient overload)', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient: regularAccounts[0],
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit 100 DAI when other overload of depositRequest is paused (custom recipient overload)', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- depositVaultWithUSTB,
- encodeFnSelector('depositRequest(address,uint256,bytes32)'),
- );
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('deposit 100 DAI when other overload of depositRequest is paused', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- depositVaultWithUSTB,
- encodeFnSelector('depositRequest(address,uint256,bytes32,address)'),
- );
-
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
- });
-
- describe('approveRequest()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- depositVaultWithUSTB,
- regularAccounts,
- mTokenToUsdDataFeed,
- mTBILL,
- } = await loadFixture(defaultDeploy);
- await approveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 1,
- parseUnits('5'),
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await approveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 1,
- parseUnits('5'),
- {
- revertMessage: 'DV: request not exist',
- },
- );
- });
-
- it('should fail: request already precessed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await approveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- requestId,
- parseUnits('5'),
- );
- await approveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- requestId,
- parseUnits('5'),
- { revertMessage: 'DV: request not pending' },
- );
- });
-
- it('approve request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await approveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- requestId,
- parseUnits('5'),
- );
- });
- });
-
- describe('safeApproveRequest()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- depositVaultWithUSTB,
- regularAccounts,
- mTokenToUsdDataFeed,
- mTBILL,
- } = await loadFixture(defaultDeploy);
- await safeApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 1,
- parseUnits('1'),
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await safeApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 1,
- parseUnits('1'),
- {
- revertMessage: 'DV: request not exist',
- },
- );
- });
-
- it('should fail: if new rate greater then variabilityTolerance', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
- await safeApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- requestId,
- parseUnits('6'),
- {
- revertMessage: 'MV: exceed price diviation',
- },
- );
- });
-
- it('should fail: if new rate lower then variabilityTolerance', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
- await safeApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- requestId,
- parseUnits('4'),
- {
- revertMessage: 'MV: exceed price diviation',
- },
- );
- });
-
- it('should fail: request already precessed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- requestId,
- parseUnits('5.000001'),
- );
- await safeApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- requestId,
- parseUnits('5.000001'),
- { revertMessage: 'DV: request not pending' },
- );
- });
-
- it('approve request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- requestId,
- parseUnits('5.000000001'),
- );
- });
- });
-
- describe('safeBulkApproveRequest() (custom price overload)', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- depositVaultWithUSTB,
- regularAccounts,
- mTokenToUsdDataFeed,
- mTBILL,
- } = await loadFixture(defaultDeploy);
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: 1 }],
- parseUnits('1'),
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: 1 }],
- parseUnits('1'),
- {
- revertMessage: 'DV: request not exist',
- },
- );
- });
-
- it('should fail: if new rate greater then variabilityTolerance', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: requestId }],
- parseUnits('6'),
- {
- revertMessage: 'MV: exceed price diviation',
- },
- );
- });
-
- it('should fail: if new rate lower then variabilityTolerance', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: requestId }],
- parseUnits('4'),
- {
- revertMessage: 'MV: exceed price diviation',
- },
- );
- });
-
- it('should fail: request already precessed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: requestId }],
- parseUnits('5.000001'),
- );
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: requestId }],
- parseUnits('5.000001'),
- { revertMessage: 'DV: request not pending' },
- );
- });
-
- it('should fail: process multiple requests, when one of them already precessed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 200);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 200);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
-
- await safeApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 0,
- parseUnits('5.000001'),
- );
-
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: 1 }, { id: 0 }],
- parseUnits('5.000001'),
- { revertMessage: 'DV: request not pending' },
- );
- });
-
- it('should fail: process multiple requests, when couple of them have equal id', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 200);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 200);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
-
- await safeApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 0,
- parseUnits('5.000001'),
- );
-
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: 1 }, { id: 1 }],
- parseUnits('5.000001'),
- { revertMessage: 'DV: request not pending' },
- );
- });
-
- it('approve 1 request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: requestId }],
- parseUnits('5.000000001'),
- );
- });
-
- it('approve 2 requests from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 200);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 200);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
-
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: 0 }, { id: 1 }],
- parseUnits('5.000000001'),
- );
- });
-
- it('approve 10 requests from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 1000);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 1000);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- for (let i = 0; i < 10; i++) {
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- }
-
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- Array.from({ length: 10 }, (_, i) => ({ id: i })),
- parseUnits('5.000000001'),
- );
- });
- });
-
- describe('safeBulkApproveRequest() (current price overload)', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- depositVaultWithUSTB,
- regularAccounts,
- mTokenToUsdDataFeed,
- mTBILL,
- } = await loadFixture(defaultDeploy);
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: 1 }],
- undefined,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: 1 }],
- undefined,
- {
- revertMessage: 'DV: request not exist',
- },
- );
- });
-
- it('should fail: if new rate greater then variabilityTolerance', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 10);
-
- const requestId = 0;
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: requestId }],
- undefined,
- {
- revertMessage: 'MV: exceed price diviation',
- },
- );
- });
-
- it('should fail: if new rate lower then variabilityTolerance', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 3);
-
- const requestId = 0;
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: requestId }],
- undefined,
- {
- revertMessage: 'MV: exceed price diviation',
- },
- );
- });
-
- it('should fail: request already precessed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: requestId }],
- undefined,
- );
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: requestId }],
- undefined,
- { revertMessage: 'DV: request not pending' },
- );
- });
-
- it('should fail: process multiple requests, when one of them already precessed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 200);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 200);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
-
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: 0 }],
- undefined,
- );
-
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: 1 }, { id: 0 }],
- undefined,
- { revertMessage: 'DV: request not pending' },
- );
- });
-
- it('should fail: process multiple requests, when couple of the have equal id', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 200);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 200);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
-
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: 0 }],
- undefined,
- );
-
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: 1 }, { id: 1 }],
- undefined,
- { revertMessage: 'DV: request not pending' },
- );
- });
-
- it('approve 1 request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: requestId }],
- undefined,
- );
- });
-
- it('approve 2 requests from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 200);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 200);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
-
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: 0 }, { id: 1 }],
- undefined,
- );
- });
-
- it('approve 10 requests from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 1000);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 1000);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- for (let i = 0; i < 10; i++) {
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- }
-
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- Array.from({ length: 10 }, (_, i) => ({ id: i })),
- undefined,
- );
- });
-
- it('approve 10 requests from vaut admin account when different users are recievers', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 1000);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 1000);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- for (let i = 0; i < 10; i++) {
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient: regularAccounts[i],
- },
- stableCoins.dai,
- 100,
- );
- }
-
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- Array.from({ length: 10 }, (_, i) => ({ id: i })),
- undefined,
- );
- });
-
- it('approve 2 requests from vaut admin account when each request has different token', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await mintToken(stableCoins.usdc, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
-
- await safeBulkApproveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: 0 }, { id: 1 }],
- undefined,
- );
- });
- });
-
- describe('rejectRequest()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- depositVaultWithUSTB,
- regularAccounts,
- mTokenToUsdDataFeed,
- mTBILL,
- } = await loadFixture(defaultDeploy);
- await rejectRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await rejectRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 1,
- {
- revertMessage: 'DV: request not exist',
- },
- );
- });
-
- it('should fail: request is already rejected', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await rejectRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- requestId,
- );
-
- await rejectRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- requestId,
- {
- revertMessage: 'DV: request not pending',
- },
- );
- });
-
- it('reject request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await rejectRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- requestId,
- );
- });
- });
-
- describe('setUstbDepositsEnabled', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { depositVaultWithUSTB, owner, regularAccounts } =
- await loadFixture(defaultDeploy);
- await setUstbDepositsEnabledTest({ depositVaultWithUSTB, owner }, true, {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- });
- });
-
- it('call from address with vault admin role', async () => {
- const { depositVaultWithUSTB, owner } = await loadFixture(defaultDeploy);
- await setUstbDepositsEnabledTest({ depositVaultWithUSTB, owner }, true);
- });
-
- it('set true when ustbDepositsEnabled is already true', async () => {
- const { depositVaultWithUSTB, owner } = await loadFixture(defaultDeploy);
- await setUstbDepositsEnabledTest({ depositVaultWithUSTB, owner }, true);
- await setUstbDepositsEnabledTest({ depositVaultWithUSTB, owner }, true);
- });
-
- it('set false when ustbDepositsEnabled is already false', async () => {
- const { depositVaultWithUSTB, owner } = await loadFixture(defaultDeploy);
- await setUstbDepositsEnabledTest({ depositVaultWithUSTB, owner }, false);
- });
- });
-
- describe('depositInstant() complex', () => {
- it('should fail: when is paused', async () => {
- const {
- depositVaultWithUSTB,
- owner,
- mTBILL,
- stableCoins,
- regularAccounts,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVault(depositVaultWithUSTB);
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: paused',
- },
- );
- });
-
- it('is on pause, but admin can use everything', async () => {
- const {
- depositVaultWithUSTB,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVault(depositVaultWithUSTB);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'Pausable: paused',
- },
- );
- });
-
- it('call for amount == minAmountToDepositTest', async () => {
- const {
- depositVaultWithUSTB,
- mockedAggregator,
- mockedAggregatorMToken,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(stableCoins.dai, owner, 102_000);
- await approveBase18(
- owner,
- stableCoins.dai,
- depositVaultWithUSTB,
- 102_000,
- );
-
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setMinAmountToDepositTest(
- { depositVault: depositVaultWithUSTB, owner },
- 100_000,
- );
- await setInstantDailyLimitTest(
- { vault: depositVaultWithUSTB, owner },
- parseUnits('150000'),
- );
-
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 102_000,
- );
- });
-
- it('call for amount == minAmountToDepositTest+1, then deposit with amount 100', async () => {
- const {
- depositVaultWithUSTB,
- mockedAggregator,
- mockedAggregatorMToken,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(stableCoins.dai, owner, 103_101);
- await approveBase18(
- owner,
- stableCoins.dai,
- depositVaultWithUSTB,
- 103_101,
- );
-
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setMinAmountToDepositTest(
- { depositVault: depositVaultWithUSTB, owner },
- 100_000,
- );
- await setInstantDailyLimitTest(
- { vault: depositVaultWithUSTB, owner },
- parseUnits('150000'),
- );
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 103_001,
- );
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- });
-
- it('deposit 100 DAI, when price is 5$, 25 USDC when price is 5.1$, 14 USDT when price is 5.4$', async () => {
- const {
- owner,
- mockedAggregator,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await mintToken(stableCoins.usdc, owner, 125);
- await mintToken(stableCoins.usdt, owner, 114);
-
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithUSTB, 125);
- await approveBase18(owner, stableCoins.usdt, depositVaultWithUSTB, 114);
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.usdt,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.04);
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
-
- await setRoundData({ mockedAggregator }, 1);
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 125,
- );
-
- await setRoundData({ mockedAggregator }, 1.01);
- await depositInstantTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdt,
- 114,
- );
- });
- });
-
- describe('depositRequest() complex', () => {
- it('should fail: when is paused', async () => {
- const {
- depositVaultWithUSTB,
- owner,
- mTBILL,
- stableCoins,
- regularAccounts,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVault(depositVaultWithUSTB);
- await mintToken(stableCoins.dai, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- depositVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: paused',
- },
- );
- });
-
- it('is on pause, but admin can use everything', async () => {
- const {
- depositVaultWithUSTB,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVault(depositVaultWithUSTB);
-
- await mintToken(stableCoins.dai, owner, 100);
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'Pausable: paused',
- },
- );
- });
-
- it('call for amount == minAmountToDepositTest', async () => {
- const {
- depositVaultWithUSTB,
- mockedAggregator,
- mockedAggregatorMToken,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(stableCoins.dai, owner, 105_000);
- await approveBase18(
- owner,
- stableCoins.dai,
- depositVaultWithUSTB,
- 105_000,
- );
-
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setMinAmountToDepositTest(
- { depositVault: depositVaultWithUSTB, owner },
- 100_000,
- );
- await setInstantDailyLimitTest(
- { vault: depositVaultWithUSTB, owner },
- 150_000,
- );
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 102_000,
- );
- const requestId = 0;
-
- await approveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- requestId,
- parseUnits('5'),
- );
- });
-
- it('call for amount == minAmountToDepositTest+1, then deposit with amount 1', async () => {
- const {
- depositVaultWithUSTB,
- mockedAggregator,
- mockedAggregatorMToken,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(stableCoins.dai, owner, 105_101);
- await approveBase18(
- owner,
- stableCoins.dai,
- depositVaultWithUSTB,
- 105_101,
- );
-
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setMinAmountToDepositTest(
- { depositVault: depositVaultWithUSTB, owner },
- 100_000,
- );
- await setInstantDailyLimitTest(
- { vault: depositVaultWithUSTB, owner },
- 150_000,
- );
-
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 102_001,
- );
- let requestId = 0;
-
- await approveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- requestId,
- parseUnits('5'),
- );
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- requestId = 1;
-
- await approveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- requestId,
- parseUnits('5'),
- );
- });
-
- it('deposit 100 DAI, when price is 5$, 25 USDC when price is 5.1$, 14 USDT when price is 5.4$', async () => {
- const {
- owner,
- mockedAggregator,
- depositVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, owner, 100);
- await mintToken(stableCoins.usdc, owner, 125);
- await mintToken(stableCoins.usdt, owner, 114);
-
- await approveBase18(owner, stableCoins.dai, depositVaultWithUSTB, 100);
- await approveBase18(owner, stableCoins.usdc, depositVaultWithUSTB, 125);
- await approveBase18(owner, stableCoins.usdt, depositVaultWithUSTB, 114);
-
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.usdt,
- dataFeed.address,
- 0,
- true,
- );
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 10);
-
- await setRoundData({ mockedAggregator }, 1.04);
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- let requestId = 0;
-
- await approveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- requestId,
- parseUnits('5'),
- );
-
- await setRoundData({ mockedAggregator }, 1);
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 125,
- );
- requestId = 1;
-
- await approveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- requestId,
- parseUnits('5'),
- );
-
- await setRoundData({ mockedAggregator }, 1.01);
- await depositRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdt,
- 114,
- );
- requestId = 2;
-
- await approveRequestTest(
- {
- depositVault: depositVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- requestId,
- parseUnits('5'),
- );
- });
- });
-
- describe('ManageableVault internal functions', () => {
- it('should fail: invalid rounding tokenTransferFromToTester()', async () => {
- const { depositVaultWithUSTB, stableCoins, owner } = await loadFixture(
- defaultDeploy,
- );
-
- await mintToken(stableCoins.usdc, owner, 1000);
-
- await approveBase18(owner, stableCoins.usdc, depositVaultWithUSTB, 1000);
-
- await expect(
- depositVaultWithUSTB.tokenTransferFromToTester(
- stableCoins.usdc.address,
- owner.address,
- depositVaultWithUSTB.address,
- parseUnits('999.999999999'),
- 8,
- ),
- ).revertedWith('MV: invalid rounding');
- });
-
- it('should fail: invalid rounding tokenTransferToUserTester()', async () => {
- const { depositVaultWithUSTB, stableCoins, owner } = await loadFixture(
- defaultDeploy,
- );
-
- await mintToken(stableCoins.usdc, depositVaultWithUSTB, 1000);
-
- await expect(
- depositVaultWithUSTB.tokenTransferToUserTester(
- stableCoins.usdc.address,
- owner.address,
- parseUnits('999.999999999'),
- 8,
- ),
- ).revertedWith('MV: invalid rounding');
- });
- });
-
- describe('_convertUsdToToken', () => {
- it('should fail: when amountUsd == 0', async () => {
- const { depositVaultWithUSTB } = await loadFixture(defaultDeploy);
-
- await expect(
- depositVaultWithUSTB.convertTokenToUsdTest(constants.AddressZero, 0),
- ).revertedWith('DV: amount zero');
- });
-
- it('should fail: when tokenRate == 0', async () => {
- const { depositVaultWithUSTB } = await loadFixture(defaultDeploy);
-
- await depositVaultWithUSTB.setOverrideGetTokenRate(true);
- await depositVaultWithUSTB.setGetTokenRateValue(0);
-
- await expect(
- depositVaultWithUSTB.convertTokenToUsdTest(constants.AddressZero, 1),
- ).revertedWith('DV: rate zero');
- });
- });
-
- describe('_convertUsdToMToken', () => {
- it('should fail: when rate == 0', async () => {
- const { depositVaultWithUSTB } = await loadFixture(defaultDeploy);
-
- await depositVaultWithUSTB.setOverrideGetTokenRate(true);
- await depositVaultWithUSTB.setGetTokenRateValue(0);
-
- await expect(depositVaultWithUSTB.convertUsdToMTokenTest(1)).revertedWith(
- 'DV: rate zero',
- );
- });
- });
-
- describe('_calcAndValidateDeposit', () => {
- it('should fail: when tokenOut is not MANUAL_FULLFILMENT_TOKEN but isFiat = true', async () => {
- const { depositVaultWithUSTB, stableCoins, owner, dataFeed } =
- await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: depositVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- parseUnits('100', 2),
- true,
- );
-
- await setMinAmountTest({ vault: depositVaultWithUSTB, owner }, 0);
-
- await expect(
- depositVaultWithUSTB.calcAndValidateDeposit(
- constants.AddressZero,
- stableCoins.dai.address,
- parseUnits('100'),
- true,
- ),
- ).revertedWith('DV: invalid mint amount');
- });
- });
-});
+ },
+);
diff --git a/test/unit/Greenlistable.test.ts b/test/unit/Greenlistable.test.ts
index 126e54ae..667b5ab4 100644
--- a/test/unit/Greenlistable.test.ts
+++ b/test/unit/Greenlistable.test.ts
@@ -1,11 +1,12 @@
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
import { expect } from 'chai';
-import { GreenlistableTester__factory } from '../../typechain-types';
+import { encodeFnSelector } from '../../helpers/utils';
import {
acErrors,
greenList,
- greenListToggler,
+ setPermissionRoleTester,
+ setupGrantOperatorRole,
unGreenList,
} from '../common/ac.helpers';
import { defaultDeploy } from '../common/fixtures';
@@ -25,30 +26,6 @@ describe('Greenlistable', function () {
).eq(true);
});
- it('onlyInitializing', async () => {
- const { owner, accessControl } = await loadFixture(defaultDeploy);
-
- const greenListable = await new GreenlistableTester__factory(
- owner,
- ).deploy();
-
- await expect(
- greenListable.initializeWithoutInitializer(accessControl.address),
- ).revertedWith('Initializable: contract is not initializing');
- });
-
- it('onlyInitializing unchained', async () => {
- const { owner, accessControl } = await loadFixture(defaultDeploy);
-
- const greenListable = await new GreenlistableTester__factory(
- owner,
- ).deploy();
-
- await expect(
- greenListable.initializeUnchainedWithoutInitializer(),
- ).revertedWith('Initializable: contract is not initializing');
- });
-
describe('modifier onlyGreenlisted', () => {
it('should fail: call from greenlisted user', async () => {
const { greenListableTester, regularAccounts, owner } = await loadFixture(
@@ -62,7 +39,7 @@ describe('Greenlistable', function () {
await expect(
greenListableTester.onlyGreenlistedTester(regularAccounts[0].address),
- ).revertedWith(acErrors.WMAC_HASNT_ROLE);
+ ).revertedWithCustomError(greenListableTester, 'NotGreenlisted');
});
it('call from not greenlisted user', async () => {
@@ -70,7 +47,12 @@ describe('Greenlistable', function () {
await loadFixture(defaultDeploy);
await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
+ {
+ greenlistable: greenListableTester,
+ accessControl,
+ owner,
+ role: await greenListableTester.greenlistAdminRole(),
+ },
regularAccounts[0],
);
await expect(
@@ -79,35 +61,6 @@ describe('Greenlistable', function () {
});
});
- describe('modifier onlyGreenlistToggler', () => {
- it('should fail: call from not greenlistToggler user', async () => {
- const { greenListableTester, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
-
- await expect(
- greenListableTester.onlyGreenlistTogglerTester(
- regularAccounts[0].address,
- ),
- ).revertedWith(acErrors.WMAC_HASNT_ROLE);
- });
-
- it('call from greenlistToggler user', async () => {
- const { accessControl, greenListableTester, owner, regularAccounts } =
- await loadFixture(defaultDeploy);
-
- await greenListToggler(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
- await expect(
- greenListableTester.onlyGreenlistTogglerTester(
- regularAccounts[0].address,
- ),
- ).not.reverted;
- });
- });
-
describe('setGreenlistEnable()', () => {
it('should fail: call from user without GREENLIST_TOGGLER_ROLE role', async () => {
const { greenListableTester, owner, regularAccounts } = await loadFixture(
@@ -119,7 +72,7 @@ describe('Greenlistable', function () {
true,
{
from: regularAccounts[0],
- revertMessage: `WMAC: hasnt role`,
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
},
);
});
@@ -131,7 +84,9 @@ describe('Greenlistable', function () {
{ greenlistable: greenListableTester, owner },
false,
{
- revertMessage: `GL: same enable status`,
+ revertCustomError: {
+ customErrorName: 'SameBoolValue',
+ },
},
);
});
@@ -143,6 +98,95 @@ describe('Greenlistable', function () {
true,
);
});
+
+ it('succeeds with only scoped function permission', async () => {
+ const { accessControl, greenListableTester, owner, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ const greenlistAdmin = await greenListableTester.greenlistAdminRole();
+ const selector = encodeFnSelector('setGreenlistEnable(bool)');
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: greenlistAdmin,
+ targetContract: greenListableTester.address,
+ functionSelector: selector,
+ grantOperator: owner,
+ });
+
+ const user = regularAccounts[0];
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ greenListableTester.address,
+ selector,
+ [
+ {
+ account: user.address,
+ enabled: true,
+ },
+ ],
+ );
+
+ expect(await accessControl.hasRole(greenlistAdmin, user.address)).eq(
+ false,
+ );
+
+ await greenListEnable(
+ { greenlistable: greenListableTester, owner },
+ true,
+ { from: user },
+ );
+ });
+
+ it('succeeds with scoped permission and greenlist admin role', async () => {
+ const { accessControl, greenListableTester, owner, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ const greenlistAdmin = await greenListableTester.greenlistAdminRole();
+ const selector = encodeFnSelector('setGreenlistEnable(bool)');
+ const user = regularAccounts[0];
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: greenlistAdmin,
+ targetContract: greenListableTester.address,
+ functionSelector: selector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ greenListableTester.address,
+ selector,
+ [
+ {
+ account: user.address,
+ enabled: true,
+ },
+ ],
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ greenlistAdmin,
+ user.address,
+ );
+
+ expect(await accessControl.hasRole(greenlistAdmin, user.address)).eq(
+ true,
+ );
+
+ await greenListEnable(
+ { greenlistable: greenListableTester, owner },
+ true,
+ {
+ from: user,
+ },
+ );
+ });
});
describe('addToGreenList', () => {
@@ -151,11 +195,16 @@ describe('Greenlistable', function () {
await loadFixture(defaultDeploy);
await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
+ {
+ greenlistable: greenListableTester,
+ accessControl,
+ owner,
+ role: await greenListableTester.greenlistedRole(),
+ },
regularAccounts[0],
{
from: regularAccounts[0],
- revertMessage: `AccessControl: account ${regularAccounts[0].address.toLowerCase()} is missing role ${await accessControl.GREENLIST_OPERATOR_ROLE()}`,
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
},
);
});
@@ -164,7 +213,12 @@ describe('Greenlistable', function () {
const { accessControl, greenListableTester, owner, regularAccounts } =
await loadFixture(defaultDeploy);
await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
+ {
+ greenlistable: greenListableTester,
+ accessControl,
+ owner,
+ role: await greenListableTester.greenlistedRole(),
+ },
regularAccounts[0],
);
});
@@ -176,11 +230,16 @@ describe('Greenlistable', function () {
await loadFixture(defaultDeploy);
await unGreenList(
- { greenlistable: greenListableTester, accessControl, owner },
+ {
+ greenlistable: greenListableTester,
+ accessControl,
+ owner,
+ role: await greenListableTester.greenlistedRole(),
+ },
regularAccounts[0],
{
from: regularAccounts[0],
- revertMessage: `AccessControl: account ${regularAccounts[0].address.toLowerCase()} is missing role ${await accessControl.GREENLIST_OPERATOR_ROLE()}`,
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
},
);
});
@@ -189,7 +248,12 @@ describe('Greenlistable', function () {
const { accessControl, greenListableTester, owner, regularAccounts } =
await loadFixture(defaultDeploy);
await unGreenList(
- { greenlistable: greenListableTester, accessControl, owner },
+ {
+ greenlistable: greenListableTester,
+ accessControl,
+ owner,
+ role: await greenListableTester.greenlistAdminRole(),
+ },
regularAccounts[0],
);
});
diff --git a/test/unit/MGlobalInfiniFiCustomAggregatorFeedGrowth.test.ts b/test/unit/MGlobalInfiniFiCustomAggregatorFeedGrowth.test.ts
deleted file mode 100644
index 150daf8b..00000000
--- a/test/unit/MGlobalInfiniFiCustomAggregatorFeedGrowth.test.ts
+++ /dev/null
@@ -1,145 +0,0 @@
-import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
-import { expect } from 'chai';
-import { parseUnits, solidityKeccak256 } from 'ethers/lib/utils';
-import { ethers, upgrades } from 'hardhat';
-
-import {
- MGlobalInfiniFiCustomAggregatorFeedGrowth,
- MGlobalInfiniFiCustomAggregatorFeedGrowth__factory,
- MidasAccessControlTest__factory,
-} from '../../typechain-types';
-import { acErrors } from '../common/ac.helpers';
-
-const PARAMS = {
- minAnswer: parseUnits('0.1', 8),
- maxAnswer: parseUnits('2', 8),
- maxAnswerDeviation: parseUnits('1', 8),
- minGrowthApr: parseUnits('0', 8),
- maxGrowthApr: parseUnits('7.24', 8),
- onlyUp: true,
- description: 'infiniFi MG Yield Oracle',
-} as const;
-
-const EXPECTED_ROLE = solidityKeccak256(
- ['string'],
- ['INFINIFI_MG_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE'],
-);
-
-const deployFixture = async () => {
- const [owner, infinifiAdmin, stranger] = await ethers.getSigners();
-
- const accessControl = await new MidasAccessControlTest__factory(
- owner,
- ).deploy();
- await accessControl.initialize();
-
- const feed = (await upgrades.deployProxy(
- new MGlobalInfiniFiCustomAggregatorFeedGrowth__factory(owner),
- [
- accessControl.address,
- PARAMS.minAnswer,
- PARAMS.maxAnswer,
- PARAMS.maxAnswerDeviation,
- PARAMS.minGrowthApr,
- PARAMS.maxGrowthApr,
- PARAMS.onlyUp,
- PARAMS.description,
- ],
- )) as MGlobalInfiniFiCustomAggregatorFeedGrowth;
-
- return { owner, infinifiAdmin, stranger, accessControl, feed };
-};
-
-describe('MGlobalInfiniFiCustomAggregatorFeedGrowth', () => {
- it('initializes with the InfiniFi ticket parameters', async () => {
- const { feed } = await loadFixture(deployFixture);
-
- expect(await feed.description()).eq(PARAMS.description);
- expect(await feed.decimals()).eq(8);
- expect(await feed.version()).eq(1);
- expect(await feed.minAnswer()).eq(PARAMS.minAnswer);
- expect(await feed.maxAnswer()).eq(PARAMS.maxAnswer);
- expect(await feed.maxAnswerDeviation()).eq(PARAMS.maxAnswerDeviation);
- expect(await feed.minGrowthApr()).eq(PARAMS.minGrowthApr);
- expect(await feed.maxGrowthApr()).eq(PARAMS.maxGrowthApr);
- expect(await feed.onlyUp()).eq(PARAMS.onlyUp);
- });
-
- it('exposes the dedicated INFINIFI_MG role and the constant matches its keccak preimage', async () => {
- const { feed } = await loadFixture(deployFixture);
-
- expect(await feed.feedAdminRole()).eq(EXPECTED_ROLE);
- expect(await feed.INFINIFI_MG_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE()).eq(
- EXPECTED_ROLE,
- );
- expect(await feed.feedAdminRole()).not.eq(
- await feed.M_GLOBAL_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE(),
- );
- });
-
- it('rejects setRoundData from a caller without the InfiniFi role', async () => {
- const { feed, stranger } = await loadFixture(deployFixture);
-
- const ts = (await ethers.provider.getBlock('latest')).timestamp - 1;
-
- await expect(
- feed.connect(stranger).setRoundData(parseUnits('1', 8), ts, 0),
- ).revertedWith(acErrors.WMAC_HASNT_ROLE);
- });
-
- it('accepts setRoundData from a caller granted the InfiniFi role and seeds $1.00 / 0% APR', async () => {
- const { feed, accessControl, owner, infinifiAdmin } = await loadFixture(
- deployFixture,
- );
-
- await accessControl
- .connect(owner)
- .grantRole(EXPECTED_ROLE, infinifiAdmin.address);
-
- const ts = (await ethers.provider.getBlock('latest')).timestamp - 1;
-
- await expect(
- feed
- .connect(infinifiAdmin)
- .setRoundData(parseUnits('1', 8), ts, parseUnits('0', 8)),
- ).to.emit(feed, 'AnswerUpdated');
-
- const [, answer, , , , growthApr] = await feed.latestRoundDataRaw();
- expect(answer).eq(parseUnits('1', 8));
- expect(growthApr).eq(0);
- });
-
- it('enforces growth APR bounds: rejects > maxGrowthApr (7.24%)', async () => {
- const { feed, accessControl, owner, infinifiAdmin } = await loadFixture(
- deployFixture,
- );
-
- await accessControl
- .connect(owner)
- .grantRole(EXPECTED_ROLE, infinifiAdmin.address);
-
- const ts = (await ethers.provider.getBlock('latest')).timestamp - 1;
-
- await expect(
- feed
- .connect(infinifiAdmin)
- .setRoundData(parseUnits('1', 8), ts, PARAMS.maxGrowthApr.add(1)),
- ).revertedWith('CAG: out of [min;max] growth');
- });
-
- it('enforces growth APR bounds: rejects negative APR (minGrowthApr = 0)', async () => {
- const { feed, accessControl, owner, infinifiAdmin } = await loadFixture(
- deployFixture,
- );
-
- await accessControl
- .connect(owner)
- .grantRole(EXPECTED_ROLE, infinifiAdmin.address);
-
- const ts = (await ethers.provider.getBlock('latest')).timestamp - 1;
-
- await expect(
- feed.connect(infinifiAdmin).setRoundData(parseUnits('1', 8), ts, -1),
- ).revertedWith('CAG: out of [min;max] growth');
- });
-});
diff --git a/test/unit/MidasAccessControl.test.ts b/test/unit/MidasAccessControl.test.ts
index 0288d833..7bb6257c 100644
--- a/test/unit/MidasAccessControl.test.ts
+++ b/test/unit/MidasAccessControl.test.ts
@@ -1,10 +1,183 @@
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
+import { increase } from '@nomicfoundation/hardhat-network-helpers/dist/src/helpers/time';
+import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { expect } from 'chai';
import { constants } from 'ethers';
import { ethers } from 'hardhat';
-import { WithMidasAccessControlTester__factory } from '../../typechain-types';
+import { encodeFnSelector } from '../../helpers/utils';
+import {
+ MidasAccessControl,
+ MidasAccessControl__factory,
+ MidasAccessControlTimelockController,
+ MidasTimelockManager,
+ WithMidasAccessControlTester,
+ WithMidasAccessControlTester__factory,
+} from '../../typechain-types';
+import {
+ acErrors,
+ grantRoleMultTester,
+ grantRoleTester,
+ revokeRoleMultTester,
+ revokeRoleTester,
+ setIsUserFacingRoleTester,
+ setGrantOperatorRoleTester,
+ setPermissionRoleMultTester,
+ setPermissionRoleTester,
+ setupGrantOperatorRole,
+ setDefaultDelayTest,
+ setRoleTimelocksAndExecute,
+ setRoleTimelocksTester,
+ NO_DELAY,
+} from '../common/ac.helpers';
+import {
+ handleRevert,
+ validateImplementation,
+ asyncForEach,
+} from '../common/common.helpers';
+import { deployProxyContract } from '../common/deploy.helpers';
import { defaultDeploy } from '../common/fixtures';
+import {
+ executeTimelockOperationTester,
+ bulkScheduleTimelockOperationTester,
+} from '../common/timelock-manager.helpers';
+
+const withOnlyRoleSelector = encodeFnSelector('withOnlyRole(bytes32,bool)');
+const withOnlyContractAdminSelector = encodeFnSelector(
+ 'withOnlyContractAdmin()',
+);
+const withOnlyRoleNoTimelockSelector = encodeFnSelector(
+ 'withOnlyRoleNoTimelock(bytes32,bool)',
+);
+
+const DELAY_FOR_SET_DEFAULT_DELAY = 2 * 24 * 3600;
+const setDefaultDelaySelector = encodeFnSelector('setDefaultDelay(uint256)');
+
+const PROXY_ADMIN_SLOT =
+ '0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103';
+
+const setProxyAdmin = (address: string, admin: string) =>
+ ethers.provider.send('hardhat_setStorageAt', [
+ address,
+ PROXY_ADMIN_SLOT,
+ ethers.utils.hexZeroPad(admin, 32),
+ ]);
+
+const setInitializedVersion = (address: string, version: number) =>
+ ethers.provider.send('hardhat_setStorageAt', [
+ address,
+ ethers.utils.hexZeroPad('0x00', 32),
+ ethers.utils.hexZeroPad(ethers.utils.hexlify(version), 32),
+ ]);
+
+const timelockManagerRevertOpts = (
+ timelockManager: MidasTimelockManager,
+ customErrorName: string,
+ args?: unknown[],
+) => ({
+ revertCustomError: {
+ contract: timelockManager,
+ customErrorName,
+ args,
+ },
+});
+
+const getScopedFunctionKeys = async (
+ accessControl: MidasAccessControl,
+ masterRole: string,
+ functionSelector: string,
+ wAccessControlTester: WithMidasAccessControlTester,
+ timelockManager: MidasTimelockManager,
+) => {
+ const wacFunctionKey = await accessControl.permissionRoleKey(
+ masterRole,
+ wAccessControlTester.address,
+ functionSelector,
+ );
+ const timelockManagerFunctionKey = await accessControl.permissionRoleKey(
+ masterRole,
+ timelockManager.address,
+ functionSelector,
+ );
+
+ return { wacFunctionKey, timelockManagerFunctionKey };
+};
+
+const setupFunctionPermissionRole = async (
+ accessControl: MidasAccessControl,
+ owner: SignerWithAddress,
+ wAccessControlTester: WithMidasAccessControlTester,
+ timelockManager: MidasTimelockManager,
+ masterRole: string,
+ functionSelector: string,
+ account: string,
+) => {
+ await asyncForEach(
+ [wAccessControlTester.address, timelockManager.address],
+ async (targetContract) => {
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole,
+ targetContract,
+ functionSelector,
+ grantOperator: owner,
+ });
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ masterRole,
+ targetContract,
+ functionSelector,
+ [{ account, enabled: true }],
+ );
+ },
+ true,
+ );
+
+ return getScopedFunctionKeys(
+ accessControl,
+ masterRole,
+ functionSelector,
+ wAccessControlTester,
+ timelockManager,
+ );
+};
+
+const setupWithOnlyRolePermission = async (
+ accessControl: MidasAccessControl,
+ owner: SignerWithAddress,
+ wAccessControlTester: WithMidasAccessControlTester,
+ timelockManager: MidasTimelockManager,
+ masterRole: string,
+ account: string,
+) =>
+ setupFunctionPermissionRole(
+ accessControl,
+ owner,
+ wAccessControlTester,
+ timelockManager,
+ masterRole,
+ withOnlyRoleSelector,
+ account,
+ );
+
+const setupWithOnlyContractAdminPermission = async (
+ accessControl: MidasAccessControl,
+ owner: SignerWithAddress,
+ wAccessControlTester: WithMidasAccessControlTester,
+ timelockManager: MidasTimelockManager,
+ masterRole: string,
+ account: string,
+) =>
+ setupFunctionPermissionRole(
+ accessControl,
+ owner,
+ wAccessControlTester,
+ timelockManager,
+ masterRole,
+ withOnlyContractAdminSelector,
+ account,
+ );
describe('MidasAccessControl', function () {
it('deployment', async () => {
@@ -14,16 +187,11 @@ describe('MidasAccessControl', function () {
roles.common.blacklistedOperator,
roles.common.greenlistedOperator,
roles.common.defaultAdmin,
- roles.tokenRoles.mTBILL.burner,
- roles.tokenRoles.mTBILL.minter,
- roles.tokenRoles.mTBILL.pauser,
- roles.tokenRoles.mTBILL.redemptionVaultAdmin,
- roles.tokenRoles.mTBILL.depositVaultAdmin,
];
- for (const role of initGrantedRoles) {
+ await asyncForEach(initGrantedRoles, async (role) => {
expect(await accessControl.hasRole(role, owner.address)).to.eq(true);
- }
+ });
expect(await accessControl.getRoleAdmin(roles.common.blacklisted)).eq(
roles.common.blacklistedOperator,
@@ -32,259 +200,4120 @@ describe('MidasAccessControl', function () {
expect(await accessControl.getRoleAdmin(roles.common.greenlisted)).eq(
roles.common.greenlistedOperator,
);
+
+ expect(await accessControl.isUserFacingRole(roles.common.blacklisted)).eq(
+ true,
+ );
+
+ expect(await accessControl.isUserFacingRole(roles.common.greenlisted)).eq(
+ true,
+ );
+
+ await validateImplementation(MidasAccessControl__factory);
});
it('initialize', async () => {
const { accessControl } = await loadFixture(defaultDeploy);
- await expect(accessControl.initialize()).revertedWith(
+ await expect(accessControl.initialize(NO_DELAY, [])).revertedWith(
'Initializable: contract is already initialized',
);
});
- describe('grantRoleMult()', () => {
+ describe('initializeV2() proxy admin restriction', () => {
+ const deployAccessControl = () =>
+ deployProxyContract(
+ 'MidasAccessControl',
+ [0, []],
+ 'initialize',
+ );
+
+ it('initializeV2 runs while proxy admin is zero during fresh deploy', async () => {
+ const accessControl = await deployProxyContract(
+ 'MidasAccessControl',
+ [3600, []],
+ 'initialize',
+ );
+
+ expect(await accessControl.defaultDelay()).eq(3600);
+ });
+
+ it('should fail: when already reinitialized, even from the proxy admin', async () => {
+ const [, admin] = await ethers.getSigners();
+ const accessControl = await deployAccessControl();
+
+ await setProxyAdmin(accessControl.address, admin.address);
+
+ await expect(
+ accessControl.connect(admin).initializeV2(0, []),
+ ).revertedWith('Initializable: contract is already initialized');
+ });
+
+ it('should fail: when initialized and caller is not the proxy admin', async () => {
+ const [, admin, stranger] = await ethers.getSigners();
+ const accessControl = await deployAccessControl();
+
+ await setProxyAdmin(accessControl.address, admin.address);
+ await setInitializedVersion(accessControl.address, 1);
+
+ await expect(
+ accessControl.connect(stranger).initializeV2(0, []),
+ ).revertedWithCustomError(accessControl, 'SenderNotProxyAdmin');
+ });
+
+ it('when initialized and caller is the proxy admin', async () => {
+ const [, admin] = await ethers.getSigners();
+ const accessControl = await deployAccessControl();
+
+ await setProxyAdmin(accessControl.address, admin.address);
+ await setInitializedVersion(accessControl.address, 1);
+
+ await accessControl.connect(admin).initializeV2(3600, []);
+
+ expect(await accessControl.defaultDelay()).eq(3600);
+ });
+ });
+
+ describe('renounceRole()', () => {
it('should fail: function is forbidden', async () => {
const { accessControl } = await loadFixture(defaultDeploy);
await expect(
accessControl.renounceRole(constants.HashZero, constants.AddressZero),
- ).revertedWith('MAC: Forbidden');
+ ).revertedWithCustomError(accessControl, 'Forbidden');
});
});
describe('grantRoleMult()', () => {
- it('should fail: arrays length mismatch', async () => {
- const { accessControl } = await loadFixture(defaultDeploy);
+ it('should fail: array is empty', async () => {
+ const { accessControl, owner, roles } = await loadFixture(defaultDeploy);
- await expect(
- accessControl.grantRoleMult([], [ethers.constants.AddressZero]),
- ).revertedWith('MAC: mismatch arrays');
+ await grantRoleMultTester({ accessControl, owner }, [], {
+ revertCustomError: { customErrorName: 'EmptyArray' },
+ });
});
- it('should fail: arrays length mismatch', async () => {
- const { accessControl, regularAccounts } = await loadFixture(
+ it('should fail: when user does not have admin role for roles[0]', async () => {
+ const { accessControl, regularAccounts, roles } = await loadFixture(
defaultDeploy,
);
- const arr = [
+ await grantRoleMultTester(
+ { accessControl, owner: regularAccounts[0] },
+ [
+ {
+ role: roles.common.blacklisted,
+ account: regularAccounts[1].address,
+ delay: 0,
+ },
+ ],
+ { revertCustomError: acErrors.WMAC_HASNT_PERMISSION() },
+ );
+ });
+
+ it('should fail: when user has admin role but roles[1] admin role is different fron roles[0]', async () => {
+ const { accessControl, owner, regularAccounts, roles } =
+ await loadFixture(defaultDeploy);
+
+ await grantRoleTester(
+ { accessControl, owner },
+ roles.common.blacklistedOperator,
+ regularAccounts[0].address,
+ );
+
+ await grantRoleMultTester(
+ { accessControl, owner: regularAccounts[0] },
+ [
+ {
+ role: roles.common.blacklisted,
+ account: regularAccounts[1].address,
+ delay: 0,
+ },
+ {
+ role: roles.common.greenlisted,
+ account: regularAccounts[2].address,
+ delay: 0,
+ },
+ ],
+ { revertCustomError: { customErrorName: 'RoleAdminMismatch' } },
+ );
+ });
+
+ it('when timelock delay is not 0 - schedule and execute the tx', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ timelock,
+ timelockManager,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [roles.common.blacklistedOperator],
+ [3600],
+ );
+
+ const data = accessControl.interface.encodeFunctionData('grantRoleMult', [
+ [
+ {
+ role: roles.common.blacklisted,
+ account: regularAccounts[0].address,
+ delay: 0,
+ },
+ ],
+ ]);
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [accessControl.address],
+ [data],
+ {},
+ { from: owner },
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ accessControl.address,
+ data,
+ owner.address,
+ { from: owner },
+ );
+ });
+
+ it('when address already have role (shouldnt fail)', async () => {
+ const { accessControl, owner, regularAccounts, roles } =
+ await loadFixture(defaultDeploy);
+
+ await grantRoleMultTester({ accessControl, owner }, [
{
- role: await accessControl.BLACKLIST_OPERATOR_ROLE(),
- user: regularAccounts[0].address,
+ role: roles.common.blacklisted,
+ account: regularAccounts[0].address,
+ delay: 0,
},
+ ]);
+
+ await grantRoleMultTester({ accessControl, owner }, [
{
- role: await accessControl.GREENLIST_OPERATOR_ROLE(),
- user: regularAccounts[0].address,
+ role: roles.common.blacklisted,
+ account: regularAccounts[0].address,
+ delay: 0,
},
- ];
+ ]);
+ });
- await expect(
- accessControl.grantRoleMult(
- arr.map((v) => v.role),
- arr.map((v) => v.user),
- ),
- ).not.reverted;
+ it('should fail: when user have function access role but do not have role admin role', async () => {
+ const { accessControl, owner, regularAccounts, roles } =
+ await loadFixture(defaultDeploy);
- for (const setRoles of arr) {
- expect(await accessControl.hasRole(setRoles.role, setRoles.user)).eq(
- true,
- );
- }
- });
- });
+ const selector = encodeFnSelector('grantRoleMult(bytes32[],address[])');
- describe('revokeRoleMult()', () => {
- it('should fail: arrays length mismatch', async () => {
- const { accessControl } = await loadFixture(defaultDeploy);
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: roles.common.blacklistedOperator,
+ targetContract: accessControl.address,
+ functionSelector: selector,
+ grantOperator: owner,
+ });
- await expect(
- accessControl.revokeRoleMult([], [ethers.constants.AddressZero]),
- ).revertedWith('MAC: mismatch arrays');
- });
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ accessControl.address,
+ selector,
+ [{ account: regularAccounts[0].address, enabled: true }],
+ );
- it('should fail: arrays length mismatch', async () => {
- const { accessControl, regularAccounts } = await loadFixture(
- defaultDeploy,
+ await grantRoleMultTester(
+ { accessControl, owner: regularAccounts[0] },
+ [
+ {
+ role: roles.common.blacklisted,
+ account: regularAccounts[1].address,
+ delay: 0,
+ },
+ ],
+ { revertCustomError: acErrors.WMAC_HASNT_PERMISSION() },
);
+ });
+
+ it('when delay is not NULL_DELAY but actual delay is NULL_DELAY - should set the delay', async () => {
+ const { accessControl, owner, regularAccounts, roles } =
+ await loadFixture(defaultDeploy);
- const arr = [
+ await grantRoleMultTester({ accessControl, owner }, [
{
- role: await accessControl.BLACKLIST_OPERATOR_ROLE(),
- user: regularAccounts[0].address,
+ role: roles.common.blacklisted,
+ account: regularAccounts[0].address,
+ delay: 3600,
},
+ ]);
+ });
+
+ it('should fail: when delay is not NULL_DELAY but actual delay is also not null', async () => {
+ const { accessControl, owner, regularAccounts, roles } =
+ await loadFixture(defaultDeploy);
+
+ await grantRoleMultTester({ accessControl, owner }, [
{
- role: await accessControl.GREENLIST_OPERATOR_ROLE(),
- user: regularAccounts[0].address,
+ role: roles.common.blacklisted,
+ account: regularAccounts[0].address,
+ delay: 3600,
},
- ];
+ ]);
- await expect(
- accessControl.grantRoleMult(
- arr.map((v) => v.role),
- arr.map((v) => v.user),
- ),
- ).not.reverted;
- await expect(
- accessControl.revokeRoleMult(
- arr.map((v) => v.role),
- arr.map((v) => v.user),
- ),
- ).not.reverted;
+ await grantRoleMultTester(
+ { accessControl, owner },
+ [
+ {
+ role: roles.common.blacklisted,
+ account: regularAccounts[1].address,
+ delay: 7200,
+ },
+ ],
+ { revertCustomError: { customErrorName: 'DelayIsAlreadySet' } },
+ );
+ });
- for (const setRoles of arr) {
- expect(await accessControl.hasRole(setRoles.role, setRoles.user)).eq(
- false,
- );
- }
+ it('should fail: when array contains 2 identical roles and both tries to update the delay, and actual delay is NULL_DELAY', async () => {
+ const { accessControl, owner, regularAccounts, roles } =
+ await loadFixture(defaultDeploy);
+
+ await grantRoleMultTester(
+ { accessControl, owner },
+ [
+ {
+ role: roles.common.blacklisted,
+ account: regularAccounts[0].address,
+ delay: 3600,
+ },
+ {
+ role: roles.common.blacklisted,
+ account: regularAccounts[1].address,
+ delay: 3600,
+ },
+ ],
+ { revertCustomError: { customErrorName: 'DelayIsAlreadySet' } },
+ );
});
});
-});
-describe('WithMidasAccessControl', function () {
- it('deployment', async () => {
- const { accessControl, wAccessControlTester } = await loadFixture(
- defaultDeploy,
- );
- expect(await wAccessControlTester.accessControl()).eq(
- accessControl.address,
- );
- });
+ describe('revokeRoleMult()', () => {
+ it('should fail: array is empty', async () => {
+ const { accessControl, owner } = await loadFixture(defaultDeploy);
- it('onlyInitializing', async () => {
- const { accessControl, owner } = await loadFixture(defaultDeploy);
+ await revokeRoleMultTester({ accessControl, owner }, [], {
+ revertCustomError: { customErrorName: 'EmptyArray' },
+ });
+ });
- const wac = await new WithMidasAccessControlTester__factory(owner).deploy();
+ it('should fail: when user does not have admin role for roles[0]', async () => {
+ const { accessControl, regularAccounts, roles } = await loadFixture(
+ defaultDeploy,
+ );
- await expect(
- wac.initializeWithoutInitializer(accessControl.address),
- ).revertedWith('Initializable: contract is not initializing');
- });
+ await revokeRoleMultTester(
+ { accessControl, owner: regularAccounts[0] },
+ [
+ {
+ role: roles.common.blacklisted,
+ account: regularAccounts[1].address,
+ },
+ ],
+ { revertCustomError: acErrors.WMAC_HASNT_PERMISSION() },
+ );
+ });
- describe('modifier onlyRole', () => {
- it('should fail when call from non DEFAULT_ADMIN_ROLE address', async () => {
- const { wAccessControlTester, regularAccounts, roles } =
+ it('should fail: when user has admin role but roles[1] admin role is different fron roles[0]', async () => {
+ const { accessControl, owner, regularAccounts, roles } =
await loadFixture(defaultDeploy);
- await expect(
- wAccessControlTester
- .connect(regularAccounts[1])
- .withOnlyRole(roles.common.blacklisted, regularAccounts[0].address),
- ).revertedWith('WMAC: hasnt role');
+
+ await grantRoleTester(
+ { accessControl, owner },
+ roles.common.blacklistedOperator,
+ regularAccounts[0].address,
+ );
+
+ await revokeRoleMultTester(
+ { accessControl, owner: regularAccounts[0] },
+ [
+ {
+ role: roles.common.blacklisted,
+ account: regularAccounts[1].address,
+ },
+ {
+ role: roles.common.greenlisted,
+ account: regularAccounts[2].address,
+ },
+ ],
+ { revertCustomError: { customErrorName: 'RoleAdminMismatch' } },
+ );
});
- it('call from DEFAULT_ADMIN_ROLE address', async () => {
- const { wAccessControlTester, owner, roles } = await loadFixture(
- defaultDeploy,
+ it('should fail: when trying to revoke DEFAULT_ADMIN_ROLE from self', async () => {
+ const { accessControl, owner, roles } = await loadFixture(defaultDeploy);
+
+ await revokeRoleMultTester(
+ { accessControl, owner },
+ [{ role: roles.common.defaultAdmin, account: owner.address }],
+ { revertCustomError: { customErrorName: 'CannotRevokeFromSelf' } },
);
- await expect(
- wAccessControlTester.withOnlyRole(
- roles.common.blacklistedOperator,
- owner.address,
- ),
- ).not.reverted;
});
- });
- describe('modifier onlyNotRole', () => {
- it('should fail when call from DEFAULT_ADMIN_ROLE address', async () => {
- const { wAccessControlTester, owner, roles } = await loadFixture(
- defaultDeploy,
+ it('when revoking role from self but its not DEFAULT_ADMIN_ROLE (should not fail)', async () => {
+ const { accessControl, owner, roles } = await loadFixture(defaultDeploy);
+
+ await revokeRoleMultTester({ accessControl, owner }, [
+ { role: roles.common.blacklistedOperator, account: owner.address },
+ ]);
+ });
+
+ it('should fail: when revoking DEFAULT_ADMIN_ROLE from self and timelock delay is not 0', async () => {
+ // Locks in that _validateRevokeRole uses the resolved proposer (actualSender),
+ // not msg.sender. If it compared against the timelock address, self-revoke
+ // via timelock would incorrectly succeed.
+ const { accessControl, owner, roles, timelock, timelockManager } =
+ await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [roles.common.defaultAdmin],
+ [3600],
);
- await expect(
- wAccessControlTester.withOnlyNotRole(
- roles.common.blacklistedOperator,
- owner.address,
- ),
- ).revertedWith('WMAC: has role');
+
+ const data = accessControl.interface.encodeFunctionData(
+ 'revokeRoleMult',
+ [[{ role: roles.common.defaultAdmin, account: owner.address }]],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [accessControl.address],
+ [data],
+ {},
+ { from: owner },
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ accessControl.address,
+ data,
+ owner.address,
+ {
+ from: owner,
+ revertMessage: 'TimelockController: underlying transaction reverted',
+ },
+ );
+
+ expect(
+ await accessControl.hasRole(roles.common.defaultAdmin, owner.address),
+ ).eq(true);
});
- it('call from non DEFAULT_ADMIN_ROLE address', async () => {
- const { wAccessControlTester, regularAccounts, roles } =
+ it('when timelock delay is not 0 - schedule and execute the tx', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ timelock,
+ timelockManager,
+ } = await loadFixture(defaultDeploy);
+
+ await grantRoleMultTester({ accessControl, owner }, [
+ { role: roles.common.blacklisted, account: regularAccounts[0].address },
+ ]);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [roles.common.blacklistedOperator],
+ [3600],
+ );
+
+ const data = accessControl.interface.encodeFunctionData(
+ 'revokeRoleMult',
+ [
+ [
+ {
+ role: roles.common.blacklisted,
+ account: regularAccounts[0].address,
+ },
+ ],
+ ],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [accessControl.address],
+ [data],
+ {},
+ { from: owner },
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ accessControl.address,
+ data,
+ owner.address,
+ { from: owner },
+ );
+ });
+
+ it('should fail: when user have function access role but do not have role admin role', async () => {
+ const { accessControl, owner, regularAccounts, roles } =
await loadFixture(defaultDeploy);
- await expect(
- wAccessControlTester.withOnlyNotRole(
- roles.common.blacklisted,
- regularAccounts[1].address,
- ),
- ).not.reverted;
+
+ const selector = encodeFnSelector('revokeRoleMult(bytes32[],address[])');
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: roles.common.blacklistedOperator,
+ targetContract: accessControl.address,
+ functionSelector: selector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ accessControl.address,
+ selector,
+ [{ account: regularAccounts[0].address, enabled: true }],
+ );
+
+ await revokeRoleMultTester(
+ { accessControl, owner: regularAccounts[0] },
+ [
+ {
+ role: roles.common.blacklisted,
+ account: regularAccounts[1].address,
+ },
+ ],
+ { revertCustomError: acErrors.WMAC_HASNT_PERMISSION() },
+ );
+ });
+
+ it('when address do not have the role (shouldnt fail)', async () => {
+ const { accessControl, owner, regularAccounts, roles } =
+ await loadFixture(defaultDeploy);
+
+ await revokeRoleMultTester({ accessControl, owner }, [
+ { role: roles.common.blacklisted, account: regularAccounts[0].address },
+ ]);
});
});
- describe('grantRole()', () => {
- it('should fail when call from non role admin', async () => {
- const { wAccessControlTester, accessControl, regularAccounts, roles } =
+ describe('setRoleAdmin()', () => {
+ it('should fail: caller does not have `DEFAULT_ADMIN_ROLE`', async () => {
+ const { accessControl, regularAccounts, roles } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await handleRevert(
+ accessControl
+ .connect(regularAccounts[0])
+ .setRoleAdmin.bind(
+ this,
+ roles.common.blacklisted,
+ roles.common.greenlistedOperator,
+ ),
+ accessControl,
+ { revertCustomError: acErrors.WMAC_HASNT_PERMISSION() },
+ );
+ });
+
+ it('should fail: caller has admin role for another role', async () => {
+ const { accessControl, regularAccounts, owner, roles } =
await loadFixture(defaultDeploy);
- expect(
- await accessControl.hasRole(
- roles.common.blacklistedOperator,
- wAccessControlTester.address,
- ),
- ).eq(false);
+
+ await grantRoleTester(
+ { accessControl, owner },
+ roles.common.greenlistedOperator,
+ regularAccounts[0].address,
+ );
+
await expect(
- wAccessControlTester.grantRoleTester(
- roles.common.blacklisted,
- regularAccounts[1].address,
- ),
+ accessControl
+ .connect(regularAccounts[0])
+ .setRoleAdmin(
+ roles.common.blacklisted,
+ roles.common.greenlistedOperator,
+ ),
).reverted;
});
- it('call from role admin', async () => {
- const { accessControl, wAccessControlTester, regularAccounts, roles } =
+ it('caller has current role admin but not the DEFAULT_ADMIN_ROLE', async () => {
+ const { accessControl, roles, owner, regularAccounts } =
await loadFixture(defaultDeploy);
- await accessControl.grantRole(
+
+ await grantRoleTester(
+ { accessControl, owner },
roles.common.blacklistedOperator,
- wAccessControlTester.address,
+ regularAccounts[0].address,
);
+
await expect(
- wAccessControlTester.grantRoleTester(
- roles.common.blacklisted,
- regularAccounts[1].address,
- ),
+ accessControl
+ .connect(regularAccounts[0])
+ .setRoleAdmin(
+ roles.common.blacklisted,
+ roles.common.greenlistedOperator,
+ ),
).not.reverted;
});
- });
- describe('revokeRole()', () => {
- it('should fail when call from non role admin', async () => {
- const { wAccessControlTester, accessControl, regularAccounts, roles } =
- await loadFixture(defaultDeploy);
- expect(
- await accessControl.hasRole(
- roles.common.blacklistedOperator,
- wAccessControlTester.address,
- ),
- ).eq(false);
+ it('should fail: caller has DEFAULT_ADMIN_ROLE but not current role admin', async () => {
+ const { accessControl, owner, roles } = await loadFixture(defaultDeploy);
+
+ await accessControl.revokeRole(
+ roles.common.blacklistedOperator,
+ owner.address,
+ );
+
await expect(
- wAccessControlTester.revokeRoleTester(
+ accessControl.setRoleAdmin(
roles.common.blacklisted,
- regularAccounts[1].address,
+ roles.common.greenlistedOperator,
),
- ).reverted;
+ ).revertedWithCustomError(
+ accessControl,
+ acErrors.WMAC_HASNT_PERMISSION().customErrorName,
+ );
});
- it('call from role admin', async () => {
- const { accessControl, wAccessControlTester, regularAccounts, roles } =
+ it('should use new role admin for role management', async () => {
+ const { accessControl, owner, regularAccounts, roles } =
await loadFixture(defaultDeploy);
- await accessControl.grantRole(
+
+ const NEW_ADMIN_ROLE = ethers.utils.id('NEW_ADMIN_ROLE');
+ const TEST_ROLE = ethers.utils.id('TEST_ROLE');
+
+ await accessControl.setRoleAdmin(
+ NEW_ADMIN_ROLE,
roles.common.blacklistedOperator,
- wAccessControlTester.address,
);
- await wAccessControlTester.grantRoleTester(
- roles.common.blacklisted,
+
+ await grantRoleTester(
+ { accessControl, owner },
+ roles.common.blacklistedOperator,
+ regularAccounts[0].address,
+ );
+ await grantRoleTester(
+ { accessControl, owner },
+ NEW_ADMIN_ROLE,
regularAccounts[1].address,
);
- await expect(
- wAccessControlTester.revokeRoleTester(
- roles.common.blacklisted,
- regularAccounts[1].address,
- ),
- ).not.reverted;
+ await accessControl.setRoleAdmin(TEST_ROLE, NEW_ADMIN_ROLE);
+
+ await grantRoleTester(
+ { accessControl, owner },
+ TEST_ROLE,
+ regularAccounts[2].address,
+ undefined,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ },
+ );
+
+ await grantRoleTester(
+ { accessControl, owner },
+ TEST_ROLE,
+ regularAccounts[2].address,
+ undefined,
+ {
+ from: regularAccounts[1],
+ },
+ );
expect(
- await accessControl.hasRole(
- roles.common.blacklisted,
- regularAccounts[1].address,
+ await accessControl.hasRole(TEST_ROLE, regularAccounts[2].address),
+ ).eq(true);
+ });
+
+ it('when timelock delay is not 0 - schedule and execute the tx', async () => {
+ const { accessControl, owner, roles, timelock, timelockManager } =
+ await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [roles.common.blacklistedOperator],
+ [3600],
+ );
+
+ const data = accessControl.interface.encodeFunctionData('setRoleAdmin', [
+ roles.common.blacklisted,
+ roles.common.greenlistedOperator,
+ ]);
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [accessControl.address],
+ [data],
+ {},
+ { from: owner },
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ accessControl.address,
+ data,
+ owner.address,
+ { from: owner },
+ );
+ });
+
+ it('should fail: when user have function access role but do not have role admin role', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ const selector = encodeFnSelector('setRoleAdmin(bytes32,bytes32)');
+
+ await wAccessControlTester.setContractAdminRole(
+ roles.common.blacklistedOperator,
+ );
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: roles.common.blacklistedOperator,
+ targetContract: wAccessControlTester.address,
+ functionSelector: selector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ roles.common.blacklistedOperator,
+ wAccessControlTester.address,
+ selector,
+ [{ account: regularAccounts[0].address, enabled: true }],
+ );
+
+ await expect(
+ accessControl
+ .connect(regularAccounts[0])
+ .setRoleAdmin(
+ roles.common.blacklisted,
+ roles.common.greenlistedOperator,
+ ),
+ ).revertedWithCustomError(
+ accessControl,
+ acErrors.WMAC_HASNT_PERMISSION().customErrorName,
+ );
+ });
+ });
+
+ describe('setUserFacingRoleMult()', () => {
+ it('should fail: non-DEFAULT_ADMIN reverts', async () => {
+ const { accessControl, regularAccounts, roles } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await setIsUserFacingRoleTester(
+ {
+ accessControl,
+ owner: regularAccounts[0],
+ },
+ [
+ {
+ role: roles.common.greenlistedOperator,
+ enabled: true,
+ },
+ ],
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ },
+ );
+ });
+
+ it('call from DEFAULT_ADMIN_ROLE', async () => {
+ const { accessControl, owner, roles } = await loadFixture(defaultDeploy);
+
+ await setIsUserFacingRoleTester(
+ {
+ accessControl,
+ owner,
+ },
+ [
+ {
+ role: roles.common.greenlistedOperator,
+ enabled: true,
+ },
+ ],
+ );
+ });
+
+ it('when already user facing (should not revert)', async () => {
+ const { accessControl, owner, roles } = await loadFixture(defaultDeploy);
+
+ await setIsUserFacingRoleTester({ accessControl, owner }, [
+ { role: roles.common.blacklisted, enabled: true },
+ ]);
+ });
+
+ it('when already non-userfacing (should not revert)', async () => {
+ const { accessControl, owner, roles } = await loadFixture(defaultDeploy);
+
+ await setIsUserFacingRoleTester({ accessControl, owner }, [
+ { role: roles.common.blacklistedOperator, enabled: false },
+ ]);
+ });
+
+ it('when timelock delay is not 0 - schedule and execute the tx', async () => {
+ const { accessControl, owner, roles, timelock, timelockManager } =
+ await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [roles.common.defaultAdmin],
+ [3600],
+ );
+
+ const data = accessControl.interface.encodeFunctionData(
+ 'setUserFacingRoleMult',
+ [[{ role: roles.common.blacklistedOperator, enabled: true }]],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [accessControl.address],
+ [data],
+ {},
+ { from: owner },
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ accessControl.address,
+ data,
+ owner.address,
+ { from: owner },
+ );
+ });
+
+ it('should fail: when user have function access role but do not have role admin role', async () => {
+ const { accessControl, owner, regularAccounts, roles } =
+ await loadFixture(defaultDeploy);
+
+ const selector = encodeFnSelector(
+ 'setUserFacingRoleMult((bytes32,bool)[])',
+ );
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: roles.common.defaultAdmin,
+ targetContract: accessControl.address,
+ functionSelector: selector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ accessControl.address,
+ selector,
+ [{ account: regularAccounts[0].address, enabled: true }],
+ );
+
+ await setIsUserFacingRoleTester(
+ { accessControl, owner: regularAccounts[0] },
+ [{ role: roles.common.blacklistedOperator, enabled: true }],
+ { revertCustomError: acErrors.WMAC_HASNT_PERMISSION() },
+ );
+ });
+
+ it('should fail: when params lenght is 0', async () => {
+ const { accessControl, owner } = await loadFixture(defaultDeploy);
+
+ await setIsUserFacingRoleTester({ accessControl, owner }, [], {
+ revertCustomError: { customErrorName: 'EmptyArray' },
+ });
+ });
+
+ it('when switching enabled to disabled', async () => {
+ const { accessControl, owner, roles } = await loadFixture(defaultDeploy);
+
+ await setIsUserFacingRoleTester({ accessControl, owner }, [
+ { role: roles.common.greenlistedOperator, enabled: true },
+ ]);
+
+ await setIsUserFacingRoleTester({ accessControl, owner }, [
+ { role: roles.common.greenlistedOperator, enabled: false },
+ ]);
+ });
+ });
+
+ describe('setGrantOperatorRoleMult()', () => {
+ it('should fail: reverts when role is user facing role', async () => {
+ const { accessControl, owner, wAccessControlTester, roles } =
+ await loadFixture(defaultDeploy);
+
+ await wAccessControlTester.setContractAdminRole(roles.common.greenlisted);
+ await setGrantOperatorRoleTester(
+ {
+ accessControl,
+ owner,
+ },
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: encodeFnSelector('setGreenlistEnable(bool)'),
+ operator: owner.address,
+ enabled: true,
+ },
+ ],
+ {
+ revertCustomError: {
+ customErrorName: 'UserFacingRoleNotAllowed',
+ },
+ },
+ );
+ });
+
+ it('when role is not user facing role', async () => {
+ const { accessControl, owner, roles, wAccessControlTester } =
+ await loadFixture(defaultDeploy);
+
+ await wAccessControlTester.setContractAdminRole(
+ roles.common.greenlistedOperator,
+ );
+
+ await setGrantOperatorRoleTester(
+ {
+ accessControl,
+ owner,
+ },
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: encodeFnSelector('setGreenlistEnable(bool)'),
+ operator: owner.address,
+ enabled: true,
+ },
+ ],
+ );
+ });
+
+ it('when address is already grant operator (should not revert)', async () => {
+ const { accessControl, owner, roles, wAccessControlTester } =
+ await loadFixture(defaultDeploy);
+
+ const params = [
+ {
+ functionSelector: encodeFnSelector('setGreenlistEnable(bool)'),
+ operator: owner.address,
+ enabled: true,
+ },
+ ];
+
+ await wAccessControlTester.setContractAdminRole(
+ roles.common.greenlistedOperator,
+ );
+
+ await setGrantOperatorRoleTester(
+ { accessControl, owner },
+ wAccessControlTester.address,
+ params,
+ );
+
+ await setGrantOperatorRoleTester(
+ { accessControl, owner },
+ wAccessControlTester.address,
+ params,
+ );
+ });
+
+ it('when timelock delay is not 0 - schedule and execute the tx', async () => {
+ const {
+ accessControl,
+ owner,
+ roles,
+ timelock,
+ timelockManager,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [roles.common.greenlistedOperator],
+ [3600],
+ );
+
+ await wAccessControlTester.setContractAdminRole(
+ roles.common.greenlistedOperator,
+ );
+ const data = accessControl.interface.encodeFunctionData(
+ 'setGrantOperatorRoleMult',
+ [
+ wAccessControlTester.address,
+ [
+ {
+ delay: 0,
+ functionSelector: encodeFnSelector('setGreenlistEnable(bool)'),
+ operator: owner.address,
+ enabled: true,
+ },
+ ],
+ ],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [accessControl.address],
+ [data],
+ {},
+ { from: owner },
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ accessControl.address,
+ data,
+ owner.address,
+ { from: owner },
+ );
+ });
+
+ it('should fail: when user have function access role but do not have masterRole', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ const selector = encodeFnSelector(
+ 'setGrantOperatorRoleMult(bytes32,(address,bytes4,address,bool)[])',
+ );
+ await wAccessControlTester.setContractAdminRole(
+ roles.common.greenlistedOperator,
+ );
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: roles.common.greenlistedOperator,
+ targetContract: wAccessControlTester.address,
+ functionSelector: selector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ wAccessControlTester.address,
+ selector,
+ [{ account: regularAccounts[0].address, enabled: true }],
+ );
+
+ await setGrantOperatorRoleTester(
+ { accessControl, owner: regularAccounts[0] },
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: encodeFnSelector('setGreenlistEnable(bool)'),
+ operator: regularAccounts[1].address,
+ enabled: true,
+ },
+ ],
+ { revertCustomError: acErrors.WMAC_HASNT_PERMISSION() },
+ );
+ });
+
+ it('should fail: when params lenght is 0', async () => {
+ const { accessControl, owner, wAccessControlTester } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await setGrantOperatorRoleTester(
+ { accessControl, owner },
+ wAccessControlTester.address,
+ [],
+ {
+ revertCustomError: { customErrorName: 'EmptyArray' },
+ },
+ );
+ });
+
+ it('when switching enabled to disabled', async () => {
+ const { accessControl, owner, roles, wAccessControlTester } =
+ await loadFixture(defaultDeploy);
+
+ const selector = encodeFnSelector('setGreenlistEnable(bool)');
+
+ await wAccessControlTester.setContractAdminRole(
+ roles.common.greenlistedOperator,
+ );
+
+ const params = [
+ {
+ functionSelector: selector,
+ operator: owner.address,
+ enabled: true,
+ },
+ ];
+
+ await setGrantOperatorRoleTester(
+ { accessControl, owner },
+ wAccessControlTester.address,
+ params,
+ );
+
+ await setGrantOperatorRoleTester(
+ { accessControl, owner },
+ wAccessControlTester.address,
+ [{ ...params[0], enabled: false }],
+ );
+ });
+
+ it('when delay is not NULL_DELAY but actual delay is NULL_DELAY - should set the delay', async () => {
+ const { accessControl, owner, roles, wAccessControlTester } =
+ await loadFixture(defaultDeploy);
+
+ const selector = encodeFnSelector('setGreenlistEnable(bool)');
+
+ await wAccessControlTester.setContractAdminRole(
+ roles.common.greenlistedOperator,
+ );
+
+ await setGrantOperatorRoleTester(
+ { accessControl, owner },
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: selector,
+ operator: owner.address,
+ enabled: true,
+ delay: 3600,
+ },
+ ],
+ );
+ });
+
+ it('should fail: when delay is not NULL_DELAY but actual delay is also not null', async () => {
+ const {
+ accessControl,
+ owner,
+ roles,
+ timelock,
+ timelockManager,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ const selector = encodeFnSelector('setGreenlistEnable(bool)');
+
+ await wAccessControlTester.setContractAdminRole(
+ roles.common.greenlistedOperator,
+ );
+
+ const operatorRoleKey = await accessControl.grantOperatorRoleKey(
+ roles.common.greenlistedOperator,
+ wAccessControlTester.address,
+ selector,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [operatorRoleKey],
+ [3600],
+ );
+
+ await setGrantOperatorRoleTester(
+ { accessControl, owner },
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: selector,
+ operator: owner.address,
+ enabled: true,
+ delay: 7200,
+ },
+ ],
+ { revertCustomError: { customErrorName: 'DelayIsAlreadySet' } },
+ );
+ });
+ });
+
+ describe('setPermissionRoleMult()', () => {
+ it('when caller is function operator', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ const selector = encodeFnSelector('setGreenlistEnable(bool)');
+ await wAccessControlTester.setContractAdminRole(
+ roles.common.greenlistedOperator,
+ );
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: roles.common.greenlistedOperator,
+ targetContract: wAccessControlTester.address,
+ functionSelector: selector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ wAccessControlTester.address,
+ selector,
+ [
+ {
+ account: regularAccounts[0].address,
+ enabled: true,
+ },
+ ],
+ );
+ });
+
+ it('extracts master role from target contract when not provided', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ const selector = encodeFnSelector('setGreenlistEnable(bool)');
+ const contractAdminRole = roles.common.greenlistedOperator;
+
+ await wAccessControlTester.setContractAdminRole(contractAdminRole);
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: contractAdminRole,
+ targetContract: wAccessControlTester.address,
+ functionSelector: selector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ wAccessControlTester.address,
+ selector,
+ [{ account: regularAccounts[0].address, enabled: true }],
+ );
+
+ expect(
+ await accessControl[
+ 'hasFunctionPermission(bytes32,address,bytes4,address)'
+ ](
+ contractAdminRole,
+ wAccessControlTester.address,
+ selector,
+ regularAccounts[0].address,
+ ),
+ ).eq(true);
+ expect(
+ await accessControl[
+ 'hasFunctionPermission(bytes32,address,bytes4,address)'
+ ](
+ roles.common.defaultAdmin,
+ wAccessControlTester.address,
+ selector,
+ regularAccounts[0].address,
),
).eq(false);
});
+
+ it('should fail: caller is not a grant operator', async () => {
+ const { accessControl, owner, regularAccounts, roles } =
+ await loadFixture(defaultDeploy);
+
+ const selector = encodeFnSelector('setGreenlistEnable(bool)');
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: roles.common.greenlistedOperator,
+ targetContract: accessControl.address,
+ functionSelector: selector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner: regularAccounts[1] },
+ undefined,
+ accessControl.address,
+ selector,
+ [
+ {
+ account: regularAccounts[2].address,
+ enabled: true,
+ },
+ ],
+ undefined,
+ { revertCustomError: acErrors.WMAC_HASNT_PERMISSION() },
+ );
+ });
+
+ it('should fail: caller is an operator for a different function', async () => {
+ const { accessControl, owner, regularAccounts, roles } =
+ await loadFixture(defaultDeploy);
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: roles.common.greenlistedOperator,
+ targetContract: accessControl.address,
+ functionSelector: encodeFnSelector('setGreenlistEnable1(bool)'),
+ grantOperator: owner,
+ });
+
+ const selector = encodeFnSelector('setGreenlistEnable(bool)');
+
+ await grantRoleTester(
+ { accessControl, owner },
+ roles.common.defaultAdmin,
+ regularAccounts[2],
+ );
+
+ await revokeRoleTester(
+ { accessControl, owner },
+ roles.common.defaultAdmin,
+ owner,
+ {
+ from: regularAccounts[2],
+ },
+ );
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ accessControl.address,
+ selector,
+ [
+ {
+ account: regularAccounts[2].address,
+ enabled: true,
+ },
+ ],
+ undefined,
+ { revertCustomError: acErrors.WMAC_HASNT_PERMISSION() },
+ );
+ });
+
+ it('when address is already has permission (shouldnt fail)', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ const selector = encodeFnSelector('setGreenlistEnable(bool)');
+
+ await wAccessControlTester.setContractAdminRole(
+ roles.common.greenlistedOperator,
+ );
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: roles.common.greenlistedOperator,
+ targetContract: wAccessControlTester.address,
+ functionSelector: selector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ wAccessControlTester.address,
+ selector,
+ [{ account: regularAccounts[0].address, enabled: true }],
+ );
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ wAccessControlTester.address,
+ selector,
+ [{ account: regularAccounts[0].address, enabled: true }],
+ );
+ });
+
+ it('should fail: when params lenght is 0', async () => {
+ const { accessControl, owner, roles } = await loadFixture(defaultDeploy);
+
+ const selector = encodeFnSelector('setGreenlistEnable(bool)');
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: roles.common.greenlistedOperator,
+ targetContract: accessControl.address,
+ functionSelector: selector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ accessControl.address,
+ selector,
+ [],
+ undefined,
+ { revertCustomError: { customErrorName: 'EmptyArray' } },
+ );
+ });
+
+ it('should fail: when user do not have grant operator role', async () => {
+ const { accessControl, owner, regularAccounts, roles } =
+ await loadFixture(defaultDeploy);
+
+ const selector = encodeFnSelector('setGreenlistEnable(bool)');
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: roles.common.greenlistedOperator,
+ targetContract: accessControl.address,
+ functionSelector: selector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner: regularAccounts[0] },
+ undefined,
+ accessControl.address,
+ selector,
+ [{ account: regularAccounts[1].address, enabled: true }],
+ undefined,
+ { revertCustomError: acErrors.WMAC_HASNT_PERMISSION() },
+ );
+ });
+
+ it('when switching enabled to disabled', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ const selector = encodeFnSelector('setGreenlistEnable(bool)');
+
+ await wAccessControlTester.setContractAdminRole(
+ roles.common.greenlistedOperator,
+ );
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: roles.common.greenlistedOperator,
+ targetContract: wAccessControlTester.address,
+ functionSelector: selector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ wAccessControlTester.address,
+ selector,
+ [{ account: regularAccounts[0].address, enabled: true }],
+ );
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ wAccessControlTester.address,
+ selector,
+ [{ account: regularAccounts[0].address, enabled: false }],
+ );
+ });
+
+ it('when delay is not NULL_DELAY but actual delay is NULL_DELAY - should set the delay', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ const selector = encodeFnSelector('setGreenlistEnable(bool)');
+
+ await wAccessControlTester.setContractAdminRole(
+ roles.common.greenlistedOperator,
+ );
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: roles.common.greenlistedOperator,
+ targetContract: wAccessControlTester.address,
+ functionSelector: selector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ wAccessControlTester.address,
+ selector,
+ [{ account: regularAccounts[0].address, enabled: true }],
+ 3600,
+ );
+ });
+
+ it('should fail: when delay is not NULL_DELAY but actual delay is also not null', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ const selector = encodeFnSelector('setGreenlistEnable(bool)');
+
+ await wAccessControlTester.setContractAdminRole(
+ roles.common.greenlistedOperator,
+ );
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: roles.common.greenlistedOperator,
+ targetContract: wAccessControlTester.address,
+ functionSelector: selector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ wAccessControlTester.address,
+ selector,
+ [{ account: regularAccounts[0].address, enabled: true }],
+ 3600,
+ );
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ wAccessControlTester.address,
+ selector,
+ [{ account: regularAccounts[1].address, enabled: true }],
+ 7200,
+ { revertCustomError: { customErrorName: 'DelayIsAlreadySet' } },
+ );
+ });
+
+ it('when master batches multiple selectors for one account', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ const selectorA = encodeFnSelector('withOnlyRole(bytes32,bool)');
+ const selectorB = encodeFnSelector('withOnlyContractAdmin()');
+ const masterRole = roles.common.greenlistedOperator;
+ const account = regularAccounts[0].address;
+
+ await wAccessControlTester.setContractAdminRole(masterRole);
+
+ await setPermissionRoleMultTester(
+ { accessControl, owner },
+ undefined,
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: selectorA,
+ account,
+ enabled: true,
+ },
+ {
+ functionSelector: selectorB,
+ account,
+ enabled: true,
+ },
+ ],
+ );
+
+ expect(
+ await accessControl[
+ 'hasFunctionPermission(bytes32,address,bytes4,address)'
+ ](masterRole, wAccessControlTester.address, selectorA, account),
+ ).eq(true);
+ expect(
+ await accessControl[
+ 'hasFunctionPermission(bytes32,address,bytes4,address)'
+ ](masterRole, wAccessControlTester.address, selectorB, account),
+ ).eq(true);
+ });
+
+ it('when master batches multiple selectors for multiple accounts', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ const selectorA = encodeFnSelector('withOnlyRole(bytes32,bool)');
+ const selectorB = encodeFnSelector('withOnlyContractAdmin()');
+ const masterRole = roles.common.greenlistedOperator;
+
+ await wAccessControlTester.setContractAdminRole(masterRole);
+
+ await setPermissionRoleMultTester(
+ { accessControl, owner },
+ undefined,
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: selectorA,
+ account: regularAccounts[0].address,
+ enabled: true,
+ },
+ {
+ functionSelector: selectorB,
+ account: regularAccounts[1].address,
+ enabled: true,
+ },
+ ],
+ );
+
+ expect(
+ await accessControl[
+ 'hasFunctionPermission(bytes32,address,bytes4,address)'
+ ](
+ masterRole,
+ wAccessControlTester.address,
+ selectorA,
+ regularAccounts[0].address,
+ ),
+ ).eq(true);
+ expect(
+ await accessControl[
+ 'hasFunctionPermission(bytes32,address,bytes4,address)'
+ ](
+ masterRole,
+ wAccessControlTester.address,
+ selectorB,
+ regularAccounts[1].address,
+ ),
+ ).eq(true);
+ });
+
+ it('when operator batches multiple accounts for the same selector', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ const selector = encodeFnSelector('setGreenlistEnable(bool)');
+ const masterRole = roles.common.greenlistedOperator;
+
+ await wAccessControlTester.setContractAdminRole(masterRole);
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole,
+ targetContract: wAccessControlTester.address,
+ functionSelector: selector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleMultTester(
+ { accessControl, owner },
+ undefined,
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: selector,
+ account: regularAccounts[0].address,
+ enabled: true,
+ },
+ {
+ functionSelector: selector,
+ account: regularAccounts[1].address,
+ enabled: true,
+ },
+ ],
+ );
+
+ expect(
+ await accessControl[
+ 'hasFunctionPermission(bytes32,address,bytes4,address)'
+ ](
+ masterRole,
+ wAccessControlTester.address,
+ selector,
+ regularAccounts[0].address,
+ ),
+ ).eq(true);
+ expect(
+ await accessControl[
+ 'hasFunctionPermission(bytes32,address,bytes4,address)'
+ ](
+ masterRole,
+ wAccessControlTester.address,
+ selector,
+ regularAccounts[1].address,
+ ),
+ ).eq(true);
+ });
+
+ it('should fail: when operator batches mixed selectors', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ const selectorA = encodeFnSelector('withOnlyRole(bytes32,bool)');
+ const selectorB = encodeFnSelector('withOnlyContractAdmin()');
+ const masterRole = roles.common.greenlistedOperator;
+ const operator = regularAccounts[0];
+
+ await wAccessControlTester.setContractAdminRole(masterRole);
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole,
+ targetContract: wAccessControlTester.address,
+ functionSelector: selectorA,
+ grantOperator: operator,
+ });
+
+ expect(await accessControl.hasRole(masterRole, operator.address)).eq(
+ false,
+ );
+
+ await setPermissionRoleMultTester(
+ { accessControl, owner },
+ undefined,
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: selectorA,
+ account: regularAccounts[1].address,
+ enabled: true,
+ },
+ {
+ functionSelector: selectorB,
+ account: regularAccounts[1].address,
+ enabled: true,
+ },
+ ],
+ {
+ from: operator,
+ revertCustomError: {
+ customErrorName: 'FunctionSelectorMismatch',
+ },
+ },
+ );
+ });
+
+ it('when master batches multiple selectors with per-param delays', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ const selectorA = encodeFnSelector('withOnlyRole(bytes32,bool)');
+ const selectorB = encodeFnSelector('withOnlyContractAdmin()');
+ const masterRole = roles.common.greenlistedOperator;
+ const account = regularAccounts[0].address;
+
+ await wAccessControlTester.setContractAdminRole(masterRole);
+
+ await setPermissionRoleMultTester(
+ { accessControl, owner },
+ undefined,
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: selectorA,
+ account,
+ enabled: true,
+ delay: 3600,
+ },
+ {
+ functionSelector: selectorB,
+ account,
+ enabled: true,
+ delay: 7200,
+ },
+ ],
+ );
+
+ const keyA = await accessControl.permissionRoleKey(
+ masterRole,
+ wAccessControlTester.address,
+ selectorA,
+ );
+ const keyB = await accessControl.permissionRoleKey(
+ masterRole,
+ wAccessControlTester.address,
+ selectorB,
+ );
+
+ expect((await accessControl.getRoleTimelockDelay(keyA, 0))[0]).eq(3600);
+ expect((await accessControl.getRoleTimelockDelay(keyB, 0))[0]).eq(7200);
+ });
+
+ describe('without timelock', () => {
+ it('when caller has master role but not operator role - uses master role', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ const selector = encodeFnSelector('setGreenlistEnable(bool)');
+ const masterRole = roles.common.greenlistedOperator;
+
+ await wAccessControlTester.setContractAdminRole(masterRole);
+
+ expect(await accessControl.hasRole(masterRole, owner.address)).eq(true);
+ expect(
+ await accessControl[
+ 'isFunctionAccessGrantOperator(bytes32,address,bytes4,address)'
+ ](masterRole, wAccessControlTester.address, selector, owner.address),
+ ).eq(false);
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ wAccessControlTester.address,
+ selector,
+ [{ account: regularAccounts[0].address, enabled: true }],
+ );
+ });
+
+ it('when caller has operator role but not master role - uses operator role', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ timelock,
+ timelockManager,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ const selector = encodeFnSelector('setGreenlistEnable(bool)');
+ const masterRole = roles.common.greenlistedOperator;
+ const operator = regularAccounts[0];
+
+ await wAccessControlTester.setContractAdminRole(masterRole);
+
+ await setGrantOperatorRoleTester(
+ { accessControl, owner },
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: selector,
+ operator: operator.address,
+ enabled: true,
+ },
+ ],
+ );
+
+ const operatorRoleKey = await accessControl.grantOperatorRoleKey(
+ masterRole,
+ wAccessControlTester.address,
+ selector,
+ );
+
+ // master has delay so direct call would fail if master role were wrongly selected
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [masterRole],
+ [3600],
+ );
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [operatorRoleKey],
+ [NO_DELAY],
+ );
+
+ expect(await accessControl.hasRole(masterRole, operator.address)).eq(
+ false,
+ );
+ expect(
+ await accessControl[
+ 'isFunctionAccessGrantOperator(bytes32,address,bytes4,address)'
+ ](
+ masterRole,
+ wAccessControlTester.address,
+ selector,
+ operator.address,
+ ),
+ ).eq(true);
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ wAccessControlTester.address,
+ selector,
+ [{ account: regularAccounts[1].address, enabled: true }],
+ undefined,
+ { from: operator },
+ );
+ });
+
+ it('when caller has both roles and master has no delay but operator does - uses master role', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ timelock,
+ timelockManager,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ const selector = encodeFnSelector('setGreenlistEnable(bool)');
+ const masterRole = roles.common.greenlistedOperator;
+
+ await wAccessControlTester.setContractAdminRole(masterRole);
+
+ const operatorRoleKey = await accessControl.grantOperatorRoleKey(
+ masterRole,
+ wAccessControlTester.address,
+ selector,
+ );
+
+ await setGrantOperatorRoleTester(
+ { accessControl, owner },
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: selector,
+ operator: owner.address,
+ enabled: true,
+ },
+ ],
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [operatorRoleKey],
+ [3600],
+ );
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ wAccessControlTester.address,
+ selector,
+ [{ account: regularAccounts[0].address, enabled: true }],
+ );
+ });
+
+ it('when caller has both roles and operator has no delay but master does - uses operator role', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ timelock,
+ timelockManager,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ const selector = encodeFnSelector('setGreenlistEnable(bool)');
+ const masterRole = roles.common.greenlistedOperator;
+
+ await wAccessControlTester.setContractAdminRole(masterRole);
+
+ const operatorRoleKey = await accessControl.grantOperatorRoleKey(
+ masterRole,
+ wAccessControlTester.address,
+ selector,
+ );
+
+ await setGrantOperatorRoleTester(
+ { accessControl, owner },
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: selector,
+ operator: owner.address,
+ enabled: true,
+ },
+ ],
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [masterRole],
+ [3600],
+ );
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [operatorRoleKey],
+ [NO_DELAY],
+ );
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ wAccessControlTester.address,
+ selector,
+ [{ account: regularAccounts[0].address, enabled: true }],
+ );
+ });
+
+ it('when caller has both roles and operator has shorter delay - should fail on mixed selectors', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ timelock,
+ timelockManager,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ const selectorA = encodeFnSelector('withOnlyRole(bytes32,bool)');
+ const selectorB = encodeFnSelector('withOnlyContractAdmin()');
+ const masterRole = roles.common.greenlistedOperator;
+
+ await wAccessControlTester.setContractAdminRole(masterRole);
+
+ const operatorRoleKey = await accessControl.grantOperatorRoleKey(
+ masterRole,
+ wAccessControlTester.address,
+ selectorA,
+ );
+
+ await setGrantOperatorRoleTester(
+ { accessControl, owner },
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: selectorA,
+ operator: owner.address,
+ enabled: true,
+ },
+ ],
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [masterRole],
+ [7200],
+ );
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [operatorRoleKey],
+ [NO_DELAY],
+ );
+
+ await setPermissionRoleMultTester(
+ { accessControl, owner },
+ undefined,
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: selectorA,
+ account: regularAccounts[0].address,
+ enabled: true,
+ },
+ {
+ functionSelector: selectorB,
+ account: regularAccounts[0].address,
+ enabled: true,
+ },
+ ],
+ {
+ revertCustomError: {
+ customErrorName: 'FunctionSelectorMismatch',
+ },
+ },
+ );
+ });
+
+ it('when caller has both roles and master has shorter delay - can batch mixed selectors', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ timelock,
+ timelockManager,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ const selectorA = encodeFnSelector('withOnlyRole(bytes32,bool)');
+ const selectorB = encodeFnSelector('withOnlyContractAdmin()');
+ const masterRole = roles.common.greenlistedOperator;
+ const account = regularAccounts[0].address;
+
+ await wAccessControlTester.setContractAdminRole(masterRole);
+
+ const operatorRoleKey = await accessControl.grantOperatorRoleKey(
+ masterRole,
+ wAccessControlTester.address,
+ selectorA,
+ );
+
+ await setGrantOperatorRoleTester(
+ { accessControl, owner },
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: selectorA,
+ operator: owner.address,
+ enabled: true,
+ },
+ ],
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [operatorRoleKey],
+ [3600],
+ );
+
+ await setPermissionRoleMultTester(
+ { accessControl, owner },
+ undefined,
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: selectorA,
+ account,
+ enabled: true,
+ },
+ {
+ functionSelector: selectorB,
+ account,
+ enabled: true,
+ },
+ ],
+ );
+
+ expect(
+ await accessControl[
+ 'hasFunctionPermission(bytes32,address,bytes4,address)'
+ ](masterRole, wAccessControlTester.address, selectorA, account),
+ ).eq(true);
+ expect(
+ await accessControl[
+ 'hasFunctionPermission(bytes32,address,bytes4,address)'
+ ](masterRole, wAccessControlTester.address, selectorB, account),
+ ).eq(true);
+ });
+ });
+
+ describe('with timelock', () => {
+ const encodeSetPermissionRoleMult = (
+ accessControl: MidasAccessControl,
+ target: string,
+ params: {
+ functionSelector: string;
+ account: string;
+ enabled: boolean;
+ delay?: number;
+ }[],
+ ) =>
+ accessControl.interface.encodeFunctionData(
+ 'setPermissionRoleMult(address,(bytes4,address,uint32,bool)[])',
+ [
+ target,
+ params.map((param) => [
+ param.functionSelector,
+ param.account,
+ param.delay ?? 0,
+ param.enabled,
+ ]),
+ ],
+ );
+
+ const scheduleAndExecuteSetPermissionRoleMult = async (
+ {
+ accessControl,
+ owner,
+ timelock,
+ timelockManager,
+ }: {
+ accessControl: MidasAccessControl;
+ owner: SignerWithAddress;
+ timelock: MidasAccessControlTimelockController;
+ timelockManager: MidasTimelockManager;
+ },
+ data: string,
+ delay: number,
+ from: SignerWithAddress,
+ ) => {
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [accessControl.address],
+ [data],
+ {},
+ { from },
+ );
+
+ await increase(delay);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ accessControl.address,
+ data,
+ from.address,
+ { from: owner },
+ );
+ };
+
+ it('when caller has master role but not operator role - schedule and execute', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ timelock,
+ timelockManager,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ const selector = encodeFnSelector('setGreenlistEnable(bool)');
+ const masterRole = roles.common.greenlistedOperator;
+ const delay = 3600;
+ const permissionAccount = regularAccounts[0].address;
+
+ await wAccessControlTester.setContractAdminRole(masterRole);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [masterRole],
+ [delay],
+ );
+
+ expect(await accessControl.hasRole(masterRole, owner.address)).eq(true);
+ expect(
+ await accessControl[
+ 'isFunctionAccessGrantOperator(bytes32,address,bytes4,address)'
+ ](masterRole, wAccessControlTester.address, selector, owner.address),
+ ).eq(false);
+
+ const data = encodeSetPermissionRoleMult(
+ accessControl,
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: selector,
+ account: permissionAccount,
+ enabled: true,
+ },
+ ],
+ );
+
+ const [roleUsed] = await timelockManager.getTargetRole(
+ accessControl.address,
+ data,
+ owner.address,
+ );
+ expect(roleUsed).eq(masterRole);
+
+ await scheduleAndExecuteSetPermissionRoleMult(
+ { accessControl, owner, timelock, timelockManager },
+ data,
+ delay,
+ owner,
+ );
+
+ expect(
+ await accessControl[
+ 'hasFunctionPermission(bytes32,address,bytes4,address)'
+ ](
+ masterRole,
+ wAccessControlTester.address,
+ selector,
+ permissionAccount,
+ ),
+ ).eq(true);
+ });
+
+ it('when caller has operator role but not master role - schedule and execute', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ timelock,
+ timelockManager,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ const selector = encodeFnSelector('setGreenlistEnable(bool)');
+ const masterRole = roles.common.greenlistedOperator;
+ const operator = regularAccounts[0];
+ const delay = 3600;
+ const permissionAccount = regularAccounts[1].address;
+
+ await wAccessControlTester.setContractAdminRole(masterRole);
+
+ await setGrantOperatorRoleTester(
+ { accessControl, owner },
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: selector,
+ operator: operator.address,
+ enabled: true,
+ },
+ ],
+ );
+
+ const operatorRoleKey = await accessControl.grantOperatorRoleKey(
+ masterRole,
+ wAccessControlTester.address,
+ selector,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [operatorRoleKey],
+ [delay],
+ );
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [masterRole],
+ [7200],
+ );
+
+ expect(await accessControl.hasRole(masterRole, operator.address)).eq(
+ false,
+ );
+ expect(
+ await accessControl[
+ 'isFunctionAccessGrantOperator(bytes32,address,bytes4,address)'
+ ](
+ masterRole,
+ wAccessControlTester.address,
+ selector,
+ operator.address,
+ ),
+ ).eq(true);
+
+ const data = encodeSetPermissionRoleMult(
+ accessControl,
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: selector,
+ account: permissionAccount,
+ enabled: true,
+ },
+ ],
+ );
+
+ const [roleUsed] = await timelockManager.getTargetRole(
+ accessControl.address,
+ data,
+ operator.address,
+ );
+ expect(roleUsed).eq(operatorRoleKey);
+
+ await scheduleAndExecuteSetPermissionRoleMult(
+ { accessControl, owner, timelock, timelockManager },
+ data,
+ delay,
+ operator,
+ );
+
+ expect(
+ await accessControl[
+ 'hasFunctionPermission(bytes32,address,bytes4,address)'
+ ](
+ masterRole,
+ wAccessControlTester.address,
+ selector,
+ permissionAccount,
+ ),
+ ).eq(true);
+ });
+
+ it('when caller has both roles with the same delay - schedule and execute', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ timelock,
+ timelockManager,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ const selector = encodeFnSelector('setGreenlistEnable(bool)');
+ const masterRole = roles.common.greenlistedOperator;
+ const delay = 3600;
+ const permissionAccount = regularAccounts[0].address;
+
+ await wAccessControlTester.setContractAdminRole(masterRole);
+
+ const operatorRoleKey = await accessControl.grantOperatorRoleKey(
+ masterRole,
+ wAccessControlTester.address,
+ selector,
+ );
+
+ await setGrantOperatorRoleTester(
+ { accessControl, owner },
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: selector,
+ operator: owner.address,
+ enabled: true,
+ },
+ ],
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [operatorRoleKey, masterRole],
+ [delay, delay],
+ );
+
+ const data = encodeSetPermissionRoleMult(
+ accessControl,
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: selector,
+ account: permissionAccount,
+ enabled: true,
+ },
+ ],
+ );
+
+ // equal delays → resolveAccessRole prefers master (rootDelay <= functionDelay)
+ const [roleUsed] = await timelockManager.getTargetRole(
+ accessControl.address,
+ data,
+ owner.address,
+ );
+ expect(roleUsed).eq(masterRole);
+
+ await scheduleAndExecuteSetPermissionRoleMult(
+ { accessControl, owner, timelock, timelockManager },
+ data,
+ delay,
+ owner,
+ );
+
+ expect(
+ await accessControl[
+ 'hasFunctionPermission(bytes32,address,bytes4,address)'
+ ](
+ masterRole,
+ wAccessControlTester.address,
+ selector,
+ permissionAccount,
+ ),
+ ).eq(true);
+ });
+
+ it('when caller has both roles and master has shorter delay - uses master role delay', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ timelock,
+ timelockManager,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ const selector = encodeFnSelector('setGreenlistEnable(bool)');
+ const masterRole = roles.common.greenlistedOperator;
+ const masterDelay = 3600;
+ const operatorDelay = 7200;
+ const permissionAccount = regularAccounts[0].address;
+
+ await wAccessControlTester.setContractAdminRole(masterRole);
+
+ const operatorRoleKey = await accessControl.grantOperatorRoleKey(
+ masterRole,
+ wAccessControlTester.address,
+ selector,
+ );
+
+ await setGrantOperatorRoleTester(
+ { accessControl, owner },
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: selector,
+ operator: owner.address,
+ enabled: true,
+ },
+ ],
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [masterRole, operatorRoleKey],
+ [masterDelay, operatorDelay],
+ );
+
+ const data = encodeSetPermissionRoleMult(
+ accessControl,
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: selector,
+ account: permissionAccount,
+ enabled: true,
+ },
+ ],
+ );
+
+ const [roleUsed] = await timelockManager.getTargetRole(
+ accessControl.address,
+ data,
+ owner.address,
+ );
+ expect(roleUsed).eq(masterRole);
+
+ await scheduleAndExecuteSetPermissionRoleMult(
+ { accessControl, owner, timelock, timelockManager },
+ data,
+ masterDelay,
+ owner,
+ );
+
+ expect(
+ await accessControl[
+ 'hasFunctionPermission(bytes32,address,bytes4,address)'
+ ](
+ masterRole,
+ wAccessControlTester.address,
+ selector,
+ permissionAccount,
+ ),
+ ).eq(true);
+ });
+
+ it('when caller has both roles and operator has shorter delay - uses operator role delay', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ timelock,
+ timelockManager,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ const selector = encodeFnSelector('setGreenlistEnable(bool)');
+ const masterRole = roles.common.greenlistedOperator;
+ const masterDelay = 7200;
+ const operatorDelay = 3600;
+ const permissionAccount = regularAccounts[0].address;
+
+ await wAccessControlTester.setContractAdminRole(masterRole);
+
+ const operatorRoleKey = await accessControl.grantOperatorRoleKey(
+ masterRole,
+ wAccessControlTester.address,
+ selector,
+ );
+
+ await setGrantOperatorRoleTester(
+ { accessControl, owner },
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: selector,
+ operator: owner.address,
+ enabled: true,
+ },
+ ],
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [masterRole, operatorRoleKey],
+ [masterDelay, operatorDelay],
+ );
+
+ const data = encodeSetPermissionRoleMult(
+ accessControl,
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: selector,
+ account: permissionAccount,
+ enabled: true,
+ },
+ ],
+ );
+
+ const [roleUsed] = await timelockManager.getTargetRole(
+ accessControl.address,
+ data,
+ owner.address,
+ );
+ expect(roleUsed).eq(operatorRoleKey);
+
+ await scheduleAndExecuteSetPermissionRoleMult(
+ { accessControl, owner, timelock, timelockManager },
+ data,
+ operatorDelay,
+ owner,
+ );
+
+ expect(
+ await accessControl[
+ 'hasFunctionPermission(bytes32,address,bytes4,address)'
+ ](
+ masterRole,
+ wAccessControlTester.address,
+ selector,
+ permissionAccount,
+ ),
+ ).eq(true);
+ });
+
+ it('when master schedules multi-selector batch - schedule and execute', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ timelock,
+ timelockManager,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ const selectorA = encodeFnSelector('withOnlyRole(bytes32,bool)');
+ const selectorB = encodeFnSelector('withOnlyContractAdmin()');
+ const masterRole = roles.common.greenlistedOperator;
+ const delay = 3600;
+ const account = regularAccounts[0].address;
+
+ await wAccessControlTester.setContractAdminRole(masterRole);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [masterRole],
+ [delay],
+ );
+
+ const data = encodeSetPermissionRoleMult(
+ accessControl,
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: selectorA,
+ account,
+ enabled: true,
+ },
+ {
+ functionSelector: selectorB,
+ account,
+ enabled: true,
+ },
+ ],
+ );
+
+ const [roleUsed] = await timelockManager.getTargetRole(
+ accessControl.address,
+ data,
+ owner.address,
+ );
+ expect(roleUsed).eq(masterRole);
+
+ await scheduleAndExecuteSetPermissionRoleMult(
+ { accessControl, owner, timelock, timelockManager },
+ data,
+ delay,
+ owner,
+ );
+
+ expect(
+ await accessControl[
+ 'hasFunctionPermission(bytes32,address,bytes4,address)'
+ ](masterRole, wAccessControlTester.address, selectorA, account),
+ ).eq(true);
+ expect(
+ await accessControl[
+ 'hasFunctionPermission(bytes32,address,bytes4,address)'
+ ](masterRole, wAccessControlTester.address, selectorB, account),
+ ).eq(true);
+ });
+
+ it('when operator schedules mixed-selector batch - should fail on execute', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ timelock,
+ timelockManager,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ const selectorA = encodeFnSelector('withOnlyRole(bytes32,bool)');
+ const selectorB = encodeFnSelector('withOnlyContractAdmin()');
+ const masterRole = roles.common.greenlistedOperator;
+ const operator = regularAccounts[0];
+ const delay = 3600;
+ const account = regularAccounts[1].address;
+
+ await wAccessControlTester.setContractAdminRole(masterRole);
+
+ await setGrantOperatorRoleTester(
+ { accessControl, owner },
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: selectorA,
+ operator: operator.address,
+ enabled: true,
+ },
+ ],
+ );
+
+ const operatorRoleKey = await accessControl.grantOperatorRoleKey(
+ masterRole,
+ wAccessControlTester.address,
+ selectorA,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [operatorRoleKey],
+ [delay],
+ );
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [masterRole],
+ [7200],
+ );
+
+ const data = encodeSetPermissionRoleMult(
+ accessControl,
+ wAccessControlTester.address,
+ [
+ {
+ functionSelector: selectorA,
+ account,
+ enabled: true,
+ },
+ {
+ functionSelector: selectorB,
+ account,
+ enabled: true,
+ },
+ ],
+ );
+
+ const [roleUsed] = await timelockManager.getTargetRole(
+ accessControl.address,
+ data,
+ operator.address,
+ );
+ expect(roleUsed).eq(operatorRoleKey);
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [accessControl.address],
+ [data],
+ {},
+ { from: operator },
+ );
+
+ await increase(delay);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ accessControl.address,
+ data,
+ operator.address,
+ {
+ from: owner,
+ revertMessage:
+ 'TimelockController: underlying transaction reverted',
+ },
+ );
+
+ expect(
+ await accessControl[
+ 'hasFunctionPermission(bytes32,address,bytes4,address)'
+ ](masterRole, wAccessControlTester.address, selectorA, account),
+ ).eq(false);
+ expect(
+ await accessControl[
+ 'hasFunctionPermission(bytes32,address,bytes4,address)'
+ ](masterRole, wAccessControlTester.address, selectorB, account),
+ ).eq(false);
+ });
+ });
+ });
+
+ describe('grantRole()', () => {
+ it('should fail: when sender does not have role admin role', async () => {
+ const { accessControl, regularAccounts, roles } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await grantRoleTester(
+ { accessControl, owner: regularAccounts[0] },
+ roles.common.blacklisted,
+ regularAccounts[1].address,
+ undefined,
+ { revertCustomError: acErrors.WMAC_HASNT_PERMISSION() },
+ );
+ });
+
+ it('when timelock delay is not 0 - schedule and execute the tx', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ timelock,
+ timelockManager,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [roles.common.blacklistedOperator],
+ [3600],
+ );
+
+ const data = accessControl.interface.encodeFunctionData(
+ 'grantRole(bytes32,address)',
+ [roles.common.blacklisted, regularAccounts[0].address],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [accessControl.address],
+ [data],
+ {},
+ { from: owner },
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ accessControl.address,
+ data,
+ owner.address,
+ { from: owner },
+ );
+ });
+
+ it('when role already granted - should not fail', async () => {
+ const { accessControl, owner, regularAccounts, roles } =
+ await loadFixture(defaultDeploy);
+
+ await grantRoleTester(
+ { accessControl, owner },
+ roles.common.blacklisted,
+ regularAccounts[0].address,
+ );
+
+ await grantRoleTester(
+ { accessControl, owner },
+ roles.common.blacklisted,
+ regularAccounts[0].address,
+ );
+ });
+
+ it('when delay is not NULL_DELAY but actual delay is NULL_DELAY - should set the delay', async () => {
+ const { accessControl, owner, regularAccounts, roles } =
+ await loadFixture(defaultDeploy);
+
+ await grantRoleTester(
+ { accessControl, owner },
+ roles.common.blacklisted,
+ regularAccounts[0].address,
+ 3600,
+ );
+ });
+
+ it('should fail: when delay is not NULL_DELAY but actual delay is also not null', async () => {
+ const { accessControl, owner, regularAccounts, roles } =
+ await loadFixture(defaultDeploy);
+
+ await grantRoleTester(
+ { accessControl, owner },
+ roles.common.blacklisted,
+ regularAccounts[0].address,
+ 3600,
+ );
+
+ await grantRoleTester(
+ { accessControl, owner },
+ roles.common.blacklisted,
+ regularAccounts[1].address,
+ 7200,
+ { revertCustomError: { customErrorName: 'DelayIsAlreadySet' } },
+ );
+ });
+ });
+
+ describe('revokeRole()', () => {
+ it('should fail: when sender does not have role admin role', async () => {
+ const { accessControl, regularAccounts, roles } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await revokeRoleTester(
+ { accessControl, owner: regularAccounts[0] },
+ roles.common.blacklisted,
+ regularAccounts[1].address,
+ { revertCustomError: acErrors.WMAC_HASNT_PERMISSION() },
+ );
+ });
+
+ it('when timelock delay is not 0 - schedule and execute the tx', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ timelock,
+ timelockManager,
+ } = await loadFixture(defaultDeploy);
+
+ await grantRoleTester(
+ { accessControl, owner },
+ roles.common.blacklisted,
+ regularAccounts[0].address,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [roles.common.blacklistedOperator],
+ [3600],
+ );
+
+ const data = accessControl.interface.encodeFunctionData('revokeRole', [
+ roles.common.blacklisted,
+ regularAccounts[0].address,
+ ]);
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [accessControl.address],
+ [data],
+ {},
+ { from: owner },
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ accessControl.address,
+ data,
+ owner.address,
+ { from: owner },
+ );
+ });
+
+ it('when role already revoked - should not fail', async () => {
+ const { accessControl, owner, regularAccounts, roles } =
+ await loadFixture(defaultDeploy);
+
+ await revokeRoleTester(
+ { accessControl, owner },
+ roles.common.blacklisted,
+ regularAccounts[0].address,
+ );
+ });
+
+ it('should fail: when revoking DEFAULT_ADMIN_ROLE from self', async () => {
+ const { accessControl, owner, roles } = await loadFixture(defaultDeploy);
+
+ await revokeRoleTester(
+ { accessControl, owner },
+ roles.common.defaultAdmin,
+ owner.address,
+ { revertCustomError: { customErrorName: 'CannotRevokeFromSelf' } },
+ );
+ });
+
+ it('when revoking role from self but its not DEFAULT_ADMIN_ROLE (should not fail)', async () => {
+ const { accessControl, owner, roles } = await loadFixture(defaultDeploy);
+
+ await revokeRoleTester(
+ { accessControl, owner },
+ roles.common.blacklistedOperator,
+ owner.address,
+ );
+ });
+
+ it('should fail: when revoking DEFAULT_ADMIN_ROLE from self and timelock delay is not 0', async () => {
+ // Locks in that _validateRevokeRole uses the resolved proposer (actualSender),
+ // not msg.sender. If it compared against the timelock address, self-revoke
+ // via timelock would incorrectly succeed.
+ const { accessControl, owner, roles, timelock, timelockManager } =
+ await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [roles.common.defaultAdmin],
+ [3600],
+ );
+
+ const data = accessControl.interface.encodeFunctionData('revokeRole', [
+ roles.common.defaultAdmin,
+ owner.address,
+ ]);
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [accessControl.address],
+ [data],
+ {},
+ { from: owner },
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ accessControl.address,
+ data,
+ owner.address,
+ {
+ from: owner,
+ revertMessage: 'TimelockController: underlying transaction reverted',
+ },
+ );
+
+ expect(
+ await accessControl.hasRole(roles.common.defaultAdmin, owner.address),
+ ).eq(true);
+ });
+
+ it('when revoking DEFAULT_ADMIN_ROLE from another admin via timelock - succeeds', async () => {
+ const {
+ accessControl,
+ owner,
+ regularAccounts,
+ roles,
+ timelock,
+ timelockManager,
+ } = await loadFixture(defaultDeploy);
+
+ const otherAdmin = regularAccounts[0];
+
+ await grantRoleTester(
+ { accessControl, owner },
+ roles.common.defaultAdmin,
+ otherAdmin.address,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [roles.common.defaultAdmin],
+ [3600],
+ );
+
+ const data = accessControl.interface.encodeFunctionData('revokeRole', [
+ roles.common.defaultAdmin,
+ otherAdmin.address,
+ ]);
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [accessControl.address],
+ [data],
+ {},
+ { from: owner },
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ accessControl.address,
+ data,
+ owner.address,
+ { from: owner },
+ );
+
+ expect(
+ await accessControl.hasRole(
+ roles.common.defaultAdmin,
+ otherAdmin.address,
+ ),
+ ).eq(false);
+ expect(
+ await accessControl.hasRole(roles.common.defaultAdmin, owner.address),
+ ).eq(true);
+ });
+ });
+
+ describe('setRoleDelayMult()', () => {
+ it('should set role delays', async () => {
+ const { timelockManager, timelock, owner, accessControl } =
+ await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [7200],
+ );
+ });
+
+ it('should fail: when params array is empty', async () => {
+ const { timelockManager, timelock, owner, accessControl } =
+ await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [],
+ [],
+ {
+ revertCustomError: {
+ customErrorName: 'EmptyArray',
+ },
+ },
+ );
+ });
+
+ it('should fail: when caller do not have DEFAULT_ADMIN_ROLE', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ regularAccounts,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(
+ undefined,
+ timelockManager,
+ ),
+ from: regularAccounts[0],
+ },
+ );
+ });
+ });
+
+ describe('setDefaultDelay()', () => {
+ it('should require 2 days timelock, even if role timelock is different', async () => {
+ const { timelockManager, timelock, owner, accessControl, roles } =
+ await loadFixture(defaultDeploy);
+
+ const defaultAdminRole = roles.common.defaultAdmin;
+ const newDelay = 7200;
+
+ await setRoleTimelocksTester(
+ { owner, accessControl, timelockManager, timelock },
+ [defaultAdminRole],
+ [3600],
+ );
+
+ const calldata = accessControl.interface.encodeFunctionData(
+ 'setDefaultDelay',
+ [newDelay],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [accessControl.address],
+ [calldata],
+ );
+ await increase(DELAY_FOR_SET_DEFAULT_DELAY);
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ accessControl.address,
+ calldata,
+ owner.address,
+ );
+ });
+
+ it('should fail: when called from a wallet without default admin role', async () => {
+ const {
+ accessControl,
+ owner,
+ timelock,
+ timelockManager,
+ regularAccounts,
+ } = await loadFixture(defaultDeploy);
+
+ await setDefaultDelayTest(
+ { owner, accessControl, timelock, timelockManager },
+ 7200,
+ {
+ revertCustomError: {
+ customErrorName: 'NoFunctionPermission',
+ },
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: when called from a function admin role', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ regularAccounts,
+ roles,
+ } = await loadFixture(defaultDeploy);
+
+ const defaultAdminRole = roles.common.defaultAdmin;
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: defaultAdminRole,
+ targetContract: accessControl.address,
+ functionSelector: setDefaultDelaySelector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ accessControl.address,
+ setDefaultDelaySelector,
+ [{ account: regularAccounts[0].address, enabled: true }],
+ );
+
+ await setDefaultDelayTest(
+ { timelockManager, timelock, owner, accessControl },
+ 7200,
+ {
+ revertCustomError: {
+ customErrorName: 'NoFunctionPermission',
+ },
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: when called directly without timelock', async () => {
+ const { timelockManager, timelock, owner, accessControl, roles } =
+ await loadFixture(defaultDeploy);
+
+ await setDefaultDelayTest(
+ { timelockManager, timelock, owner, accessControl },
+ 7200,
+ {
+ revertCustomError: {
+ contract: accessControl,
+ customErrorName: 'SenderIsNotTimelock',
+ },
+ },
+ );
+ });
+ });
+
+ describe('defaultDelay()', () => {
+ it('should return default timelock delay', async () => {
+ const { accessControl } = await loadFixture(defaultDeploy);
+
+ await accessControl.setDefaultDelayTest(3600);
+
+ expect(await accessControl.defaultDelay()).to.eq(3600);
+ });
+ });
+
+ describe('getRoleTimelockDelay()', () => {
+ it('should return default delay when role delay is not set', async () => {
+ const { accessControl } = await loadFixture(defaultDeploy);
+
+ await accessControl.setDefaultDelayTest(3600);
+
+ const [delay, isDefault] = await accessControl.getRoleTimelockDelay(
+ constants.HashZero,
+ 0,
+ );
+
+ expect(delay).to.eq(3600);
+ expect(isDefault).to.eq(true);
+ });
+
+ it('should return default delay when override delay is 0 and role delay is not set', async () => {
+ const { accessControl } = await loadFixture(defaultDeploy);
+
+ await accessControl.setDefaultDelayTest(3600);
+
+ const [delay, isDefault] = await accessControl.getRoleTimelockDelay(
+ constants.HashZero,
+ 0,
+ );
+
+ expect(delay).to.eq(3600);
+ expect(isDefault).to.eq(true);
+ });
+
+ it('should return override delay if its not zero if role delay is not set', async () => {
+ const { accessControl } = await loadFixture(defaultDeploy);
+
+ await accessControl.setDefaultDelayTest(3600);
+
+ const [delay, isDefault] = await accessControl.getRoleTimelockDelay(
+ constants.HashZero,
+ 1,
+ );
+
+ expect(delay).to.eq(1);
+ expect(isDefault).to.eq(false);
+ });
+
+ it('should return override delay if its not zero if role delay is set', async () => {
+ const { timelockManager, timelock, owner, accessControl } =
+ await loadFixture(defaultDeploy);
+
+ await accessControl.setDefaultDelayTest(3600);
+
+ await setRoleTimelocksAndExecute(
+ { timelockManager, timelock, owner, accessControl },
+ [{ role: constants.HashZero, delay: 1 }],
+ );
+
+ const [delay, isDefault] = await accessControl.getRoleTimelockDelay(
+ constants.HashZero,
+ 2,
+ );
+
+ expect(delay).to.eq(2);
+ expect(isDefault).to.eq(false);
+ });
+
+ it('should return 0 if override delay is NO_DELAY (uint256.max)', async () => {
+ const { accessControl } = await loadFixture(defaultDeploy);
+
+ await accessControl.setDefaultDelayTest(3600);
+
+ const [delay, isDefault] = await accessControl.getRoleTimelockDelay(
+ constants.HashZero,
+ NO_DELAY,
+ );
+
+ expect(delay).to.eq(0);
+ expect(isDefault).to.eq(false);
+ });
+
+ it('should return configured delay when role delay is set', async () => {
+ const { timelockManager, timelock, owner, accessControl } =
+ await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [7200],
+ );
+
+ const [delay, isDefault] = await accessControl.getRoleTimelockDelay(
+ constants.HashZero,
+ 0,
+ );
+
+ expect(delay).to.eq(7200);
+ expect(isDefault).to.eq(false);
+ });
+
+ it('should return zero delay when role delay is max uint256', async () => {
+ const { timelockManager, timelock, owner, accessControl } =
+ await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [NO_DELAY],
+ );
+
+ const [delay, isDefault] = await accessControl.getRoleTimelockDelay(
+ constants.HashZero,
+ 0,
+ );
+
+ expect(delay).to.eq(0);
+ expect(isDefault).to.eq(false);
+ });
+ });
+});
+
+describe('WithMidasAccessControl', function () {
+ it('deployment', async () => {
+ const { accessControl, wAccessControlTester } = await loadFixture(
+ defaultDeploy,
+ );
+ expect(await wAccessControlTester.accessControl()).eq(
+ accessControl.address,
+ );
+ });
+
+ it('onlyInitializing', async () => {
+ const { accessControl, owner } = await loadFixture(defaultDeploy);
+
+ const wac = await new WithMidasAccessControlTester__factory(owner).deploy();
+
+ await expect(
+ wac.initializeWithoutInitializer(accessControl.address),
+ ).revertedWith('Initializable: contract is not initializing');
+ });
+
+ describe('modifier onlyRole', () => {
+ it('should fail: when call from address without role', async () => {
+ const { wAccessControlTester, regularAccounts, roles } =
+ await loadFixture(defaultDeploy);
+
+ await expect(
+ wAccessControlTester
+ .connect(regularAccounts[1])
+ .withOnlyRole(roles.common.defaultAdmin, false),
+ ).revertedWithCustomError(
+ wAccessControlTester,
+ acErrors.WMAC_HASNT_PERMISSION().customErrorName,
+ );
+ });
+
+ it('when validateFunctionRole is false and caller has root admin role', async () => {
+ const { wAccessControlTester, roles } = await loadFixture(defaultDeploy);
+
+ await expect(
+ wAccessControlTester.withOnlyRole(roles.common.defaultAdmin, false),
+ ).not.reverted;
+ });
+
+ it('should fail: when role is timelocked and trying to call the function directly', async () => {
+ const {
+ accessControl,
+ wAccessControlTester,
+ owner,
+ regularAccounts,
+ roles,
+ timelockManager,
+ timelock,
+ } = await loadFixture(defaultDeploy);
+
+ const adminRole = roles.common.defaultAdmin;
+
+ const { wacFunctionKey, timelockManagerFunctionKey } =
+ await setupWithOnlyRolePermission(
+ accessControl,
+ owner,
+ wAccessControlTester,
+ timelockManager,
+ adminRole,
+ regularAccounts[0].address,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wacFunctionKey, timelockManagerFunctionKey],
+ [3600, 3600],
+ );
+
+ await expect(
+ wAccessControlTester
+ .connect(regularAccounts[0])
+ .withOnlyRole(adminRole, true),
+ ).revertedWithCustomError(accessControl, 'SenderIsNotTimelock');
+ });
+
+ it('should fail: when validateFunctionRole is false but trying to call with function admin', async () => {
+ const {
+ accessControl,
+ wAccessControlTester,
+ owner,
+ regularAccounts,
+ roles,
+ timelockManager,
+ } = await loadFixture(defaultDeploy);
+
+ const adminRole = roles.common.defaultAdmin;
+
+ await setupWithOnlyRolePermission(
+ accessControl,
+ owner,
+ wAccessControlTester,
+ timelockManager,
+ adminRole,
+ regularAccounts[0].address,
+ );
+
+ await expect(
+ wAccessControlTester
+ .connect(regularAccounts[0])
+ .withOnlyRole(adminRole, false),
+ ).revertedWithCustomError(
+ wAccessControlTester,
+ acErrors.WMAC_HASNT_PERMISSION().customErrorName,
+ );
+ });
+
+ it('when validateFunctionRole is true and trying to call with function admin and there is no timelock', async () => {
+ const {
+ accessControl,
+ wAccessControlTester,
+ owner,
+ regularAccounts,
+ roles,
+ timelockManager,
+ } = await loadFixture(defaultDeploy);
+
+ const adminRole = roles.common.defaultAdmin;
+
+ await setupWithOnlyRolePermission(
+ accessControl,
+ owner,
+ wAccessControlTester,
+ timelockManager,
+ adminRole,
+ regularAccounts[0].address,
+ );
+
+ await expect(
+ wAccessControlTester
+ .connect(regularAccounts[0])
+ .withOnlyRole(adminRole, true),
+ ).not.reverted;
+ });
+
+ it('when validateFunctionRole is true and trying to call with function admin and there is timelock on function role', async () => {
+ const {
+ accessControl,
+ wAccessControlTester,
+ owner,
+ regularAccounts,
+ roles,
+ timelockManager,
+ timelock,
+ } = await loadFixture(defaultDeploy);
+
+ const adminRole = roles.common.defaultAdmin;
+
+ const { wacFunctionKey, timelockManagerFunctionKey } =
+ await setupWithOnlyRolePermission(
+ accessControl,
+ owner,
+ wAccessControlTester,
+ timelockManager,
+ adminRole,
+ regularAccounts[0].address,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wacFunctionKey, timelockManagerFunctionKey],
+ [3600, 3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyRole',
+ [adminRole, true],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ {},
+ { from: regularAccounts[0] },
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ regularAccounts[0].address,
+ );
+ });
+
+ it('when validateFunctionRole is true and trying to call with function admin and there is timelock on root role', async () => {
+ const {
+ accessControl,
+ wAccessControlTester,
+ owner,
+ regularAccounts,
+ roles,
+ timelockManager,
+ timelock,
+ } = await loadFixture(defaultDeploy);
+
+ const adminRole = roles.common.defaultAdmin;
+
+ await grantRoleTester(
+ { accessControl, owner },
+ adminRole,
+ regularAccounts[0].address,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [adminRole],
+ [3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyRole',
+ [adminRole, true],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ {},
+ { from: regularAccounts[0] },
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ regularAccounts[0].address,
+ );
+ });
+
+ it('when validateFunctionRole is true, caller has both function admin and root roles, delay is on both - it should select role with lower delay', async () => {
+ const {
+ accessControl,
+ wAccessControlTester,
+ owner,
+ regularAccounts,
+ roles,
+ timelockManager,
+ timelock,
+ } = await loadFixture(defaultDeploy);
+
+ const adminRole = roles.common.defaultAdmin;
+
+ await grantRoleTester(
+ { accessControl, owner },
+ adminRole,
+ regularAccounts[0].address,
+ );
+
+ const { wacFunctionKey, timelockManagerFunctionKey } =
+ await setupWithOnlyRolePermission(
+ accessControl,
+ owner,
+ wAccessControlTester,
+ timelockManager,
+ adminRole,
+ regularAccounts[0].address,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [adminRole, wacFunctionKey, timelockManagerFunctionKey],
+ [7200, 3600, 3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyRole',
+ [adminRole, true],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ {},
+ { from: regularAccounts[0] },
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ regularAccounts[0].address,
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ {},
+ { from: regularAccounts[0] },
+ );
+
+ await increase(1);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ regularAccounts[0].address,
+ timelockManagerRevertOpts(timelockManager, 'TimelockOperationNotReady'),
+ );
+
+ await increase(3599);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ regularAccounts[0].address,
+ );
+ });
+ });
+
+ describe('modifier onlyContractAdmin', () => {
+ const setupContractAdminRole = async (
+ wAccessControlTester: WithMidasAccessControlTester,
+ adminRole: string,
+ ) => {
+ await wAccessControlTester.setContractAdminRole(adminRole);
+ };
+
+ it('should fail: when call from address without role', async () => {
+ const { wAccessControlTester, regularAccounts, roles } =
+ await loadFixture(defaultDeploy);
+
+ await setupContractAdminRole(
+ wAccessControlTester,
+ roles.common.defaultAdmin,
+ );
+
+ await expect(
+ wAccessControlTester
+ .connect(regularAccounts[1])
+ .withOnlyContractAdmin(),
+ ).revertedWithCustomError(
+ wAccessControlTester,
+ acErrors.WMAC_HASNT_PERMISSION().customErrorName,
+ );
+ });
+
+ it('when caller has root admin role', async () => {
+ const { wAccessControlTester, roles } = await loadFixture(defaultDeploy);
+
+ await setupContractAdminRole(
+ wAccessControlTester,
+ roles.common.defaultAdmin,
+ );
+
+ await expect(wAccessControlTester.withOnlyContractAdmin()).not.reverted;
+ });
+
+ it('should fail: when role is timelocked and trying to call the function directly', async () => {
+ const {
+ accessControl,
+ wAccessControlTester,
+ owner,
+ regularAccounts,
+ roles,
+ timelockManager,
+ timelock,
+ } = await loadFixture(defaultDeploy);
+
+ const adminRole = roles.common.defaultAdmin;
+ await setupContractAdminRole(wAccessControlTester, adminRole);
+
+ const { wacFunctionKey, timelockManagerFunctionKey } =
+ await setupWithOnlyContractAdminPermission(
+ accessControl,
+ owner,
+ wAccessControlTester,
+ timelockManager,
+ adminRole,
+ regularAccounts[0].address,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wacFunctionKey, timelockManagerFunctionKey],
+ [3600, 3600],
+ );
+
+ await expect(
+ wAccessControlTester
+ .connect(regularAccounts[0])
+ .withOnlyContractAdmin(),
+ ).revertedWithCustomError(accessControl, 'SenderIsNotTimelock');
+ });
+
+ it('when trying to call with function admin and there is no timelock', async () => {
+ const {
+ accessControl,
+ wAccessControlTester,
+ owner,
+ regularAccounts,
+ roles,
+ timelockManager,
+ } = await loadFixture(defaultDeploy);
+
+ const adminRole = roles.common.defaultAdmin;
+ await setupContractAdminRole(wAccessControlTester, adminRole);
+
+ await setupWithOnlyContractAdminPermission(
+ accessControl,
+ owner,
+ wAccessControlTester,
+ timelockManager,
+ adminRole,
+ regularAccounts[0].address,
+ );
+
+ await expect(
+ wAccessControlTester
+ .connect(regularAccounts[0])
+ .withOnlyContractAdmin(),
+ ).not.reverted;
+ });
+
+ it('when trying to call with function admin and there is timelock on function role', async () => {
+ const {
+ accessControl,
+ wAccessControlTester,
+ owner,
+ regularAccounts,
+ roles,
+ timelockManager,
+ timelock,
+ } = await loadFixture(defaultDeploy);
+
+ const adminRole = roles.common.defaultAdmin;
+ await setupContractAdminRole(wAccessControlTester, adminRole);
+
+ const { wacFunctionKey, timelockManagerFunctionKey } =
+ await setupWithOnlyContractAdminPermission(
+ accessControl,
+ owner,
+ wAccessControlTester,
+ timelockManager,
+ adminRole,
+ regularAccounts[0].address,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wacFunctionKey, timelockManagerFunctionKey],
+ [3600, 3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ {},
+ { from: regularAccounts[0] },
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ regularAccounts[0].address,
+ );
+ });
+
+ it('when trying to call with function admin and there is timelock on root role', async () => {
+ const {
+ accessControl,
+ wAccessControlTester,
+ owner,
+ regularAccounts,
+ roles,
+ timelockManager,
+ timelock,
+ } = await loadFixture(defaultDeploy);
+
+ const adminRole = roles.common.defaultAdmin;
+ await setupContractAdminRole(wAccessControlTester, adminRole);
+
+ await grantRoleTester(
+ { accessControl, owner },
+ adminRole,
+ regularAccounts[0].address,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [adminRole],
+ [3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ {},
+ { from: regularAccounts[0] },
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ regularAccounts[0].address,
+ );
+ });
+
+ it('when caller has both function admin and root roles, delay is on both - it should select role with lower delay', async () => {
+ const {
+ accessControl,
+ wAccessControlTester,
+ owner,
+ regularAccounts,
+ roles,
+ timelockManager,
+ timelock,
+ } = await loadFixture(defaultDeploy);
+
+ const adminRole = roles.common.defaultAdmin;
+ await setupContractAdminRole(wAccessControlTester, adminRole);
+
+ await grantRoleTester(
+ { accessControl, owner },
+ adminRole,
+ regularAccounts[0].address,
+ );
+
+ const { wacFunctionKey, timelockManagerFunctionKey } =
+ await setupWithOnlyContractAdminPermission(
+ accessControl,
+ owner,
+ wAccessControlTester,
+ timelockManager,
+ adminRole,
+ regularAccounts[0].address,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [adminRole, wacFunctionKey, timelockManagerFunctionKey],
+ [7200, 3600, 3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ {},
+ { from: regularAccounts[0] },
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ regularAccounts[0].address,
+ );
+ });
+ });
+
+ describe('modifier onlyRoleNoTimelock', () => {
+ it('should fail: when trying to schedule through timelock', async () => {
+ const {
+ wAccessControlTester,
+ owner,
+ roles,
+ timelockManager,
+ timelock,
+ accessControl,
+ } = await loadFixture(defaultDeploy);
+
+ const adminRole = roles.common.defaultAdmin;
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyRoleNoTimelock',
+ [adminRole, true],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ {},
+ timelockManagerRevertOpts(timelockManager, 'InvalidPreflightError'),
+ );
+ });
+
+ it('when validateFunctionRole is true and user has only root admin role', async () => {
+ const { wAccessControlTester, roles } = await loadFixture(defaultDeploy);
+
+ await expect(
+ wAccessControlTester.withOnlyRoleNoTimelock(
+ roles.common.defaultAdmin,
+ true,
+ ),
+ ).not.reverted;
+ });
+
+ it('when validateFunctionRole is false and user has only root admin role', async () => {
+ const { wAccessControlTester, roles } = await loadFixture(defaultDeploy);
+
+ await expect(
+ wAccessControlTester.withOnlyRoleNoTimelock(
+ roles.common.defaultAdmin,
+ false,
+ ),
+ ).not.reverted;
+ });
+
+ it('when validateFunctionRole is true and user has only function admin role', async () => {
+ const {
+ accessControl,
+ wAccessControlTester,
+ owner,
+ regularAccounts,
+ roles,
+ timelockManager,
+ } = await loadFixture(defaultDeploy);
+
+ const adminRole = roles.common.defaultAdmin;
+
+ await setupFunctionPermissionRole(
+ accessControl,
+ owner,
+ wAccessControlTester,
+ timelockManager,
+ adminRole,
+ withOnlyRoleNoTimelockSelector,
+ regularAccounts[0].address,
+ );
+
+ await expect(
+ wAccessControlTester
+ .connect(regularAccounts[0])
+ .withOnlyRoleNoTimelock(adminRole, true),
+ ).not.reverted;
+ });
+
+ it('should fail: when validateFunctionRole is false and user has only function admin role', async () => {
+ const {
+ accessControl,
+ wAccessControlTester,
+ owner,
+ regularAccounts,
+ roles,
+ timelockManager,
+ } = await loadFixture(defaultDeploy);
+
+ const adminRole = roles.common.defaultAdmin;
+
+ await setupFunctionPermissionRole(
+ accessControl,
+ owner,
+ wAccessControlTester,
+ timelockManager,
+ adminRole,
+ withOnlyRoleNoTimelockSelector,
+ regularAccounts[0].address,
+ );
+
+ await expect(
+ wAccessControlTester
+ .connect(regularAccounts[0])
+ .withOnlyRoleNoTimelock(adminRole, false),
+ ).revertedWithCustomError(
+ wAccessControlTester,
+ acErrors.WMAC_HASNT_PERMISSION().customErrorName,
+ );
+ });
+
+ it('when user has both roles', async () => {
+ const {
+ accessControl,
+ wAccessControlTester,
+ owner,
+ regularAccounts,
+ roles,
+ timelockManager,
+ } = await loadFixture(defaultDeploy);
+
+ const adminRole = roles.common.defaultAdmin;
+
+ await grantRoleTester(
+ { accessControl, owner },
+ adminRole,
+ regularAccounts[0].address,
+ );
+
+ await setupFunctionPermissionRole(
+ accessControl,
+ owner,
+ wAccessControlTester,
+ timelockManager,
+ adminRole,
+ withOnlyRoleNoTimelockSelector,
+ regularAccounts[0].address,
+ );
+
+ await expect(
+ wAccessControlTester
+ .connect(regularAccounts[0])
+ .withOnlyRoleNoTimelock(adminRole, true),
+ ).not.reverted;
+ });
});
});
diff --git a/test/unit/MidasInitializable.test.ts b/test/unit/MidasInitializable.test.ts
new file mode 100644
index 00000000..248b8dab
--- /dev/null
+++ b/test/unit/MidasInitializable.test.ts
@@ -0,0 +1,80 @@
+import { expect } from 'chai';
+import { ethers } from 'hardhat';
+
+import { MidasInitializableTester } from '../../typechain-types';
+import { deployProxyContract } from '../common/deploy.helpers';
+
+const PROXY_ADMIN_SLOT =
+ '0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103';
+
+const INITIALIZED_SLOT = ethers.utils.hexZeroPad('0x00', 32);
+
+const setProxyAdmin = (address: string, admin: string) =>
+ ethers.provider.send('hardhat_setStorageAt', [
+ address,
+ PROXY_ADMIN_SLOT,
+ ethers.utils.hexZeroPad(admin, 32),
+ ]);
+
+const setInitializedVersion = (address: string, version: number) =>
+ ethers.provider.send('hardhat_setStorageAt', [
+ address,
+ INITIALIZED_SLOT,
+ ethers.utils.hexZeroPad(ethers.utils.hexlify(version), 32),
+ ]);
+
+const deployTester = () =>
+ deployProxyContract(
+ 'MidasInitializableTester',
+ [],
+ 'initialize',
+ );
+
+describe('MidasInitializable', function () {
+ it('fresh deploy: initialize runs initializeV2 while proxy admin is zero', async () => {
+ const tester = await deployTester();
+
+ expect(await tester.initializeCallsCount()).eq(1);
+ expect(await tester.reinitCallsCount()).eq(1);
+ });
+
+ describe('initializeV2()', () => {
+ it('should fail: when already reinitialized, even from the proxy admin', async () => {
+ const [, admin, stranger] = await ethers.getSigners();
+ const tester = await deployTester();
+
+ await setProxyAdmin(tester.address, admin.address);
+
+ await expect(tester.connect(admin).initializeV2()).revertedWith(
+ 'Initializable: contract is already initialized',
+ );
+ await expect(tester.connect(stranger).initializeV2()).revertedWith(
+ 'Initializable: contract is already initialized',
+ );
+ });
+
+ it('should fail: when initialized and caller is not the proxy admin', async () => {
+ const [, admin, stranger] = await ethers.getSigners();
+ const tester = await deployTester();
+
+ await setProxyAdmin(tester.address, admin.address);
+ await setInitializedVersion(tester.address, 1);
+
+ await expect(
+ tester.connect(stranger).initializeV2(),
+ ).revertedWithCustomError(tester, 'SenderNotProxyAdmin');
+ });
+
+ it('when initialized and caller is the proxy admin', async () => {
+ const [, admin] = await ethers.getSigners();
+ const tester = await deployTester();
+
+ await setProxyAdmin(tester.address, admin.address);
+ await setInitializedVersion(tester.address, 1);
+
+ await tester.connect(admin).initializeV2();
+
+ expect(await tester.reinitCallsCount()).eq(2);
+ });
+ });
+});
diff --git a/test/unit/MidasPauseManager.test.ts b/test/unit/MidasPauseManager.test.ts
new file mode 100644
index 00000000..1e1c70a7
--- /dev/null
+++ b/test/unit/MidasPauseManager.test.ts
@@ -0,0 +1,1881 @@
+import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
+import { increase } from '@nomicfoundation/hardhat-network-helpers/dist/src/helpers/time';
+import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
+import { expect } from 'chai';
+import { BigNumberish, Contract } from 'ethers';
+
+import { encodeFnSelector } from '../../helpers/utils';
+import {
+ MidasAccessControl,
+ MidasAccessControlTimelockController,
+ MidasPauseManager,
+} from '../../typechain-types';
+import {
+ acErrors,
+ NO_DELAY,
+ NULL_DELAY,
+ setPermissionRoleTester,
+ setRoleTimelocksTester,
+ setupGrantOperatorRole,
+} from '../common/ac.helpers';
+import {
+ adminPauseContractTest,
+ adminUnpauseContractTest,
+ pauseGlobalTest,
+ pauseVault,
+ pauseVaultFn,
+ unpauseGlobalTest,
+ unpauseVault,
+ unpauseVaultFn,
+} from '../common/common.helpers';
+import { defaultDeploy } from '../common/fixtures';
+import {
+ executeTimelockOperationTester,
+ bulkScheduleTimelockOperationTester,
+} from '../common/timelock-manager.helpers';
+
+const DEFAULT_UNPAUSE_DELAY = 86400;
+const DELAY_FOR_SET_DELAY = 2 * 24 * 3600;
+const ROLE_TIMELOCK_DELAY = 3600;
+const CUSTOM_DELAY = 3600;
+
+type TimelockCtx = {
+ pauseManager: MidasPauseManager;
+ timelockManager: Awaited>['timelockManager'];
+ timelock: MidasAccessControlTimelockController;
+ accessControl: MidasAccessControl;
+ owner: SignerWithAddress;
+};
+
+const timelockParams = (ctx: TimelockCtx) => ({
+ timelockManager: ctx.timelockManager,
+ timelock: ctx.timelock,
+ accessControl: ctx.accessControl,
+ owner: ctx.owner,
+});
+
+const scheduleAndExecute = async (
+ ctx: TimelockCtx,
+ calldata: string,
+ delay: number,
+ from: SignerWithAddress = ctx.owner,
+ executeOpt?: Parameters[4],
+) => {
+ const timelockCtx = timelockParams(ctx);
+
+ await bulkScheduleTimelockOperationTester(
+ timelockCtx,
+ [ctx.pauseManager.address],
+ [calldata],
+ {},
+ { from },
+ );
+ await increase(delay);
+ await executeTimelockOperationTester(
+ timelockCtx,
+ ctx.pauseManager.address,
+ calldata,
+ from.address,
+ executeOpt,
+ );
+};
+
+const unpauseContractsViaTimelock = async (
+ ctx: TimelockCtx,
+ addresses: string[],
+ from: SignerWithAddress = ctx.owner,
+) => {
+ const calldata = ctx.pauseManager.interface.encodeFunctionData(
+ 'bulkUnpauseContract',
+ [addresses],
+ );
+ await scheduleAndExecute(ctx, calldata, DEFAULT_UNPAUSE_DELAY, from);
+};
+
+const unpauseGlobalViaTimelock = async (
+ ctx: TimelockCtx,
+ from: SignerWithAddress = ctx.owner,
+) => {
+ const calldata =
+ ctx.pauseManager.interface.encodeFunctionData('globalUnpause');
+ await scheduleAndExecute(ctx, calldata, DEFAULT_UNPAUSE_DELAY, from);
+};
+
+const unpauseContractFnsViaTimelock = async (
+ ctx: TimelockCtx,
+ addresses: string[],
+ selectors: string[],
+ from: SignerWithAddress = ctx.owner,
+) => {
+ const calldata = ctx.pauseManager.interface.encodeFunctionData(
+ 'bulkUnpauseContractFn',
+ [addresses, selectors],
+ );
+ await scheduleAndExecute(ctx, calldata, DEFAULT_UNPAUSE_DELAY, from);
+};
+
+const contractAdminUnpauseViaTimelock = async (
+ ctx: TimelockCtx,
+ contract: Contract,
+ from: SignerWithAddress = ctx.owner,
+) => {
+ const calldata = ctx.pauseManager.interface.encodeFunctionData(
+ 'contractAdminUnpause',
+ [contract.address],
+ );
+ await scheduleAndExecute(ctx, calldata, DEFAULT_UNPAUSE_DELAY, from);
+};
+
+const setPauseDelayViaTimelock = async (
+ ctx: TimelockCtx,
+ newDelay: number,
+ from: SignerWithAddress = ctx.owner,
+) => {
+ const calldata = ctx.pauseManager.interface.encodeFunctionData(
+ 'setPauseDelay',
+ [newDelay],
+ );
+ await scheduleAndExecute(ctx, calldata, DELAY_FOR_SET_DELAY, from);
+};
+
+const setUnpauseDelayViaTimelock = async (
+ ctx: TimelockCtx,
+ newDelay: BigNumberish,
+ from: SignerWithAddress = ctx.owner,
+) => {
+ const calldata = ctx.pauseManager.interface.encodeFunctionData(
+ 'setUnpauseDelay',
+ [newDelay],
+ );
+ await scheduleAndExecute(ctx, calldata, DELAY_FOR_SET_DELAY, from);
+};
+
+const grantContractPauser = async (
+ accessControl: MidasAccessControl,
+ pausableTester: Contract,
+ account: SignerWithAddress,
+) => {
+ const role = await pausableTester.contractAdminRole();
+ await accessControl['grantRole(bytes32,address)'](role, account.address);
+};
+
+const toTimelockCtx = (
+ fixture: Awaited>,
+): TimelockCtx => ({
+ pauseManager: fixture.pauseManager,
+ timelockManager: fixture.timelockManager,
+ timelock: fixture.timelock,
+ accessControl: fixture.accessControl,
+ owner: fixture.owner,
+});
+
+const scheduleGlobalPause = async (
+ ctx: TimelockCtx,
+ opt?: Parameters[4],
+) => {
+ const calldata = ctx.pauseManager.interface.encodeFunctionData('globalPause');
+ await bulkScheduleTimelockOperationTester(
+ timelockParams(ctx),
+ [ctx.pauseManager.address],
+ [calldata],
+ {},
+ opt,
+ );
+};
+
+const scheduleGlobalUnpause = async (
+ ctx: TimelockCtx,
+ opt?: Parameters[4],
+) => {
+ const calldata =
+ ctx.pauseManager.interface.encodeFunctionData('globalUnpause');
+ await bulkScheduleTimelockOperationTester(
+ timelockParams(ctx),
+ [ctx.pauseManager.address],
+ [calldata],
+ {},
+ opt,
+ );
+};
+
+const executeGlobalPause = async (
+ ctx: TimelockCtx,
+ opt?: Parameters[4],
+) => {
+ const calldata = ctx.pauseManager.interface.encodeFunctionData('globalPause');
+ await executeTimelockOperationTester(
+ timelockParams(ctx),
+ ctx.pauseManager.address,
+ calldata,
+ ctx.owner.address,
+ opt,
+ );
+};
+
+const executeGlobalUnpause = async (
+ ctx: TimelockCtx,
+ opt?: Parameters[4],
+) => {
+ const calldata =
+ ctx.pauseManager.interface.encodeFunctionData('globalUnpause');
+ await executeTimelockOperationTester(
+ timelockParams(ctx),
+ ctx.pauseManager.address,
+ calldata,
+ ctx.owner.address,
+ opt,
+ );
+};
+
+const setPauseAdminRoleTimelock = async (
+ fixture: Awaited>,
+ delay: number = ROLE_TIMELOCK_DELAY,
+) => {
+ const pauseAdminRole = await fixture.pauseManager.pauseAdminRole();
+ await setRoleTimelocksTester(
+ {
+ timelockManager: fixture.timelockManager,
+ timelock: fixture.timelock,
+ owner: fixture.owner,
+ accessControl: fixture.accessControl,
+ },
+ [pauseAdminRole],
+ [delay],
+ );
+};
+
+const setContractPauserRoleTimelock = async (
+ fixture: Awaited>,
+ delay: number = ROLE_TIMELOCK_DELAY,
+) => {
+ const contractPauserRole = await fixture.pausableTester.contractAdminRole();
+ await setRoleTimelocksTester(
+ {
+ timelockManager: fixture.timelockManager,
+ timelock: fixture.timelock,
+ owner: fixture.owner,
+ accessControl: fixture.accessControl,
+ },
+ [contractPauserRole],
+ [delay],
+ );
+};
+
+const BULK_PAUSE_CONTRACT_SEL = encodeFnSelector(
+ 'bulkPauseContract(address[])',
+);
+const BULK_UNPAUSE_CONTRACT_SEL = encodeFnSelector(
+ 'bulkUnpauseContract(address[])',
+);
+const BULK_PAUSE_CONTRACT_FN_SEL = encodeFnSelector(
+ 'bulkPauseContractFn(address[],bytes4[])',
+);
+const BULK_UNPAUSE_CONTRACT_FN_SEL = encodeFnSelector(
+ 'bulkUnpauseContractFn(address[],bytes4[])',
+);
+const CONTRACT_ADMIN_PAUSE_SEL = encodeFnSelector(
+ 'contractAdminPause(address)',
+);
+const CONTRACT_ADMIN_UNPAUSE_SEL = encodeFnSelector(
+ 'contractAdminUnpause(address)',
+);
+const DEPOSIT_REQUEST_SEL = encodeFnSelector(
+ 'depositRequest(address,uint256,bytes32)',
+);
+
+const noTimelockDelayRevert = (
+ fixture: Awaited>,
+) => ({
+ revertCustomError: {
+ contract: fixture.timelockManager,
+ customErrorName: 'NoTimelockDelayForRole',
+ },
+});
+
+const pauseAdminFunctionNotReady = async (
+ fixture: Awaited>,
+ selector: string,
+ owner?: SignerWithAddress,
+) => ({
+ revertCustomError: {
+ contract: fixture.accessControl,
+ customErrorName: 'SenderIsNotTimelock',
+ args: [
+ await fixture.pauseManager.pauseAdminRole(),
+ selector,
+ owner?.address ?? fixture.owner.address,
+ ],
+ },
+});
+
+const contractPauserFunctionNotReady = async (
+ fixture: Awaited>,
+ selector: string,
+ owner?: SignerWithAddress,
+) => ({
+ revertCustomError: {
+ contract: fixture.accessControl,
+ customErrorName: 'SenderIsNotTimelock',
+ args: [
+ await fixture.pausableTester.contractAdminRole(),
+ selector,
+ owner?.address ?? fixture.owner.address,
+ ],
+ },
+});
+
+const scheduleBulkPauseContract = async (
+ ctx: TimelockCtx,
+ addresses: string[],
+ opt?: Parameters[4],
+) => {
+ const calldata = ctx.pauseManager.interface.encodeFunctionData(
+ 'bulkPauseContract',
+ [addresses],
+ );
+ await bulkScheduleTimelockOperationTester(
+ timelockParams(ctx),
+ [ctx.pauseManager.address],
+ [calldata],
+ {},
+ opt,
+ );
+};
+
+const executeBulkPauseContract = async (
+ ctx: TimelockCtx,
+ addresses: string[],
+ opt?: Parameters[4],
+) => {
+ const calldata = ctx.pauseManager.interface.encodeFunctionData(
+ 'bulkPauseContract',
+ [addresses],
+ );
+ await executeTimelockOperationTester(
+ timelockParams(ctx),
+ ctx.pauseManager.address,
+ calldata,
+ ctx.owner.address,
+ opt,
+ );
+};
+
+const scheduleBulkUnpauseContract = async (
+ ctx: TimelockCtx,
+ addresses: string[],
+ opt?: Parameters[4],
+) => {
+ const calldata = ctx.pauseManager.interface.encodeFunctionData(
+ 'bulkUnpauseContract',
+ [addresses],
+ );
+ await bulkScheduleTimelockOperationTester(
+ timelockParams(ctx),
+ [ctx.pauseManager.address],
+ [calldata],
+ {},
+ opt,
+ );
+};
+
+const executeBulkUnpauseContract = async (
+ ctx: TimelockCtx,
+ addresses: string[],
+ opt?: Parameters[4],
+) => {
+ const calldata = ctx.pauseManager.interface.encodeFunctionData(
+ 'bulkUnpauseContract',
+ [addresses],
+ );
+ await executeTimelockOperationTester(
+ timelockParams(ctx),
+ ctx.pauseManager.address,
+ calldata,
+ ctx.owner.address,
+ opt,
+ );
+};
+
+const scheduleBulkPauseContractFn = async (
+ ctx: TimelockCtx,
+ addresses: string[],
+ selectors: string[],
+ opt?: Parameters[4],
+) => {
+ const calldata = ctx.pauseManager.interface.encodeFunctionData(
+ 'bulkPauseContractFn',
+ [addresses, selectors],
+ );
+ await bulkScheduleTimelockOperationTester(
+ timelockParams(ctx),
+ [ctx.pauseManager.address],
+ [calldata],
+ {},
+ opt,
+ );
+};
+
+const executeBulkPauseContractFn = async (
+ ctx: TimelockCtx,
+ addresses: string[],
+ selectors: string[],
+ opt?: Parameters[4],
+) => {
+ const calldata = ctx.pauseManager.interface.encodeFunctionData(
+ 'bulkPauseContractFn',
+ [addresses, selectors],
+ );
+ await executeTimelockOperationTester(
+ timelockParams(ctx),
+ ctx.pauseManager.address,
+ calldata,
+ ctx.owner.address,
+ opt,
+ );
+};
+
+const scheduleBulkUnpauseContractFn = async (
+ ctx: TimelockCtx,
+ addresses: string[],
+ selectors: string[],
+ opt?: Parameters[4],
+) => {
+ const calldata = ctx.pauseManager.interface.encodeFunctionData(
+ 'bulkUnpauseContractFn',
+ [addresses, selectors],
+ );
+ await bulkScheduleTimelockOperationTester(
+ timelockParams(ctx),
+ [ctx.pauseManager.address],
+ [calldata],
+ {},
+ opt,
+ );
+};
+
+const executeBulkUnpauseContractFn = async (
+ ctx: TimelockCtx,
+ addresses: string[],
+ selectors: string[],
+ opt?: Parameters[4],
+) => {
+ const calldata = ctx.pauseManager.interface.encodeFunctionData(
+ 'bulkUnpauseContractFn',
+ [addresses, selectors],
+ );
+ await executeTimelockOperationTester(
+ timelockParams(ctx),
+ ctx.pauseManager.address,
+ calldata,
+ ctx.owner.address,
+ opt,
+ );
+};
+
+const scheduleContractAdminPause = async (
+ ctx: TimelockCtx,
+ contractAddr: string,
+ opt?: Parameters[4],
+) => {
+ const calldata = ctx.pauseManager.interface.encodeFunctionData(
+ 'contractAdminPause',
+ [contractAddr],
+ );
+ await bulkScheduleTimelockOperationTester(
+ timelockParams(ctx),
+ [ctx.pauseManager.address],
+ [calldata],
+ {},
+ opt,
+ );
+};
+
+const executeContractAdminPause = async (
+ ctx: TimelockCtx,
+ contractAddr: string,
+ opt?: Parameters[4],
+) => {
+ const calldata = ctx.pauseManager.interface.encodeFunctionData(
+ 'contractAdminPause',
+ [contractAddr],
+ );
+ await executeTimelockOperationTester(
+ timelockParams(ctx),
+ ctx.pauseManager.address,
+ calldata,
+ ctx.owner.address,
+ opt,
+ );
+};
+
+const scheduleContractAdminUnpause = async (
+ ctx: TimelockCtx,
+ contractAddr: string,
+ opt?: Parameters[4],
+) => {
+ const calldata = ctx.pauseManager.interface.encodeFunctionData(
+ 'contractAdminUnpause',
+ [contractAddr],
+ );
+ await bulkScheduleTimelockOperationTester(
+ timelockParams(ctx),
+ [ctx.pauseManager.address],
+ [calldata],
+ {},
+ opt,
+ );
+};
+
+const executeContractAdminUnpause = async (
+ ctx: TimelockCtx,
+ contractAddr: string,
+ opt?: Parameters[4],
+) => {
+ const calldata = ctx.pauseManager.interface.encodeFunctionData(
+ 'contractAdminUnpause',
+ [contractAddr],
+ );
+ await executeTimelockOperationTester(
+ timelockParams(ctx),
+ ctx.pauseManager.address,
+ calldata,
+ ctx.owner.address,
+ opt,
+ );
+};
+
+describe('MidasPauseManager', () => {
+ describe('globalPause()', () => {
+ it('should fail: when caller doesnt have pause admin role', async () => {
+ const { pauseManager, owner, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await pauseGlobalTest(
+ { pauseManager, owner },
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ },
+ );
+ });
+
+ it('call from pause admin when already paused', async () => {
+ const { pauseManager, owner } = await loadFixture(defaultDeploy);
+
+ await pauseGlobalTest({ pauseManager, owner });
+ await pauseGlobalTest({ pauseManager, owner });
+ });
+
+ it('call from pause admin', async () => {
+ const { pauseManager, owner } = await loadFixture(defaultDeploy);
+
+ await pauseGlobalTest({ pauseManager, owner });
+ });
+
+ it('when pause admin has function scoped timelock', async () => {
+ const {
+ accessControl,
+ pauseManager,
+ owner,
+ regularAccounts,
+ timelockManager,
+ timelock,
+ } = await loadFixture(defaultDeploy);
+
+ const pauseAdminRole = await pauseManager.pauseAdminRole();
+ const globalPauseSel = encodeFnSelector('globalPause()');
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: pauseAdminRole,
+ targetContract: pauseManager.address,
+ functionSelector: globalPauseSel,
+ grantOperator: owner,
+ });
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ pauseAdminRole,
+ pauseManager.address,
+ globalPauseSel,
+ [{ account: regularAccounts[0].address, enabled: true }],
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [pauseAdminRole],
+ [3600],
+ );
+
+ await pauseGlobalTest(
+ { pauseManager, owner },
+ { from: regularAccounts[0] },
+ );
+ });
+
+ it('should fail: when pause delay is no_delay and trying to schedule through timelock', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await scheduleGlobalPause(ctx, {
+ revertCustomError: {
+ contract: fixture.timelockManager,
+ customErrorName: 'NoTimelockDelayForRole',
+ },
+ });
+ });
+
+ it('when pause delay is changed to custom delay, globalPause can be scheduled on timelock', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setPauseDelayViaTimelock(ctx, CUSTOM_DELAY);
+ await scheduleGlobalPause(ctx);
+ await increase(CUSTOM_DELAY);
+ await executeGlobalPause(ctx);
+ });
+
+ it('should fail: when pause delay is custom delay and trying to call directly before delay', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setPauseDelayViaTimelock(ctx, CUSTOM_DELAY);
+
+ await pauseGlobalTest(fixture, {
+ revertCustomError: {
+ contract: fixture.accessControl,
+ customErrorName: 'SenderIsNotTimelock',
+ args: [
+ await fixture.pauseManager.pauseAdminRole(),
+ encodeFnSelector('globalPause()'),
+ fixture.owner.address,
+ ],
+ },
+ });
+ });
+
+ it('when pause delay is changed to null_delay, globalPause uses role timelock delay', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setPauseDelayViaTimelock(ctx, NULL_DELAY);
+ await setPauseAdminRoleTimelock(fixture);
+ await scheduleGlobalPause(ctx);
+ await increase(ROLE_TIMELOCK_DELAY);
+ await executeGlobalPause(ctx);
+ });
+ });
+
+ describe('globalUnpause()', () => {
+ it('should fail: when caller doesnt have pause admin role', async () => {
+ const { pauseManager, regularAccounts, ...timelockFixture } =
+ await loadFixture(defaultDeploy);
+
+ const calldata =
+ pauseManager.interface.encodeFunctionData('globalUnpause');
+
+ await bulkScheduleTimelockOperationTester(
+ timelockFixture,
+ [pauseManager.address],
+ [calldata],
+ {},
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ },
+ );
+ });
+
+ it('call from pause admin when not paused', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ await unpauseGlobalViaTimelock(fixture);
+ });
+
+ it('when unpause delay is set, unpause can be scheduled on timelock', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+
+ await pauseGlobalTest(fixture);
+ await unpauseGlobalViaTimelock(fixture);
+ });
+
+ it('should fail: when trying to call directly before delay', async () => {
+ const { pauseManager, owner, accessControl } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await pauseGlobalTest({ pauseManager, owner });
+
+ await unpauseGlobalTest(
+ { pauseManager, owner },
+ {
+ revertCustomError: {
+ contract: accessControl,
+ customErrorName: 'SenderIsNotTimelock',
+ args: [
+ await pauseManager.pauseAdminRole(),
+ encodeFnSelector('globalUnpause()'),
+ owner.address,
+ ],
+ },
+ },
+ );
+ });
+
+ it('when unpause delay is changed to no_delay, globalUnpause can be called directly', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setUnpauseDelayViaTimelock(ctx, NO_DELAY);
+ await pauseGlobalTest(fixture);
+ await unpauseGlobalTest(fixture);
+ });
+
+ it('should fail: when unpause delay is no_delay and trying to schedule through timelock', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setUnpauseDelayViaTimelock(ctx, NO_DELAY);
+ await pauseGlobalTest(fixture);
+
+ await scheduleGlobalUnpause(ctx, {
+ revertCustomError: {
+ contract: fixture.timelockManager,
+ customErrorName: 'NoTimelockDelayForRole',
+ },
+ });
+ });
+
+ it('when unpause delay is changed to null_delay, globalUnpause uses role timelock delay', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setUnpauseDelayViaTimelock(ctx, NULL_DELAY);
+ await setPauseAdminRoleTimelock(fixture);
+ await pauseGlobalTest(fixture);
+ await scheduleGlobalUnpause(ctx);
+ await increase(ROLE_TIMELOCK_DELAY);
+ await executeGlobalUnpause(ctx);
+ });
+
+ it('when unpause delay is changed to custom delay, globalUnpause can be scheduled on timelock', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setUnpauseDelayViaTimelock(ctx, CUSTOM_DELAY);
+ await pauseGlobalTest(fixture);
+ await scheduleGlobalUnpause(ctx);
+ await increase(CUSTOM_DELAY);
+ await executeGlobalUnpause(ctx);
+ });
+
+ it('should fail: when unpause delay is custom delay and trying to call directly before delay', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setUnpauseDelayViaTimelock(ctx, CUSTOM_DELAY);
+ await pauseGlobalTest(fixture);
+
+ await unpauseGlobalTest(fixture, {
+ revertCustomError: {
+ contract: fixture.accessControl,
+ customErrorName: 'SenderIsNotTimelock',
+ args: [
+ await fixture.pauseManager.pauseAdminRole(),
+ encodeFnSelector('globalUnpause()'),
+ fixture.owner.address,
+ ],
+ },
+ });
+ });
+ });
+
+ describe('bulkPauseContract()', () => {
+ it('should fail: when caller doesnt have pause admin role', async () => {
+ const { pausableTester, regularAccounts, pauseManager, owner } =
+ await loadFixture(defaultDeploy);
+
+ await pauseVault({ pauseManager, owner }, pausableTester, {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ });
+ });
+
+ it('should fail: when caller has only contract pauser role', async () => {
+ const {
+ pausableTester,
+ regularAccounts,
+ pauseManager,
+ owner,
+ accessControl,
+ } = await loadFixture(defaultDeploy);
+
+ await grantContractPauser(
+ accessControl,
+ pausableTester,
+ regularAccounts[0],
+ );
+
+ await pauseVault({ pauseManager, owner }, pausableTester, {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ });
+ });
+
+ it('call from pause admin when already paused', async () => {
+ const { pausableTester, pauseManager, owner } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await pauseVault({ pauseManager, owner }, pausableTester);
+ await pauseVault({ pauseManager, owner }, pausableTester);
+ });
+
+ it('call from pause admin', async () => {
+ const { pausableTester, pauseManager, owner } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await pauseVault({ pauseManager, owner }, pausableTester);
+ });
+
+ it('call from pause admin with multiple contracts', async () => {
+ const { pausableTester, depositVault, pauseManager, owner } =
+ await loadFixture(defaultDeploy);
+
+ await pauseVault({ pauseManager, owner }, [pausableTester, depositVault]);
+ });
+
+ it('when pause admin has function scoped timelock', async () => {
+ const {
+ accessControl,
+ pausableTester,
+ owner,
+ regularAccounts,
+ pauseManager,
+ timelockManager,
+ timelock,
+ } = await loadFixture(defaultDeploy);
+
+ const pauseAdminRole = await pauseManager.pauseAdminRole();
+ const bulkPauseSel = encodeFnSelector('bulkPauseContract(address[])');
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: pauseAdminRole,
+ targetContract: pauseManager.address,
+ functionSelector: bulkPauseSel,
+ grantOperator: owner,
+ });
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ pauseAdminRole,
+ pauseManager.address,
+ bulkPauseSel,
+ [{ account: regularAccounts[0].address, enabled: true }],
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [pauseAdminRole],
+ [3600],
+ );
+
+ await pauseVault({ pauseManager, owner }, pausableTester, {
+ from: regularAccounts[0],
+ });
+ });
+
+ it('should fail: when pause delay is no_delay and trying to schedule through timelock', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await scheduleBulkPauseContract(ctx, [fixture.pausableTester.address], {
+ ...noTimelockDelayRevert(fixture),
+ });
+ });
+
+ it('when pause delay is changed to custom delay, bulkPauseContract can be scheduled on timelock', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setPauseDelayViaTimelock(ctx, CUSTOM_DELAY);
+ await scheduleBulkPauseContract(ctx, [fixture.pausableTester.address]);
+ await increase(CUSTOM_DELAY);
+ await executeBulkPauseContract(ctx, [fixture.pausableTester.address]);
+ });
+
+ it('should fail: when pause delay is custom delay and trying to call directly before delay', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setPauseDelayViaTimelock(ctx, CUSTOM_DELAY);
+
+ await pauseVault(fixture, fixture.pausableTester, {
+ ...(await pauseAdminFunctionNotReady(fixture, BULK_PAUSE_CONTRACT_SEL)),
+ });
+ });
+
+ it('when pause delay is changed to null_delay, bulkPauseContract uses role timelock delay', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setPauseDelayViaTimelock(ctx, NULL_DELAY);
+ await setPauseAdminRoleTimelock(fixture);
+ await scheduleBulkPauseContract(ctx, [fixture.pausableTester.address]);
+ await increase(ROLE_TIMELOCK_DELAY);
+ await executeBulkPauseContract(ctx, [fixture.pausableTester.address]);
+ });
+ });
+
+ describe('bulkUnpauseContract()', () => {
+ it('should fail: when caller doesnt have pause admin role', async () => {
+ const {
+ pausableTester,
+ regularAccounts,
+ pauseManager,
+ ...timelockFixture
+ } = await loadFixture(defaultDeploy);
+
+ const calldata = pauseManager.interface.encodeFunctionData(
+ 'bulkUnpauseContract',
+ [[pausableTester.address]],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ timelockFixture,
+ [pauseManager.address],
+ [calldata],
+ {},
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ },
+ );
+ });
+
+ it('should fail: when caller has only contract pauser role', async () => {
+ const {
+ pausableTester,
+ regularAccounts,
+ pauseManager,
+ accessControl,
+ ...timelockFixture
+ } = await loadFixture(defaultDeploy);
+
+ await grantContractPauser(
+ accessControl,
+ pausableTester,
+ regularAccounts[0],
+ );
+
+ const calldata = pauseManager.interface.encodeFunctionData(
+ 'bulkUnpauseContract',
+ [[pausableTester.address]],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { ...timelockFixture, accessControl },
+ [pauseManager.address],
+ [calldata],
+ {},
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ },
+ );
+ });
+
+ it('call from pause admin when not paused', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+
+ await unpauseContractsViaTimelock(fixture, [
+ fixture.pausableTester.address,
+ ]);
+ });
+
+ it('when unpause delay is set, unpause can be scheduled on timelock', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+
+ await pauseVault(fixture, fixture.pausableTester);
+ await unpauseContractsViaTimelock(fixture, [
+ fixture.pausableTester.address,
+ ]);
+ });
+
+ it('should fail: when trying to call directly before delay', async () => {
+ const { pausableTester, pauseManager, owner, accessControl } =
+ await loadFixture(defaultDeploy);
+
+ await pauseVault({ pauseManager, owner }, pausableTester);
+
+ await unpauseVault({ pauseManager, owner }, pausableTester, {
+ revertCustomError: {
+ contract: accessControl,
+ customErrorName: 'SenderIsNotTimelock',
+ args: [
+ await pauseManager.pauseAdminRole(),
+ BULK_UNPAUSE_CONTRACT_SEL,
+ owner.address,
+ ],
+ },
+ });
+ });
+
+ it('when unpause delay is changed to no_delay, bulkUnpauseContract can be called directly', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setUnpauseDelayViaTimelock(ctx, NO_DELAY);
+ await pauseVault(fixture, fixture.pausableTester);
+ await unpauseVault(fixture, fixture.pausableTester);
+ });
+
+ it('should fail: when unpause delay is no_delay and trying to schedule through timelock', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setUnpauseDelayViaTimelock(ctx, NO_DELAY);
+ await pauseVault(fixture, fixture.pausableTester);
+
+ await scheduleBulkUnpauseContract(ctx, [fixture.pausableTester.address], {
+ ...noTimelockDelayRevert(fixture),
+ });
+ });
+
+ it('when unpause delay is changed to null_delay, bulkUnpauseContract uses role timelock delay', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setUnpauseDelayViaTimelock(ctx, NULL_DELAY);
+ await setPauseAdminRoleTimelock(fixture);
+ await pauseVault(fixture, fixture.pausableTester);
+ await scheduleBulkUnpauseContract(ctx, [fixture.pausableTester.address]);
+ await increase(ROLE_TIMELOCK_DELAY);
+ await executeBulkUnpauseContract(ctx, [fixture.pausableTester.address]);
+ });
+
+ it('when unpause delay is changed to custom delay, bulkUnpauseContract can be scheduled on timelock', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setUnpauseDelayViaTimelock(ctx, CUSTOM_DELAY);
+ await pauseVault(fixture, fixture.pausableTester);
+ await scheduleBulkUnpauseContract(ctx, [fixture.pausableTester.address]);
+ await increase(CUSTOM_DELAY);
+ await executeBulkUnpauseContract(ctx, [fixture.pausableTester.address]);
+ });
+
+ it('should fail: when unpause delay is custom delay and trying to call directly before delay', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setUnpauseDelayViaTimelock(ctx, CUSTOM_DELAY);
+ await pauseVault(fixture, fixture.pausableTester);
+
+ await unpauseVault(fixture, fixture.pausableTester, {
+ ...(await pauseAdminFunctionNotReady(
+ fixture,
+ BULK_UNPAUSE_CONTRACT_SEL,
+ )),
+ });
+ });
+ });
+
+ describe('bulkPauseContractFn()', () => {
+ const depositRequestSel = DEPOSIT_REQUEST_SEL;
+
+ it('should fail: when caller doesnt have pause admin role', async () => {
+ const { pausableTester, regularAccounts, pauseManager, owner } =
+ await loadFixture(defaultDeploy);
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ pausableTester,
+ depositRequestSel,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ },
+ );
+ });
+
+ it('should fail: when caller has only contract pauser role', async () => {
+ const {
+ pausableTester,
+ regularAccounts,
+ pauseManager,
+ owner,
+ accessControl,
+ } = await loadFixture(defaultDeploy);
+
+ await grantContractPauser(
+ accessControl,
+ pausableTester,
+ regularAccounts[0],
+ );
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ pausableTester,
+ depositRequestSel,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ },
+ );
+ });
+
+ it('call from pause admin when already paused', async () => {
+ const { pausableTester, pauseManager, owner } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ pausableTester,
+ depositRequestSel,
+ );
+ await pauseVaultFn(
+ { pauseManager, owner },
+ pausableTester,
+ depositRequestSel,
+ );
+ });
+
+ it('call from pause admin', async () => {
+ const { pausableTester, pauseManager, owner } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ pausableTester,
+ depositRequestSel,
+ );
+ });
+
+ it('when pause admin has function scoped timelock', async () => {
+ const {
+ accessControl,
+ pausableTester,
+ owner,
+ regularAccounts,
+ pauseManager,
+ timelockManager,
+ timelock,
+ } = await loadFixture(defaultDeploy);
+
+ const pauseAdminRole = await pauseManager.pauseAdminRole();
+ const bulkPauseFnSel = encodeFnSelector(
+ 'bulkPauseContractFn(address[],bytes4[])',
+ );
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: pauseAdminRole,
+ targetContract: pauseManager.address,
+ functionSelector: bulkPauseFnSel,
+ grantOperator: owner,
+ });
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ pauseAdminRole,
+ pauseManager.address,
+ bulkPauseFnSel,
+ [{ account: regularAccounts[0].address, enabled: true }],
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [pauseAdminRole],
+ [3600],
+ );
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ pausableTester,
+ depositRequestSel,
+ { from: regularAccounts[0] },
+ );
+ });
+
+ it('should fail: when pause delay is no_delay and trying to schedule through timelock', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await scheduleBulkPauseContractFn(
+ ctx,
+ [fixture.pausableTester.address],
+ [depositRequestSel],
+ noTimelockDelayRevert(fixture),
+ );
+ });
+
+ it('when pause delay is changed to custom delay, bulkPauseContractFn can be scheduled on timelock', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setPauseDelayViaTimelock(ctx, CUSTOM_DELAY);
+ await scheduleBulkPauseContractFn(
+ ctx,
+ [fixture.pausableTester.address],
+ [depositRequestSel],
+ );
+ await increase(CUSTOM_DELAY);
+ await executeBulkPauseContractFn(
+ ctx,
+ [fixture.pausableTester.address],
+ [depositRequestSel],
+ );
+ });
+
+ it('should fail: when pause delay is custom delay and trying to call directly before delay', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setPauseDelayViaTimelock(ctx, CUSTOM_DELAY);
+
+ await pauseVaultFn(fixture, fixture.pausableTester, depositRequestSel, {
+ ...(await pauseAdminFunctionNotReady(
+ fixture,
+ BULK_PAUSE_CONTRACT_FN_SEL,
+ )),
+ });
+ });
+
+ it('when pause delay is changed to null_delay, bulkPauseContractFn uses role timelock delay', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setPauseDelayViaTimelock(ctx, NULL_DELAY);
+ await setPauseAdminRoleTimelock(fixture);
+ await scheduleBulkPauseContractFn(
+ ctx,
+ [fixture.pausableTester.address],
+ [depositRequestSel],
+ );
+ await increase(ROLE_TIMELOCK_DELAY);
+ await executeBulkPauseContractFn(
+ ctx,
+ [fixture.pausableTester.address],
+ [depositRequestSel],
+ );
+ });
+ });
+
+ describe('bulkUnpauseContractFn()', () => {
+ const depositRequestSel = DEPOSIT_REQUEST_SEL;
+
+ it('should fail: when caller doesnt have pause admin role', async () => {
+ const {
+ pausableTester,
+ regularAccounts,
+ pauseManager,
+ ...timelockFixture
+ } = await loadFixture(defaultDeploy);
+
+ const calldata = pauseManager.interface.encodeFunctionData(
+ 'bulkUnpauseContractFn',
+ [[pausableTester.address], [depositRequestSel]],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ timelockFixture,
+ [pauseManager.address],
+ [calldata],
+ {},
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ },
+ );
+ });
+
+ it('should fail: when caller has only contract pauser role', async () => {
+ const {
+ pausableTester,
+ regularAccounts,
+ pauseManager,
+ accessControl,
+ ...timelockFixture
+ } = await loadFixture(defaultDeploy);
+
+ await grantContractPauser(
+ accessControl,
+ pausableTester,
+ regularAccounts[0],
+ );
+
+ const calldata = pauseManager.interface.encodeFunctionData(
+ 'bulkUnpauseContractFn',
+ [[pausableTester.address], [depositRequestSel]],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { ...timelockFixture, accessControl },
+ [pauseManager.address],
+ [calldata],
+ {},
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ },
+ );
+ });
+
+ it('call from pause admin when not paused', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+
+ await unpauseContractFnsViaTimelock(
+ fixture,
+ [fixture.pausableTester.address],
+ [depositRequestSel],
+ );
+ });
+
+ it('when unpause delay is set, unpause can be scheduled on timelock', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+
+ await pauseVaultFn(fixture, fixture.pausableTester, depositRequestSel);
+ await unpauseContractFnsViaTimelock(
+ fixture,
+ [fixture.pausableTester.address],
+ [depositRequestSel],
+ );
+ });
+
+ it('should fail: when trying to call directly before delay', async () => {
+ const { pausableTester, pauseManager, owner, accessControl } =
+ await loadFixture(defaultDeploy);
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ pausableTester,
+ depositRequestSel,
+ );
+
+ await unpauseVaultFn(
+ { pauseManager, owner },
+ pausableTester,
+ depositRequestSel,
+ {
+ revertCustomError: {
+ contract: accessControl,
+ customErrorName: 'SenderIsNotTimelock',
+ args: [
+ await pauseManager.pauseAdminRole(),
+ BULK_UNPAUSE_CONTRACT_FN_SEL,
+ owner.address,
+ ],
+ },
+ },
+ );
+ });
+
+ it('when unpause delay is changed to no_delay, bulkUnpauseContractFn can be called directly', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setUnpauseDelayViaTimelock(ctx, NO_DELAY);
+ await pauseVaultFn(fixture, fixture.pausableTester, depositRequestSel);
+ await unpauseVaultFn(fixture, fixture.pausableTester, depositRequestSel);
+ });
+
+ it('should fail: when unpause delay is no_delay and trying to schedule through timelock', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setUnpauseDelayViaTimelock(ctx, NO_DELAY);
+ await pauseVaultFn(fixture, fixture.pausableTester, depositRequestSel);
+
+ await scheduleBulkUnpauseContractFn(
+ ctx,
+ [fixture.pausableTester.address],
+ [depositRequestSel],
+ noTimelockDelayRevert(fixture),
+ );
+ });
+
+ it('when unpause delay is changed to null_delay, bulkUnpauseContractFn uses role timelock delay', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setUnpauseDelayViaTimelock(ctx, NULL_DELAY);
+ await setPauseAdminRoleTimelock(fixture);
+ await pauseVaultFn(fixture, fixture.pausableTester, depositRequestSel);
+ await scheduleBulkUnpauseContractFn(
+ ctx,
+ [fixture.pausableTester.address],
+ [depositRequestSel],
+ );
+ await increase(ROLE_TIMELOCK_DELAY);
+ await executeBulkUnpauseContractFn(
+ ctx,
+ [fixture.pausableTester.address],
+ [depositRequestSel],
+ );
+ });
+
+ it('when unpause delay is changed to custom delay, bulkUnpauseContractFn can be scheduled on timelock', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setUnpauseDelayViaTimelock(ctx, CUSTOM_DELAY);
+ await pauseVaultFn(fixture, fixture.pausableTester, depositRequestSel);
+ await scheduleBulkUnpauseContractFn(
+ ctx,
+ [fixture.pausableTester.address],
+ [depositRequestSel],
+ );
+ await increase(CUSTOM_DELAY);
+ await executeBulkUnpauseContractFn(
+ ctx,
+ [fixture.pausableTester.address],
+ [depositRequestSel],
+ );
+ });
+
+ it('should fail: when unpause delay is custom delay and trying to call directly before delay', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setUnpauseDelayViaTimelock(ctx, CUSTOM_DELAY);
+ await pauseVaultFn(fixture, fixture.pausableTester, depositRequestSel);
+
+ await unpauseVaultFn(fixture, fixture.pausableTester, depositRequestSel, {
+ ...(await pauseAdminFunctionNotReady(
+ fixture,
+ BULK_UNPAUSE_CONTRACT_FN_SEL,
+ )),
+ });
+ });
+ });
+
+ describe('contractAdminPause()', () => {
+ it('should fail: when caller doesnt have contract pauser role', async () => {
+ const { pausableTester, regularAccounts, pauseManager, owner } =
+ await loadFixture(defaultDeploy);
+
+ await adminPauseContractTest({ pauseManager, owner }, pausableTester, {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ });
+ });
+
+ it('should fail: when caller has only pause admin role', async () => {
+ const {
+ pausableTester,
+ regularAccounts,
+ pauseManager,
+ owner,
+ accessControl,
+ } = await loadFixture(defaultDeploy);
+
+ const pauseAdminRole = await pauseManager.pauseAdminRole();
+ await accessControl['grantRole(bytes32,address)'](
+ pauseAdminRole,
+ regularAccounts[0].address,
+ );
+
+ await adminPauseContractTest({ pauseManager, owner }, pausableTester, {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ });
+ });
+
+ it('should fail: when role is user facing', async () => {
+ const { pausableTester, pauseManager, owner, roles } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await pausableTester.setContractAdminRole(roles.common.greenlisted);
+
+ await adminPauseContractTest({ pauseManager, owner }, pausableTester, {
+ revertCustomError: {
+ customErrorName: 'UserFacingRoleNotAllowed',
+ args: [roles.common.greenlisted],
+ },
+ });
+ });
+
+ it('call from contract pauser when already paused', async () => {
+ const { pausableTester, pauseManager, owner } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await adminPauseContractTest({ pauseManager, owner }, pausableTester);
+ await adminPauseContractTest({ pauseManager, owner }, pausableTester);
+ });
+
+ it('call from contract pauser', async () => {
+ const { pausableTester, pauseManager, owner } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await adminPauseContractTest({ pauseManager, owner }, pausableTester);
+ });
+
+ it('when contract pauser has function scoped access with timelock', async () => {
+ const {
+ accessControl,
+ pausableTester,
+ owner,
+ regularAccounts,
+ pauseManager,
+ timelockManager,
+ timelock,
+ roles,
+ } = await loadFixture(defaultDeploy);
+
+ await pausableTester.setContractAdminRole(
+ roles.common.blacklistedOperator,
+ );
+ const contractPauserRole = await pausableTester.contractAdminRole();
+ const pauseSel = encodeFnSelector('contractAdminPause(address)');
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: contractPauserRole,
+ targetContract: pauseManager.address,
+ functionSelector: pauseSel,
+ grantOperator: owner,
+ });
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ contractPauserRole,
+ pauseManager.address,
+ pauseSel,
+ [{ account: regularAccounts[0].address, enabled: true }],
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [contractPauserRole],
+ [3600],
+ );
+
+ await adminPauseContractTest({ pauseManager, owner }, pausableTester, {
+ from: regularAccounts[0],
+ });
+ });
+
+ it('should fail: when pause delay is no_delay and trying to schedule through timelock', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await scheduleContractAdminPause(
+ ctx,
+ fixture.pausableTester.address,
+ noTimelockDelayRevert(fixture),
+ );
+ });
+
+ it('when pause delay is changed to custom delay, contractAdminPause can be scheduled on timelock', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setPauseDelayViaTimelock(ctx, CUSTOM_DELAY);
+ await scheduleContractAdminPause(ctx, fixture.pausableTester.address);
+ await increase(CUSTOM_DELAY);
+ await executeContractAdminPause(ctx, fixture.pausableTester.address);
+ });
+
+ it('should fail: when pause delay is custom delay and trying to call directly before delay', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setPauseDelayViaTimelock(ctx, CUSTOM_DELAY);
+
+ await adminPauseContractTest(fixture, fixture.pausableTester, {
+ ...(await contractPauserFunctionNotReady(
+ fixture,
+ CONTRACT_ADMIN_PAUSE_SEL,
+ )),
+ });
+ });
+
+ it('when pause delay is changed to null_delay, contractAdminPause uses role timelock delay', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setPauseDelayViaTimelock(ctx, NULL_DELAY);
+ await setContractPauserRoleTimelock(fixture);
+ await scheduleContractAdminPause(ctx, fixture.pausableTester.address);
+ await increase(ROLE_TIMELOCK_DELAY);
+ await executeContractAdminPause(ctx, fixture.pausableTester.address);
+ });
+ });
+
+ describe('contractAdminUnpause()', () => {
+ it('should fail: when caller doesnt have contract pauser role', async () => {
+ const {
+ pausableTester,
+ regularAccounts,
+ pauseManager,
+ ...timelockFixture
+ } = await loadFixture(defaultDeploy);
+
+ const calldata = pauseManager.interface.encodeFunctionData(
+ 'contractAdminUnpause',
+ [pausableTester.address],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ timelockFixture,
+ [pauseManager.address],
+ [calldata],
+ {},
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ },
+ );
+ });
+
+ it('should fail: when caller has only pause admin role', async () => {
+ const {
+ pausableTester,
+ regularAccounts,
+ pauseManager,
+ accessControl,
+ ...timelockFixture
+ } = await loadFixture(defaultDeploy);
+
+ const pauseAdminRole = await pauseManager.pauseAdminRole();
+ await accessControl['grantRole(bytes32,address)'](
+ pauseAdminRole,
+ regularAccounts[0].address,
+ );
+
+ const calldata = pauseManager.interface.encodeFunctionData(
+ 'contractAdminUnpause',
+ [pausableTester.address],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { ...timelockFixture, accessControl },
+ [pauseManager.address],
+ [calldata],
+ {},
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ },
+ );
+ });
+
+ it('call from contract pauser when not paused', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+
+ await contractAdminUnpauseViaTimelock(fixture, fixture.pausableTester);
+ });
+
+ it('when unpause delay is set, unpause can be scheduled on timelock', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+
+ await adminPauseContractTest(fixture, fixture.pausableTester);
+ await contractAdminUnpauseViaTimelock(fixture, fixture.pausableTester);
+ });
+
+ it('should fail: when trying to call directly before delay', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+
+ await adminPauseContractTest(fixture, fixture.pausableTester);
+
+ await adminUnpauseContractTest(fixture, fixture.pausableTester, {
+ ...(await contractPauserFunctionNotReady(
+ fixture,
+ CONTRACT_ADMIN_UNPAUSE_SEL,
+ )),
+ });
+ });
+
+ it('when unpause delay is changed to no_delay, contractAdminUnpause can be called directly', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setUnpauseDelayViaTimelock(ctx, NO_DELAY);
+ await adminPauseContractTest(fixture, fixture.pausableTester);
+ await adminUnpauseContractTest(fixture, fixture.pausableTester);
+ });
+
+ it('should fail: when unpause delay is no_delay and trying to schedule through timelock', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setUnpauseDelayViaTimelock(ctx, NO_DELAY);
+ await adminPauseContractTest(fixture, fixture.pausableTester);
+
+ await scheduleContractAdminUnpause(
+ ctx,
+ fixture.pausableTester.address,
+ noTimelockDelayRevert(fixture),
+ );
+ });
+
+ it('when unpause delay is changed to null_delay, contractAdminUnpause uses role timelock delay', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setUnpauseDelayViaTimelock(ctx, NULL_DELAY);
+ await setContractPauserRoleTimelock(fixture);
+ await adminPauseContractTest(fixture, fixture.pausableTester);
+ await scheduleContractAdminUnpause(ctx, fixture.pausableTester.address);
+ await increase(ROLE_TIMELOCK_DELAY);
+ await executeContractAdminUnpause(ctx, fixture.pausableTester.address);
+ });
+
+ it('when unpause delay is changed to custom delay, contractAdminUnpause can be scheduled on timelock', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setUnpauseDelayViaTimelock(ctx, CUSTOM_DELAY);
+ await adminPauseContractTest(fixture, fixture.pausableTester);
+ await scheduleContractAdminUnpause(ctx, fixture.pausableTester.address);
+ await increase(CUSTOM_DELAY);
+ await executeContractAdminUnpause(ctx, fixture.pausableTester.address);
+ });
+
+ it('should fail: when unpause delay is custom delay and trying to call directly before delay', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const ctx = toTimelockCtx(fixture);
+
+ await setUnpauseDelayViaTimelock(ctx, CUSTOM_DELAY);
+ await adminPauseContractTest(fixture, fixture.pausableTester);
+
+ await adminUnpauseContractTest(fixture, fixture.pausableTester, {
+ ...(await contractPauserFunctionNotReady(
+ fixture,
+ CONTRACT_ADMIN_UNPAUSE_SEL,
+ )),
+ });
+ });
+ });
+
+ describe('setPauseDelay()', () => {
+ it('should fail: when caller doesnt have pause admin role', async () => {
+ const { pauseManager, regularAccounts, ...timelockFixture } =
+ await loadFixture(defaultDeploy);
+
+ const calldata = pauseManager.interface.encodeFunctionData(
+ 'setPauseDelay',
+ [NO_DELAY],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ timelockFixture,
+ [pauseManager.address],
+ [calldata],
+ {},
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ },
+ );
+ });
+
+ it('should fail: when trying to call directly before delay', async () => {
+ const { pauseManager, owner } = await loadFixture(defaultDeploy);
+
+ await expect(pauseManager.connect(owner).setPauseDelay(3600))
+ .revertedWithCustomError(pauseManager, 'SenderIsNotTimelock')
+ .withArgs(
+ await pauseManager.pauseAdminRole(),
+ encodeFnSelector('setPauseDelay(uint32)'),
+ owner.address,
+ );
+ });
+
+ it('call from pause admin after delay', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+
+ await setPauseDelayViaTimelock(fixture, 3600);
+ });
+ });
+
+ describe('setUnpauseDelay()', () => {
+ it('should fail: when caller doesnt have pause admin role', async () => {
+ const { pauseManager, regularAccounts, ...timelockFixture } =
+ await loadFixture(defaultDeploy);
+
+ const calldata = pauseManager.interface.encodeFunctionData(
+ 'setUnpauseDelay',
+ [DEFAULT_UNPAUSE_DELAY],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ timelockFixture,
+ [pauseManager.address],
+ [calldata],
+ {},
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ },
+ );
+ });
+
+ it('should fail: when trying to call directly before delay', async () => {
+ const { pauseManager, owner } = await loadFixture(defaultDeploy);
+
+ await expect(
+ pauseManager.connect(owner).setUnpauseDelay(DEFAULT_UNPAUSE_DELAY * 2),
+ )
+ .revertedWithCustomError(pauseManager, 'SenderIsNotTimelock')
+ .withArgs(
+ await pauseManager.pauseAdminRole(),
+ encodeFnSelector('setUnpauseDelay(uint32)'),
+ owner.address,
+ );
+ });
+
+ it('call from pause admin after delay', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+
+ await setUnpauseDelayViaTimelock(fixture, DEFAULT_UNPAUSE_DELAY * 2);
+ });
+
+ it('when unpause delay is changed, unpause uses new delay', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const newUnpauseDelay = DEFAULT_UNPAUSE_DELAY * 2;
+
+ await setUnpauseDelayViaTimelock(fixture, newUnpauseDelay);
+ await pauseGlobalTest(fixture);
+
+ const calldata =
+ fixture.pauseManager.interface.encodeFunctionData('globalUnpause');
+
+ await bulkScheduleTimelockOperationTester(
+ fixture,
+ [fixture.pauseManager.address],
+ [calldata],
+ );
+ await increase(DEFAULT_UNPAUSE_DELAY);
+
+ await executeTimelockOperationTester(
+ fixture,
+ fixture.pauseManager.address,
+ calldata,
+ fixture.owner.address,
+ {
+ revertCustomError: {
+ contract: fixture.timelockManager,
+ customErrorName: 'TimelockOperationNotReady',
+ },
+ },
+ );
+
+ await increase(DEFAULT_UNPAUSE_DELAY);
+ await executeTimelockOperationTester(
+ fixture,
+ fixture.pauseManager.address,
+ calldata,
+ fixture.owner.address,
+ );
+ });
+ });
+});
diff --git a/test/unit/MidasTimelockManager.test.ts b/test/unit/MidasTimelockManager.test.ts
new file mode 100644
index 00000000..faa5200d
--- /dev/null
+++ b/test/unit/MidasTimelockManager.test.ts
@@ -0,0 +1,4257 @@
+import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
+import { increase } from '@nomicfoundation/hardhat-network-helpers/dist/src/helpers/time';
+import { days } from '@nomicfoundation/hardhat-network-helpers/dist/src/helpers/time/duration';
+import { expect } from 'chai';
+import { constants, ethers } from 'ethers';
+
+import { encodeFnSelector } from '../../helpers/utils';
+import {
+ MidasTimelockManager,
+ MidasTimelockManager__factory,
+ MidasTimelockManagerTest__factory,
+} from '../../typechain-types';
+import {
+ NO_DELAY,
+ setPermissionRoleTester,
+ setRoleTimelocksTester,
+ setupGrantOperatorRole,
+} from '../common/ac.helpers';
+import {
+ asyncForEach,
+ OptionalCommonParams,
+ validateImplementation,
+} from '../common/common.helpers';
+import { defaultDeploy } from '../common/fixtures';
+import {
+ abortOperationTest,
+ executeTimelockOperationTester,
+ pauseTimelockOperationTest,
+ bulkScheduleTimelockOperationTester,
+ scheduleTimelockOperationTester,
+ setMaxPendingOperationsPerProposerTester,
+ setSecurityCouncilTest,
+ voteForExecutionTest,
+ voteForVetoTest,
+} from '../common/timelock-manager.helpers';
+
+const executeTimelockOperationSelector = encodeFnSelector(
+ 'executeTimelockOperation(address,bytes,bool)',
+);
+const withOnlyContractAdminSelector = encodeFnSelector(
+ 'withOnlyContractAdmin()',
+);
+
+export const timelockManagerRevert = (
+ timelockManager: MidasTimelockManager,
+ customErrorName: string,
+ args?: unknown[],
+): OptionalCommonParams => ({
+ revertCustomError: {
+ contract: timelockManager,
+ customErrorName,
+ args,
+ },
+});
+
+describe('MidasTimelockManager', () => {
+ it('deployment', async () => {
+ const { accessControl, timelockManager, timelock, councilMembers } =
+ await loadFixture(defaultDeploy);
+
+ expect(await timelockManager.accessControl()).to.eq(accessControl.address);
+ expect(await timelockManager.timelock()).to.eq(timelock.address);
+ expect(await timelockManager.councilQuorum(0)).to.eq(3);
+ const councilMembersInContract =
+ await timelockManager.getSecurityCouncilMembers(0);
+ expect(await timelockManager.SECURITY_COUNCIL_MIN_MEMBERS()).to.eq(5);
+ expect(councilMembersInContract.length).to.eq(councilMembers.length);
+
+ for (const member of councilMembersInContract) {
+ expect(councilMembersInContract.includes(member)).to.eq(true);
+ }
+ expect(await timelockManager.EXPIRY_PERIOD()).to.eq(days(45));
+ expect(await timelockManager.DISPUTE_PERIOD()).to.eq(days(3));
+ expect(
+ await timelockManager.MAX_OF_MAX_PENDING_OPERATIONS_PER_PROPOSER(),
+ ).to.eq(100);
+ expect(await timelockManager.SECURITY_COUNCIL_MAX_MEMBERS()).to.eq(15);
+ expect(await timelockManager.SECURITY_COUNCIL_MIN_MEMBERS()).to.eq(5);
+ expect(await timelockManager.maxPendingOperationsPerProposer()).to.eq(100);
+
+ await validateImplementation(MidasTimelockManager__factory);
+ });
+
+ describe('initializeTimelock()', () => {
+ it('should initialize timelock address', async () => {
+ const { accessControl, timelock, councilMembers, owner } =
+ await loadFixture(defaultDeploy);
+
+ const timelockManager = await new MidasTimelockManagerTest__factory(
+ owner,
+ ).deploy();
+
+ await timelockManager.initialize(
+ accessControl.address,
+ 100,
+ councilMembers.map((v) => v.address),
+ );
+
+ await timelockManager.initializeTimelock(timelock.address);
+
+ expect(await timelockManager.timelock()).to.eq(timelock.address);
+ });
+
+ it('should fail: when timelock is already set', async () => {
+ const { timelockManager, timelock } = await loadFixture(defaultDeploy);
+
+ await expect(
+ timelockManager.initializeTimelock(timelock.address),
+ ).to.be.revertedWithCustomError(timelockManager, 'TimelockAlreadySet');
+ });
+
+ it('should fail: when timelock address is zero', async () => {
+ const { accessControl, councilMembers, owner } = await loadFixture(
+ defaultDeploy,
+ );
+
+ const timelockManager = await new MidasTimelockManagerTest__factory(
+ owner,
+ ).deploy();
+
+ await timelockManager.initialize(
+ accessControl.address,
+ 100,
+ councilMembers.map((v) => v.address),
+ );
+
+ await expect(timelockManager.initializeTimelock(constants.AddressZero))
+ .to.be.revertedWithCustomError(timelockManager, 'InvalidAddress')
+ .withArgs(constants.AddressZero);
+ });
+
+ it('should fail: when caller do not have DEFAULT_ADMIN_ROLE', async () => {
+ const {
+ accessControl,
+ timelock,
+ councilMembers,
+ owner,
+ regularAccounts,
+ } = await loadFixture(defaultDeploy);
+
+ const timelockManager = await new MidasTimelockManagerTest__factory(
+ owner,
+ ).deploy();
+
+ await timelockManager.initialize(
+ accessControl.address,
+ 100,
+ councilMembers.map((v) => v.address),
+ );
+
+ await expect(
+ timelockManager
+ .connect(regularAccounts[0])
+ .initializeTimelock(timelock.address),
+ ).to.be.revertedWithCustomError(timelockManager, 'NoFunctionPermission');
+ });
+ });
+
+ describe('scheduleTimelockOperation()', () => {
+ it('should schedule timelock operation', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ await scheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ );
+ });
+
+ it('when same target and calldata was executed it can be scheduled again', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+ await scheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ owner.address,
+ );
+
+ await scheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ );
+ });
+
+ it('when too many pending operations for a signle proposer but should affect different proposer', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ regularAccounts,
+ } = await loadFixture(defaultDeploy);
+
+ await accessControl['grantRole(bytes32,address)'](
+ constants.HashZero,
+ regularAccounts[0].address,
+ );
+
+ await setMaxPendingOperationsPerProposerTester(
+ { timelockManager, owner, accessControl, timelock },
+ 1,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ await scheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ );
+
+ await scheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ accessControl.address,
+ accessControl.interface.encodeFunctionData(
+ 'grantRole(bytes32,address)',
+ [constants.HashZero, wAccessControlTester.address],
+ ),
+ {},
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: when same target and calldata is pending and not yet executed', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+
+ await scheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ );
+
+ await scheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ {},
+ timelockManagerRevert(timelockManager, 'OperationAlreadyPending'),
+ );
+ });
+
+ it('should fail: when same target and calldata is pending from another proposer', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ regularAccounts,
+ } = await loadFixture(defaultDeploy);
+
+ await accessControl['grantRole(bytes32,address)'](
+ constants.HashZero,
+ regularAccounts[0].address,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+
+ await scheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ );
+
+ await scheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ {},
+ {
+ ...timelockManagerRevert(timelockManager, 'OperationAlreadyPending'),
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: when role do not have timelock delay', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await scheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ {},
+ timelockManagerRevert(timelockManager, 'NoTimelockDelayForRole'),
+ );
+ });
+
+ it('should fail: when too many pending operations for a signle proposer', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setMaxPendingOperationsPerProposerTester(
+ { timelockManager, owner, accessControl, timelock },
+ 1,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+ await scheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ );
+
+ await scheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ accessControl.address,
+ accessControl.interface.encodeFunctionData(
+ 'grantRole(bytes32,address)',
+ [constants.HashZero, wAccessControlTester.address],
+ ),
+ {},
+ timelockManagerRevert(timelockManager, 'TooManyPendingOperations'),
+ );
+ });
+
+ it('should fail: when required role is user facing', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ roles,
+ } = await loadFixture(defaultDeploy);
+
+ await wAccessControlTester.setContractAdminRole(roles.common.greenlisted);
+
+ await scheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ {},
+ timelockManagerRevert(timelockManager, 'UserFacingRoleNotAllowed', [
+ roles.common.greenlisted,
+ ]),
+ );
+ });
+
+ it('should fail: when target is timelock address', async () => {
+ const { timelockManager, timelock, owner, accessControl } =
+ await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ await scheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ timelock.address,
+ '0x',
+ {},
+ timelockManagerRevert(timelockManager, 'InvalidAddress', [
+ timelock.address,
+ ]),
+ );
+ });
+
+ it('should fail: when trying to schedule on function that is not protected with onlyRole', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const calldata =
+ wAccessControlTester.interface.encodeFunctionData('withUnprotected');
+
+ await scheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ {},
+ timelockManagerRevert(
+ timelockManager,
+ 'PreflightCallUnexpectedSuccess',
+ ),
+ );
+ });
+
+ it('should fail: when preflight reverted with invalid error', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ roles,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withWrongRolePreflight',
+ [roles.common.defaultAdmin, 0, false, true],
+ );
+
+ await scheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ {},
+ timelockManagerRevert(timelockManager, 'InvalidPreflightError'),
+ );
+ });
+ });
+
+ describe('bulkScheduleTimelockOperation()', () => {
+ it('should schedule timelock operation', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+ });
+
+ it('when same target and calldata was executed it can be scheduled again', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ owner.address,
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ );
+ });
+
+ it('when too many pending operations for a signle proposer but should affect different proposer', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ regularAccounts,
+ } = await loadFixture(defaultDeploy);
+
+ await accessControl['grantRole(bytes32,address)'](
+ constants.HashZero,
+ regularAccounts[0].address,
+ );
+
+ await setMaxPendingOperationsPerProposerTester(
+ { timelockManager, owner, accessControl, timelock },
+ 1,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [accessControl.address],
+ [
+ accessControl.interface.encodeFunctionData(
+ 'grantRole(bytes32,address)',
+ [constants.HashZero, wAccessControlTester.address],
+ ),
+ ],
+ {},
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: when same target and calldata is pending and not yet executed', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ {},
+ timelockManagerRevert(timelockManager, 'OperationAlreadyPending'),
+ );
+ });
+
+ it('should fail: when same target and calldata is pending from another proposer', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ regularAccounts,
+ } = await loadFixture(defaultDeploy);
+
+ await accessControl['grantRole(bytes32,address)'](
+ constants.HashZero,
+ regularAccounts[0].address,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ {},
+ {
+ ...timelockManagerRevert(timelockManager, 'OperationAlreadyPending'),
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: when role do not have timelock delay', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ {},
+ timelockManagerRevert(timelockManager, 'NoTimelockDelayForRole'),
+ );
+ });
+
+ it('should fail: when too many pending operations for a signle proposer', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setMaxPendingOperationsPerProposerTester(
+ { timelockManager, owner, accessControl, timelock },
+ 1,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [accessControl.address],
+ [
+ accessControl.interface.encodeFunctionData(
+ 'grantRole(bytes32,address)',
+ [constants.HashZero, wAccessControlTester.address],
+ ),
+ ],
+ {},
+ timelockManagerRevert(timelockManager, 'TooManyPendingOperations'),
+ );
+ });
+
+ it('should fail: when required role is user facing', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ roles,
+ } = await loadFixture(defaultDeploy);
+
+ await wAccessControlTester.setContractAdminRole(roles.common.greenlisted);
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ {},
+ timelockManagerRevert(timelockManager, 'UserFacingRoleNotAllowed', [
+ roles.common.greenlisted,
+ ]),
+ );
+ });
+
+ it('should fail: when target is timelock address', async () => {
+ const { timelockManager, timelock, owner, accessControl } =
+ await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [timelock.address],
+ ['0x'],
+ {},
+ timelockManagerRevert(timelockManager, 'InvalidAddress', [
+ timelock.address,
+ ]),
+ );
+ });
+
+ it('should schedule multiple timelock operations in one transaction', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+ const grantRoleCalldata = accessControl.interface.encodeFunctionData(
+ 'grantRole(bytes32,address)',
+ [constants.HashZero, wAccessControlTester.address],
+ );
+
+ const operationIds = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address, accessControl.address],
+ [calldata, grantRoleCalldata],
+ );
+
+ expect(operationIds.length).to.eq(2);
+ });
+ });
+
+ describe('executeTimelockOperation()', () => {
+ it('should fail: when operation do not exist', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ owner.address,
+ {
+ ...timelockManagerRevert(
+ timelockManager,
+ 'UnexpectedOperationStatus',
+ ),
+ },
+ );
+ });
+
+ it('should fail: when operation exist but delay is not passed', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ );
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ owner.address,
+ {
+ ...timelockManagerRevert(
+ timelockManager,
+ 'TimelockOperationNotReady',
+ ),
+ },
+ );
+ });
+
+ it('should fail: when caller does not have contract admin role or function permission for executeTimelockOperation', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ regularAccounts,
+ } = await loadFixture(defaultDeploy);
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ owner.address,
+ {
+ ...timelockManagerRevert(timelockManager, 'NoFunctionPermission'),
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('when operation exist and timelock passed and caller has DEFAULT_ADMIN_ROLE role', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ regularAccounts,
+ } = await loadFixture(defaultDeploy);
+
+ await accessControl['grantRole(bytes32,address)'](
+ constants.HashZero,
+ regularAccounts[0].address,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ {},
+ {
+ from: regularAccounts[0],
+ },
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ regularAccounts[0].address,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('when operation exist and timelock passed and caller has function permission for executeTimelockOperation', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ regularAccounts,
+ roles,
+ } = await loadFixture(defaultDeploy);
+
+ const defaultAdminRole = roles.common.defaultAdmin;
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: defaultAdminRole,
+ targetContract: timelockManager.address,
+ functionSelector: executeTimelockOperationSelector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ timelockManager.address,
+ executeTimelockOperationSelector,
+ [{ account: regularAccounts[0].address, enabled: true }],
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ owner.address,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: with SenderIsNotTimelock when calling target directly after delay passed', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ );
+
+ await increase(3600);
+
+ await expect(wAccessControlTester.withOnlyContractAdmin())
+ .revertedWithCustomError(accessControl, 'SenderIsNotTimelock')
+ .withArgs(
+ constants.HashZero,
+ withOnlyContractAdminSelector,
+ owner.address,
+ );
+ });
+
+ it('when same target and calldata was executed and scheduled again it can be executed again', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ owner.address,
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ owner.address,
+ );
+ });
+
+ it('should fail: when operation is paused and timelock delay is passed', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ owner.address,
+ {
+ ...timelockManagerRevert(
+ timelockManager,
+ 'UnexpectedOperationStatus',
+ [2],
+ ),
+ },
+ );
+ });
+
+ it('when set security council operation is executed via timelock', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ councilMembers,
+ regularAccounts,
+ } = await loadFixture(defaultDeploy);
+
+ const securityCouncilManagerRole =
+ await timelockManager.SECURITY_COUNCIL_MANAGER_ROLE();
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [securityCouncilManagerRole],
+ [3600],
+ );
+
+ const newCouncilMembers = [
+ councilMembers[0].address,
+ councilMembers[1].address,
+ councilMembers[2].address,
+ councilMembers[3].address,
+ regularAccounts[0].address,
+ ];
+ const setCouncilCalldata = timelockManager.interface.encodeFunctionData(
+ 'setSecurityCouncil',
+ [newCouncilMembers],
+ );
+
+ const councilVersionBefore =
+ await timelockManager.securityCouncilVersion();
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [timelockManager.address],
+ [setCouncilCalldata],
+ { isSetCouncilOperation: true },
+ );
+
+ expect(await timelockManager.pendingSetCouncilOperationId()).to.eq(
+ operationId,
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ timelockManager.address,
+ setCouncilCalldata,
+ owner.address,
+ );
+
+ expect(await timelockManager.pendingSetCouncilOperationId()).to.eq(
+ constants.HashZero,
+ );
+ expect(await timelockManager.securityCouncilVersion()).to.eq(
+ councilVersionBefore.add(1),
+ );
+ });
+
+ it('when revertOnFailure is false and underlying call fails', async () => {
+ const { timelockManager, timelock, owner, accessControl } =
+ await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ // body validation fails only on execute; preflight still succeeds
+ const failingCalldata = timelockManager.interface.encodeFunctionData(
+ 'setMaxPendingOperationsPerProposer',
+ [0],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [timelockManager.address],
+ [failingCalldata],
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ timelockManager.address,
+ failingCalldata,
+ owner.address,
+ {
+ revertOnFailure: false,
+ expectExecutionSuccess: false,
+ },
+ );
+ });
+
+ it('should fail: when revertOnFailure is true and underlying call fails', async () => {
+ const { timelockManager, timelock, owner, accessControl } =
+ await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const failingCalldata = timelockManager.interface.encodeFunctionData(
+ 'setMaxPendingOperationsPerProposer',
+ [0],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [timelockManager.address],
+ [failingCalldata],
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ timelockManager.address,
+ failingCalldata,
+ owner.address,
+ {
+ revertOnFailure: true,
+ revertMessage: 'TimelockController: underlying transaction reverted',
+ },
+ );
+ });
+
+ it('when revertOnFailure is false and underlying call succeeds', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ owner.address,
+ {
+ revertOnFailure: false,
+ expectExecutionSuccess: true,
+ },
+ );
+ });
+
+ it('when revertOnFailure is true and underlying call succeeds', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ owner.address,
+ {
+ revertOnFailure: true,
+ expectExecutionSuccess: true,
+ },
+ );
+ });
+ });
+
+ describe('setSecurityCouncil()', () => {
+ it('should set security council', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setSecurityCouncilTest(
+ { timelockManager, timelock, owner, accessControl },
+ [...councilMembers.map((v) => v.address), accessControl.address],
+ );
+ });
+
+ it('should fail: when members < MIN_MEMBERS', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setSecurityCouncilTest(
+ { timelockManager, timelock, owner, accessControl },
+ [accessControl.address],
+ timelockManagerRevert(
+ timelockManager,
+ 'InvalidSecurityCouncilMembersLength',
+ ),
+ );
+ });
+
+ it('should fail: when members > MAX_MEMBERS', async () => {
+ const { timelockManager, timelock, owner, accessControl } =
+ await loadFixture(defaultDeploy);
+
+ await setSecurityCouncilTest(
+ { timelockManager, timelock, owner, accessControl },
+ [...Array(16).fill(accessControl.address)],
+ timelockManagerRevert(
+ timelockManager,
+ 'InvalidSecurityCouncilMembersLength',
+ ),
+ );
+ });
+
+ it('should fail: when council member address is zero', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setSecurityCouncilTest(
+ { timelockManager, timelock, owner, accessControl },
+ [
+ councilMembers[0].address,
+ councilMembers[1].address,
+ councilMembers[2].address,
+ councilMembers[3].address,
+ constants.AddressZero,
+ ],
+ timelockManagerRevert(timelockManager, 'InvalidAddress', [
+ constants.AddressZero,
+ ]),
+ );
+ });
+
+ it('should fail: when council member is duplicated', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setSecurityCouncilTest(
+ { timelockManager, timelock, owner, accessControl },
+ [
+ councilMembers[0].address,
+ councilMembers[0].address,
+ councilMembers[1].address,
+ councilMembers[2].address,
+ councilMembers[3].address,
+ ],
+ timelockManagerRevert(timelockManager, 'InvalidAddress', [
+ councilMembers[0].address,
+ ]),
+ );
+ });
+
+ it('should fail: when caller do not have SECURITY_COUNCIL_MANAGER_ROLE', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ councilMembers,
+ regularAccounts,
+ } = await loadFixture(defaultDeploy);
+
+ await setSecurityCouncilTest(
+ { timelockManager, timelock, owner, accessControl },
+ councilMembers.map((v) => v.address),
+ {
+ ...timelockManagerRevert(timelockManager, 'NoFunctionPermission'),
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: when pending set council operation exists', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ councilMembers,
+ regularAccounts,
+ } = await loadFixture(defaultDeploy);
+
+ const securityCouncilManagerRole =
+ await timelockManager.SECURITY_COUNCIL_MANAGER_ROLE();
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [securityCouncilManagerRole],
+ [3600],
+ );
+
+ const scheduledCouncilMembers = councilMembers.map((v) => v.address);
+ const setCouncilCalldata = timelockManager.interface.encodeFunctionData(
+ 'setSecurityCouncil',
+ [scheduledCouncilMembers],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [timelockManager.address],
+ [setCouncilCalldata],
+ { isSetCouncilOperation: true },
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [securityCouncilManagerRole],
+ [NO_DELAY],
+ );
+
+ await setSecurityCouncilTest(
+ { timelockManager, timelock, owner, accessControl },
+ [
+ councilMembers[0].address,
+ councilMembers[1].address,
+ councilMembers[2].address,
+ councilMembers[3].address,
+ regularAccounts[0].address,
+ ],
+ timelockManagerRevert(
+ timelockManager,
+ 'PendingSetCouncilOperationExists',
+ ),
+ );
+ });
+ });
+
+ describe('setMaxPendingOperationsPerProposer()', () => {
+ it('should set max pending operations per proposer', async () => {
+ const { timelockManager, timelock, owner, accessControl } =
+ await loadFixture(defaultDeploy);
+
+ await setMaxPendingOperationsPerProposerTester(
+ { timelockManager, owner, accessControl, timelock },
+ 100,
+ );
+ });
+
+ it('should fail: when max pending operations per proposer > MAX_OF_MAX_PENDING_OPERATIONS_PER_PROPOSER', async () => {
+ const { timelockManager, timelock, owner, accessControl } =
+ await loadFixture(defaultDeploy);
+
+ await setMaxPendingOperationsPerProposerTester(
+ { timelockManager, owner, accessControl, timelock },
+ 101,
+ timelockManagerRevert(
+ timelockManager,
+ 'InvalidMaxPendingOperationsPerProposer',
+ ),
+ );
+ });
+
+ it('should fail: when max pending operations per proposer is 0', async () => {
+ const { timelockManager, timelock, owner, accessControl } =
+ await loadFixture(defaultDeploy);
+
+ await setMaxPendingOperationsPerProposerTester(
+ { timelockManager, owner, accessControl, timelock },
+ 0,
+ timelockManagerRevert(
+ timelockManager,
+ 'InvalidMaxPendingOperationsPerProposer',
+ ),
+ );
+ });
+
+ it('should fail: when caller do not have DEFAULT_ADMIN_ROLE', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ regularAccounts,
+ } = await loadFixture(defaultDeploy);
+
+ await setMaxPendingOperationsPerProposerTester(
+ { timelockManager, owner, accessControl, timelock },
+ 50,
+ {
+ ...timelockManagerRevert(timelockManager, 'NoFunctionPermission'),
+ from: regularAccounts[0],
+ },
+ );
+ });
+ });
+
+ describe('pauseTimelockOperation()', () => {
+ it('should pause timelock operation', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+ });
+
+ it('should fail: when operation is not scheduled', async () => {
+ const { timelockManager, timelock, owner, accessControl } =
+ await loadFixture(defaultDeploy);
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ constants.HashZero,
+ undefined,
+ {
+ ...timelockManagerRevert(timelockManager, 'OperationNotPending'),
+ },
+ );
+ });
+
+ it('should fail: when operation is already executed', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ owner.address,
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ {
+ ...timelockManagerRevert(timelockManager, 'OperationNotPending'),
+ },
+ );
+ });
+
+ it('should fail: when operation was already paused', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+
+ undefined,
+ {
+ ...timelockManagerRevert(
+ timelockManager,
+ 'UnexpectedOperationStatus',
+ ),
+ },
+ );
+ });
+
+ it('should fail: when msg.sender do not have a pauser role', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ regularAccounts,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ {
+ ...timelockManagerRevert(timelockManager, 'NoFunctionPermission'),
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: when msg.sender do not have pauser role but do have default admin', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ regularAccounts,
+ } = await loadFixture(defaultDeploy);
+
+ await accessControl['grantRole(bytes32,address)'](
+ constants.HashZero,
+ regularAccounts[0].address,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ {
+ ...timelockManagerRevert(timelockManager, 'NoFunctionPermission'),
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: when operation is expired (45 days since scheduled)', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await increase(days(45));
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ {
+ ...timelockManagerRevert(
+ timelockManager,
+ 'UnexpectedOperationStatus',
+ ),
+ },
+ );
+ });
+
+ it('should fail: when voted for execution (3/5 council members) and status is ApprovedExecution', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await asyncForEach(
+ councilMembers.slice(0, 3),
+ async (member) => {
+ await voteForExecutionTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ from: member,
+ },
+ );
+ },
+ true,
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ {
+ ...timelockManagerRevert(
+ timelockManager,
+ 'UnexpectedOperationStatus',
+ ),
+ },
+ );
+ });
+
+ it('should fail: when voted for veto (3/5 council members) and status is ReadyToAbort', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await asyncForEach(
+ councilMembers.slice(0, 3),
+ async (member) => {
+ await voteForVetoTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ from: member,
+ },
+ );
+ },
+ true,
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ {
+ ...timelockManagerRevert(
+ timelockManager,
+ 'UnexpectedOperationStatus',
+ ),
+ },
+ );
+ });
+ });
+
+ describe('voteForVeto()', () => {
+ it('should vote for veto', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await voteForVetoTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ from: councilMembers[0],
+ },
+ );
+ });
+
+ it('should fail: when called by an address that is not in council', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ regularAccounts,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await voteForVetoTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ ...timelockManagerRevert(timelockManager, 'NotInSecurityCouncil'),
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: when address is part of current council but not the part of the council that was on the moment of pause', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ regularAccounts,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await setSecurityCouncilTest(
+ { timelockManager, timelock, owner, accessControl },
+ [
+ councilMembers[0].address,
+ councilMembers[1].address,
+ councilMembers[2].address,
+ councilMembers[3].address,
+ regularAccounts[0].address,
+ ],
+ );
+
+ await voteForVetoTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ ...timelockManagerRevert(timelockManager, 'NotInSecurityCouncil'),
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: when status is Pending', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await voteForVetoTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ ...timelockManagerRevert(
+ timelockManager,
+ 'UnexpectedOperationStatus',
+ ),
+ from: councilMembers[0],
+ },
+ );
+ });
+
+ it('should fail: when proposal do not exist', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await voteForVetoTest(
+ { timelockManager, timelock, owner, accessControl },
+ constants.HashZero,
+ {
+ ...timelockManagerRevert(
+ timelockManager,
+ 'UnexpectedOperationStatus',
+ ),
+ from: councilMembers[0],
+ },
+ );
+ });
+
+ it('should fail: when user already voted for veto', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await voteForVetoTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ from: councilMembers[0],
+ },
+ );
+
+ await voteForVetoTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ ...timelockManagerRevert(timelockManager, 'AlreadyVoted'),
+ from: councilMembers[0],
+ },
+ );
+ });
+
+ it('when 3/5 council is voted for veto and then call abortOperation', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await asyncForEach(
+ councilMembers.slice(0, 3),
+ async (member) => {
+ await voteForVetoTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ from: member,
+ },
+ );
+ },
+ true,
+ );
+
+ await abortOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ );
+ });
+
+ it('when 3/5 council is voted for execution, dispute period is not over, and then council 3/5 votes for veto and then call abortOperation', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await asyncForEach(
+ councilMembers.slice(0, 3),
+ async (member) => {
+ await voteForExecutionTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ from: member,
+ },
+ );
+ },
+ true,
+ );
+
+ await asyncForEach(
+ councilMembers.slice(0, 3),
+ async (member) => {
+ await voteForVetoTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ from: member,
+ },
+ );
+ },
+ true,
+ );
+
+ await abortOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ );
+ });
+
+ it('when 3/5 council is voted for execution, dispute period is over, and then council 3/5 votes for veto and then call abortOperation', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await asyncForEach(
+ councilMembers.slice(0, 3),
+ async (member) => {
+ await voteForExecutionTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ from: member,
+ },
+ );
+ },
+ true,
+ );
+
+ await increase(days(3));
+
+ await asyncForEach(
+ councilMembers.slice(0, 3),
+ async (member) => {
+ await voteForVetoTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ from: member,
+ },
+ );
+ },
+ true,
+ );
+
+ await abortOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ );
+ });
+
+ it('when 1/5 council member voted for veto and status remains Paused', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await voteForVetoTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ from: councilMembers[0],
+ },
+ );
+ });
+
+ it('when status is ApprovedExecution and council member votes for veto', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await asyncForEach(
+ councilMembers.slice(0, 3),
+ async (member) => {
+ await voteForExecutionTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ from: member,
+ },
+ );
+ },
+ true,
+ );
+
+ await voteForVetoTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ from: councilMembers[3],
+ },
+ );
+ });
+
+ it('should fail: when status is ReadyToAbort', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await asyncForEach(
+ councilMembers.slice(0, 3),
+ async (member) => {
+ await voteForVetoTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ from: member,
+ },
+ );
+ },
+ true,
+ );
+
+ await voteForVetoTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ ...timelockManagerRevert(
+ timelockManager,
+ 'UnexpectedOperationStatus',
+ [5],
+ ),
+ from: councilMembers[3],
+ },
+ );
+ });
+ });
+
+ describe('voteForExecution()', () => {
+ it('should vote for execution', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await voteForExecutionTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ from: councilMembers[0],
+ },
+ );
+ });
+
+ it('should fail: when called by an address that is not in council', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ regularAccounts,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await voteForExecutionTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ ...timelockManagerRevert(timelockManager, 'NotInSecurityCouncil'),
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: when address is part of current council but not the part of the council that was on the moment of pause', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ regularAccounts,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await setSecurityCouncilTest(
+ { timelockManager, timelock, owner, accessControl },
+ [
+ councilMembers[0].address,
+ councilMembers[1].address,
+ councilMembers[2].address,
+ councilMembers[3].address,
+ regularAccounts[0].address,
+ ],
+ );
+
+ await voteForExecutionTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ ...timelockManagerRevert(timelockManager, 'NotInSecurityCouncil'),
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: when status is Pending', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await voteForExecutionTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ ...timelockManagerRevert(
+ timelockManager,
+ 'UnexpectedOperationStatus',
+ ),
+ from: councilMembers[0],
+ },
+ );
+ });
+
+ it('should fail: when proposal do not exist', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await voteForExecutionTest(
+ { timelockManager, timelock, owner, accessControl },
+ constants.HashZero,
+ {
+ ...timelockManagerRevert(
+ timelockManager,
+ 'UnexpectedOperationStatus',
+ ),
+ from: councilMembers[0],
+ },
+ );
+ });
+
+ it('should fail: when proposal already executed', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ owner.address,
+ );
+
+ await voteForExecutionTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ ...timelockManagerRevert(
+ timelockManager,
+ 'UnexpectedOperationStatus',
+ ),
+ from: councilMembers[0],
+ },
+ );
+ });
+
+ it('should fail: when user already voted for veto', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await voteForVetoTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ from: councilMembers[0],
+ },
+ );
+
+ await voteForExecutionTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ ...timelockManagerRevert(timelockManager, 'AlreadyVoted'),
+ from: councilMembers[0],
+ },
+ );
+ });
+
+ it('should fail: when user already voted for execution', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await voteForExecutionTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ from: councilMembers[0],
+ },
+ );
+
+ await voteForExecutionTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ ...timelockManagerRevert(timelockManager, 'AlreadyVoted'),
+ from: councilMembers[0],
+ },
+ );
+ });
+
+ it('should fail: when 3/5 council is voted for execution and then call execute as 3 days dispute didnt pass', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await asyncForEach(
+ councilMembers.slice(0, 3),
+ async (member) => {
+ await voteForExecutionTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+
+ {
+ from: member,
+ },
+ );
+ },
+ true,
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ owner.address,
+ {
+ ...timelockManagerRevert(
+ timelockManager,
+ 'UnexpectedOperationStatus',
+ ),
+ },
+ );
+ });
+
+ it('when 3/5 council is voted for execution, wait 3 days and then call execute', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await asyncForEach(
+ councilMembers.slice(0, 3),
+ async (member) => {
+ await voteForExecutionTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ from: member,
+ },
+ );
+ },
+ true,
+ );
+
+ await increase(3600);
+ await increase(days(3));
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ owner.address,
+ );
+ });
+
+ it('should fail: when 3/5 council is voted for execution, dispute period is over, and then council 3/5 votes for veto and then call execute operation', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await asyncForEach(
+ councilMembers.slice(0, 3),
+ async (member) => {
+ await voteForExecutionTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ from: member,
+ },
+ );
+ },
+ true,
+ );
+
+ await increase(3600);
+ await increase(days(3));
+
+ await asyncForEach(
+ councilMembers.slice(0, 3),
+ async (member) => {
+ await voteForVetoTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ from: member,
+ },
+ );
+ },
+ true,
+ );
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ owner.address,
+ {
+ ...timelockManagerRevert(
+ timelockManager,
+ 'UnexpectedOperationStatus',
+ ),
+ },
+ );
+ });
+
+ it('when 1/5 council member voted for execution and status remains Paused', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await voteForExecutionTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ from: councilMembers[0],
+ },
+ );
+ });
+
+ it('should fail: when operation is expired (45 days since scheduled)', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await increase(days(45));
+
+ await voteForExecutionTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ ...timelockManagerRevert(
+ timelockManager,
+ 'UnexpectedOperationStatus',
+ [6],
+ ),
+ from: councilMembers[0],
+ },
+ );
+ });
+ });
+
+ describe('abortOperation()', () => {
+ it('when operation is expired (45 days since scheduled) and call abortOperation', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await increase(days(45));
+
+ await abortOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ );
+ });
+
+ it('should fail: when status is Paused', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await abortOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ timelockManagerRevert(timelockManager, 'UnexpectedOperationStatus', [
+ 2,
+ ]),
+ );
+ });
+
+ it('should fail: when status is Pending', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await abortOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ timelockManagerRevert(timelockManager, 'UnexpectedOperationStatus', [
+ 1,
+ ]),
+ );
+ });
+
+ it('should fail: when status is ApprovedExecution', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await asyncForEach(
+ councilMembers.slice(0, 3),
+ async (member) => {
+ await voteForExecutionTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ from: member,
+ },
+ );
+ },
+ true,
+ );
+
+ await abortOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ timelockManagerRevert(timelockManager, 'UnexpectedOperationStatus', [
+ 3,
+ ]),
+ );
+ });
+ });
+
+ describe('getOriginalProposer()', () => {
+ it('should return proposer for scheduled operation', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ );
+
+ expect(
+ await timelockManager.getOriginalProposer(
+ wAccessControlTester.address,
+ calldata,
+ ),
+ ).to.eq(owner.address);
+ });
+ });
+
+ describe('pendingSetCouncilOperationId()', () => {
+ it('should fail: when pending set council operation already exists', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ const securityCouncilManagerRole =
+ await timelockManager.SECURITY_COUNCIL_MANAGER_ROLE();
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [securityCouncilManagerRole],
+ [3600],
+ );
+
+ const setCouncilCalldata = timelockManager.interface.encodeFunctionData(
+ 'setSecurityCouncil',
+ [councilMembers.map((v) => v.address)],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [timelockManager.address],
+ [setCouncilCalldata],
+ { isSetCouncilOperation: true },
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [timelockManager.address],
+ [setCouncilCalldata],
+ { isSetCouncilOperation: true },
+ timelockManagerRevert(
+ timelockManager,
+ 'PendingSetCouncilOperationExists',
+ ),
+ );
+ });
+ });
+
+ describe('councilQuorum()', () => {
+ it('should return quorum for initial security council version', async () => {
+ const { timelockManager } = await loadFixture(defaultDeploy);
+
+ expect(await timelockManager.councilQuorum(0)).to.eq(3);
+ });
+
+ it('should return quorum for updated security council version', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setSecurityCouncilTest(
+ { timelockManager, timelock, owner, accessControl },
+ [...councilMembers.map((v) => v.address), accessControl.address],
+ );
+
+ expect(await timelockManager.securityCouncilVersion()).to.eq(1);
+ expect(await timelockManager.councilQuorum(1)).to.eq(4);
+ });
+ });
+
+ describe('getSecurityCouncilMembers()', () => {
+ it('should return members for initial security council version', async () => {
+ const { timelockManager, councilMembers } = await loadFixture(
+ defaultDeploy,
+ );
+
+ const members = await timelockManager.getSecurityCouncilMembers(0);
+
+ expect(members.length).to.eq(councilMembers.length);
+ for (const member of councilMembers) {
+ expect(members.includes(member.address)).to.eq(true);
+ }
+ });
+
+ it('should return members for updated security council version', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ const newMembers = [
+ ...councilMembers.map((v) => v.address),
+ accessControl.address,
+ ];
+
+ await setSecurityCouncilTest(
+ { timelockManager, timelock, owner, accessControl },
+ newMembers,
+ );
+
+ const members = await timelockManager.getSecurityCouncilMembers(1);
+
+ expect(members.length).to.eq(newMembers.length);
+ for (const member of newMembers) {
+ expect(members.includes(member)).to.eq(true);
+ }
+ });
+ });
+
+ describe('getPendingOperations()', () => {
+ it('should return empty list when no operations are scheduled', async () => {
+ const { timelockManager } = await loadFixture(defaultDeploy);
+
+ const pending = await timelockManager.getPendingOperations();
+
+ expect(pending.length).to.eq(0);
+ });
+
+ it('should return scheduled operation ids', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ const pending = await timelockManager.getPendingOperations();
+
+ expect(pending.length).to.eq(1);
+ expect(pending[0]).to.eq(operationId);
+ });
+
+ it('should not return executed operation ids', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ owner.address,
+ );
+
+ const pending = await timelockManager.getPendingOperations();
+
+ expect(pending.length).to.eq(0);
+ });
+ });
+
+ describe('getOperationId()', () => {
+ it('should return operation id for scheduled operation', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ );
+
+ expect(
+ await timelockManager.getOperationId(
+ wAccessControlTester.address,
+ calldata,
+ ),
+ ).to.eq(operationId);
+ });
+ });
+
+ describe('getOperationStatus()', () => {
+ it('should return NotExist for unknown operation', async () => {
+ const { timelockManager } = await loadFixture(defaultDeploy);
+
+ expect(
+ await timelockManager.getOperationStatus(constants.HashZero),
+ ).to.eq(0);
+ });
+
+ it('should return Pending for scheduled operation', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ expect(await timelockManager.getOperationStatus(operationId)).to.eq(1);
+ });
+
+ it('should return Paused for paused operation', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ expect(await timelockManager.getOperationStatus(operationId)).to.eq(2);
+ });
+
+ it('should return ReadyToExecute when dispute period passed', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await asyncForEach(
+ councilMembers.slice(0, 3),
+ async (member) => {
+ await voteForExecutionTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ from: member,
+ },
+ );
+ },
+ true,
+ );
+
+ await increase(days(3));
+
+ expect(await timelockManager.getOperationStatus(operationId)).to.eq(4);
+ });
+
+ it('should return Expired when expiry period passed', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await increase(days(45));
+
+ expect(await timelockManager.getOperationStatus(operationId)).to.eq(6);
+ });
+ });
+
+ describe('getOperationStatusRaw()', () => {
+ it('should return NotExist for unknown operation', async () => {
+ const { timelockManager } = await loadFixture(defaultDeploy);
+
+ expect(
+ await timelockManager.getOperationStatusRaw(constants.HashZero),
+ ).to.eq(0);
+ });
+
+ it('should return stored status without expiry adjustment', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await increase(days(45));
+
+ expect(await timelockManager.getOperationStatus(operationId)).to.eq(6);
+ expect(await timelockManager.getOperationStatusRaw(operationId)).to.eq(1);
+ });
+
+ it('should return ApprovedExecution after council execution quorum', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await asyncForEach(
+ councilMembers.slice(0, 3),
+ async (member) => {
+ await voteForExecutionTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ from: member,
+ },
+ );
+ },
+ true,
+ );
+
+ await increase(days(3));
+
+ expect(await timelockManager.getOperationStatus(operationId)).to.eq(4);
+ expect(await timelockManager.getOperationStatusRaw(operationId)).to.eq(3);
+ });
+ });
+
+ describe('getOperationDetails()', () => {
+ it('should return empty details for unknown operation', async () => {
+ const { timelockManager } = await loadFixture(defaultDeploy);
+
+ const details = await timelockManager.getOperationDetails(
+ constants.HashZero,
+ );
+
+ expect(details.status).to.eq(0);
+ expect(details.operationProposer).to.eq(constants.AddressZero);
+ expect(details.pauser).to.eq(constants.AddressZero);
+ expect(details.votesForExecution).to.eq(0);
+ expect(details.votesForVeto).to.eq(0);
+ });
+
+ it('should return details for paused operation', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ 7,
+ );
+
+ const details = await timelockManager.getOperationDetails(operationId);
+
+ expect(details.status).to.eq(2);
+ expect(details.operationProposer).to.eq(owner.address);
+ expect(details.pauser).to.eq(owner.address);
+ expect(details.pauseReasonCode).to.eq(7);
+ expect(details.votesForExecution).to.eq(0);
+ expect(details.votesForVeto).to.eq(0);
+ });
+
+ it('should return vote counts after council votes', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await voteForExecutionTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ from: councilMembers[0],
+ },
+ );
+
+ await voteForVetoTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ from: councilMembers[1],
+ },
+ );
+
+ const details = await timelockManager.getOperationDetails(operationId);
+
+ expect(details.status).to.eq(2);
+ expect(details.votesForExecution).to.eq(1);
+ expect(details.votesForVeto).to.eq(1);
+ });
+ });
+
+ describe('getCouncilMemberVoteStatus()', () => {
+ it('should return false when member did not vote', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ const [votedForExecution, votedForVeto] =
+ await timelockManager.getCouncilMemberVoteStatus(
+ operationId,
+ councilMembers[0].address,
+ );
+
+ expect(votedForExecution).to.eq(false);
+ expect(votedForVeto).to.eq(false);
+ });
+
+ it('should return vote flags after member voted', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ councilMembers,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const [operationId] = await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ await pauseTimelockOperationTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ undefined,
+ );
+
+ await voteForExecutionTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ from: councilMembers[0],
+ },
+ );
+
+ await voteForVetoTest(
+ { timelockManager, timelock, owner, accessControl },
+ operationId,
+ {
+ from: councilMembers[1],
+ },
+ );
+
+ const [member0Execution, member0Veto] =
+ await timelockManager.getCouncilMemberVoteStatus(
+ operationId,
+ councilMembers[0].address,
+ );
+ const [member1Execution, member1Veto] =
+ await timelockManager.getCouncilMemberVoteStatus(
+ operationId,
+ councilMembers[1].address,
+ );
+
+ expect(member0Execution).to.eq(true);
+ expect(member0Veto).to.eq(false);
+ expect(member1Execution).to.eq(false);
+ expect(member1Veto).to.eq(true);
+ });
+ });
+
+ describe('proposerPendingOperationsCount()', () => {
+ it('should return pending operations count per proposer', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ expect(
+ await timelockManager.proposerPendingOperationsCount(owner.address),
+ ).to.eq(0);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [
+ wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ ),
+ ],
+ );
+
+ expect(
+ await timelockManager.proposerPendingOperationsCount(owner.address),
+ ).to.eq(1);
+ });
+
+ it('should decrement count when operation is executed', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ );
+
+ expect(
+ await timelockManager.proposerPendingOperationsCount(owner.address),
+ ).to.eq(1);
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ wAccessControlTester.address,
+ calldata,
+ owner.address,
+ );
+
+ expect(
+ await timelockManager.proposerPendingOperationsCount(owner.address),
+ ).to.eq(0);
+ });
+ });
+
+ describe('dataHashIndexes()', () => {
+ it('should return data hash index for scheduled operation', async () => {
+ const {
+ timelockManager,
+ timelock,
+ owner,
+ accessControl,
+ wAccessControlTester,
+ } = await loadFixture(defaultDeploy);
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [constants.HashZero],
+ [3600],
+ );
+
+ const calldata = wAccessControlTester.interface.encodeFunctionData(
+ 'withOnlyContractAdmin',
+ );
+ const dataWithCaller = ethers.utils.solidityPack(
+ ['bytes', 'address'],
+ [calldata, owner.address],
+ );
+ const dataHash = ethers.utils.solidityKeccak256(
+ ['address', 'uint256', 'bytes'],
+ [wAccessControlTester.address, 0, dataWithCaller],
+ );
+
+ expect(await timelockManager.dataHashIndexes(dataHash)).to.eq(0);
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [wAccessControlTester.address],
+ [calldata],
+ );
+
+ expect(await timelockManager.dataHashIndexes(dataHash)).to.eq(0);
+ });
+ });
+});
diff --git a/test/unit/Pausable.test.ts b/test/unit/Pausable.test.ts
index 5ea0a089..e000c247 100644
--- a/test/unit/Pausable.test.ts
+++ b/test/unit/Pausable.test.ts
@@ -2,186 +2,227 @@ import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
import { expect } from 'chai';
import { encodeFnSelector } from '../../helpers/utils';
-import { PausableTester__factory } from '../../typechain-types';
+import { acErrors } from '../common/ac.helpers';
import {
pauseVault,
pauseVaultFn,
- unpauseVault,
- unpauseVaultFn,
+ pauseGlobalTest,
} from '../common/common.helpers';
import { defaultDeploy } from '../common/fixtures';
describe('Pausable', () => {
it('deployment', async () => {
- const { pausableTester, roles } = await loadFixture(defaultDeploy);
+ const { pausableTester, roles, pauseManager } = await loadFixture(
+ defaultDeploy,
+ );
- expect(await pausableTester.pauseAdminRole()).eq(roles.common.defaultAdmin);
+ expect(await pausableTester.contractAdminRole()).eq(
+ roles.common.defaultAdmin,
+ );
- expect(await pausableTester.paused()).eq(false);
- });
-
- it('onlyInitializing', async () => {
- const { accessControl, owner } = await loadFixture(defaultDeploy);
-
- const pausable = await new PausableTester__factory(owner).deploy();
-
- await expect(
- pausable.initializeWithoutInitializer(accessControl.address),
- ).revertedWith('Initializable: contract is not initializing');
+ expect(
+ await pauseManager.isPaused(pausableTester.address, '0x00000000'),
+ ).eq(false);
});
describe('onlyPauseAdmin modifier', async () => {
it('should fail: can`t pause if doesn`t have role', async () => {
- const { pausableTester, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
+ const { pausableTester, regularAccounts, pauseManager, owner } =
+ await loadFixture(defaultDeploy);
- await pauseVault(pausableTester, {
+ await pauseVault({ pauseManager, owner }, pausableTester, {
from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
+ revertCustomError: {
+ ...acErrors.WMAC_HASNT_PERMISSION(),
+ contract: pauseManager,
+ },
});
});
it('can change state if has role', async () => {
- const { pausableTester } = await loadFixture(defaultDeploy);
+ const { pausableTester, pauseManager, owner } = await loadFixture(
+ defaultDeploy,
+ );
- await pauseVault(pausableTester);
+ await pauseVault({ pauseManager, owner }, pausableTester);
});
});
- describe('pause()', async () => {
- it('fail: can`t pause if caller doesnt have admin role', async () => {
- const { pausableTester, regularAccounts } = await loadFixture(
- defaultDeploy,
+ describe('_requireFnNotPaused()', () => {
+ it('should not fail when fn is not paused', async () => {
+ const { pausableTester } = await loadFixture(defaultDeploy);
+ const selector = encodeFnSelector(
+ 'depositRequest(address,uint256,bytes32)',
);
- await pauseVault(pausableTester, {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- });
+ await expect(pausableTester.requireFnNotPaused(selector)).not.reverted;
});
- it('fail: when paused', async () => {
+ it('should not fail when contract is not paused', async () => {
const { pausableTester } = await loadFixture(defaultDeploy);
- await pauseVault(pausableTester);
- await pauseVault(pausableTester, {
- revertMessage: 'Pausable: paused',
- });
+ await expect(
+ pausableTester.requireFnNotPaused(encodeFnSelector('randomSelector()')),
+ ).not.reverted;
});
- it('when not paused and caller is admin', async () => {
- const { pausableTester } = await loadFixture(defaultDeploy);
+ it('should not fail when globally is not paused', async () => {
+ const { pausableTester, pauseManager, owner } = await loadFixture(
+ defaultDeploy,
+ );
- await pauseVault(pausableTester);
+ await expect(
+ pausableTester.requireFnNotPaused(encodeFnSelector('randomSelector()')),
+ ).not.reverted;
});
- });
- describe('pauseFn()', async () => {
- it('fail: can`t pause if caller doesnt have admin role', async () => {
- const { pausableTester, regularAccounts } = await loadFixture(
+ it('should fail: when fn is paused', async () => {
+ const { pausableTester, pauseManager, owner } = await loadFixture(
defaultDeploy,
);
-
const selector = encodeFnSelector(
'depositRequest(address,uint256,bytes32)',
);
- await pauseVaultFn(pausableTester, selector, {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- });
- });
+ await pauseVaultFn({ pauseManager, owner }, pausableTester, selector);
- it('fail: when paused', async () => {
- const { pausableTester } = await loadFixture(defaultDeploy);
+ await expect(pausableTester.requireFnNotPaused(selector))
+ .revertedWithCustomError(pausableTester, 'Paused')
+ .withArgs(pausableTester.address, selector);
+ });
+ it('should not fail when contract is paused', async () => {
+ const { pausableTester, pauseManager, owner } = await loadFixture(
+ defaultDeploy,
+ );
const selector = encodeFnSelector(
'depositRequest(address,uint256,bytes32)',
);
- await pauseVaultFn(pausableTester, selector);
- await pauseVaultFn(pausableTester, selector, {
- revertMessage: 'Pausable: fn paused',
- });
- });
+ await pauseVault({ pauseManager, owner }, pausableTester);
- it('when not paused and caller is admin', async () => {
- const { pausableTester } = await loadFixture(defaultDeploy);
+ await expect(pausableTester.requireFnNotPaused(selector)).not.reverted;
+ });
+ it('should not fail when globally is paused', async () => {
+ const { pausableTester, pauseManager, owner } = await loadFixture(
+ defaultDeploy,
+ );
const selector = encodeFnSelector(
'depositRequest(address,uint256,bytes32)',
);
- await pauseVaultFn(pausableTester, selector);
+ await pauseGlobalTest({ pauseManager, owner });
+
+ await expect(pausableTester.requireFnNotPaused(selector)).not.reverted;
});
- });
- describe('unpauseFn()', async () => {
- it('fail: can`t pause if caller doesnt have admin role', async () => {
- const { pausableTester, regularAccounts } = await loadFixture(
+ it('should fail: when globally, contract and fn are paused', async () => {
+ const { pausableTester, pauseManager, owner } = await loadFixture(
defaultDeploy,
);
-
const selector = encodeFnSelector(
'depositRequest(address,uint256,bytes32)',
);
- await unpauseVaultFn(pausableTester, selector, {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- });
+ await pauseGlobalTest({ pauseManager, owner });
+ await pauseVault({ pauseManager, owner }, pausableTester);
+ await pauseVaultFn({ pauseManager, owner }, pausableTester, selector);
+
+ await expect(pausableTester.requireFnNotPaused(selector))
+ .revertedWithCustomError(pausableTester, 'Paused')
+ .withArgs(pausableTester.address, selector);
});
+ });
- it('fail: when unpaused', async () => {
+ describe('_requireNotPaused()', () => {
+ it('should not fail when fn is not paused', async () => {
const { pausableTester } = await loadFixture(defaultDeploy);
-
const selector = encodeFnSelector(
- 'function depositRequest(address,uint256,bytes32)',
+ 'depositRequest(address,uint256,bytes32)',
);
- await unpauseVaultFn(pausableTester, selector, {
- revertMessage: 'Pausable: fn unpaused',
- });
+ await expect(pausableTester.requireNotPaused(selector)).not.reverted;
});
- it('when paused and caller is admin', async () => {
+ it('should not fail when contract is not paused', async () => {
const { pausableTester } = await loadFixture(defaultDeploy);
+ await expect(
+ pausableTester.requireNotPaused(encodeFnSelector('randomSelector()')),
+ ).not.reverted;
+ });
+
+ it('should not fail when globally is not paused', async () => {
+ const { pausableTester, pauseManager, owner } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await expect(
+ pausableTester.requireNotPaused(encodeFnSelector('randomSelector()')),
+ ).not.reverted;
+ });
+
+ it('should fail: when fn is paused', async () => {
+ const { pausableTester, pauseManager, owner } = await loadFixture(
+ defaultDeploy,
+ );
const selector = encodeFnSelector(
'depositRequest(address,uint256,bytes32)',
);
- await pauseVaultFn(pausableTester, selector);
- await unpauseVaultFn(pausableTester, selector);
+ await pauseVaultFn({ pauseManager, owner }, pausableTester, selector);
+
+ await expect(pausableTester.requireNotPaused(selector))
+ .revertedWithCustomError(pausableTester, 'Paused')
+ .withArgs(pausableTester.address, selector);
});
- });
- describe('unpause()', async () => {
- it('fail: can`t unpause if caller doesnt have admin role', async () => {
- const { pausableTester, regularAccounts } = await loadFixture(
+ it('should fail: when contract is paused', async () => {
+ const { pausableTester, pauseManager, owner } = await loadFixture(
defaultDeploy,
);
+ const selector = encodeFnSelector(
+ 'depositRequest(address,uint256,bytes32)',
+ );
- await unpauseVault(pausableTester, {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- });
+ await pauseVault({ pauseManager, owner }, pausableTester);
+
+ await expect(
+ pausableTester.requireNotPaused(selector),
+ ).revertedWithCustomError(pausableTester, 'Paused');
});
- it('fail: when not paused', async () => {
- const { pausableTester } = await loadFixture(defaultDeploy);
+ it('should fail: when globally is paused', async () => {
+ const { pausableTester, pauseManager, owner } = await loadFixture(
+ defaultDeploy,
+ );
+ const selector = encodeFnSelector(
+ 'depositRequest(address,uint256,bytes32)',
+ );
- await unpauseVault(pausableTester, {
- revertMessage: 'Pausable: not paused',
- });
+ await pauseGlobalTest({ pauseManager, owner });
+
+ await expect(
+ pausableTester.requireNotPaused(selector),
+ ).revertedWithCustomError(pausableTester, 'Paused');
});
- it('when paused and caller is admin', async () => {
- const { pausableTester } = await loadFixture(defaultDeploy);
+ it('should fail: when globally, contract and fn are paused', async () => {
+ const { pausableTester, pauseManager, owner } = await loadFixture(
+ defaultDeploy,
+ );
+ const selector = encodeFnSelector(
+ 'depositRequest(address,uint256,bytes32)',
+ );
+
+ await pauseGlobalTest({ pauseManager, owner });
+ await pauseVault({ pauseManager, owner }, pausableTester);
+ await pauseVaultFn({ pauseManager, owner }, pausableTester, selector);
- await pauseVault(pausableTester);
- await unpauseVault(pausableTester);
+ await expect(pausableTester.requireNotPaused(selector))
+ .revertedWithCustomError(pausableTester, 'Paused')
+ .withArgs(pausableTester.address, selector);
});
});
});
diff --git a/test/unit/RateLimitLibrary.test.ts b/test/unit/RateLimitLibrary.test.ts
new file mode 100644
index 00000000..83d59063
--- /dev/null
+++ b/test/unit/RateLimitLibrary.test.ts
@@ -0,0 +1,614 @@
+import { loadFixture, time } from '@nomicfoundation/hardhat-network-helpers';
+import {
+ days,
+ hours,
+ minutes,
+} from '@nomicfoundation/hardhat-network-helpers/dist/src/helpers/time/duration';
+import { expect } from 'chai';
+import { BigNumberish } from 'ethers';
+import { parseUnits } from 'ethers/lib/utils';
+import { ethers } from 'hardhat';
+
+import {
+ RateLimitLibraryTester,
+ RateLimitLibraryTester__factory,
+} from '../../typechain-types';
+import { getCurrentBlockTimestamp } from '../common/common.helpers';
+import {
+ calculateWindowRateLimitCapacity,
+ mulDiv,
+} from '../common/manageable-vault.helpers';
+
+type WindowStatus = Awaited<
+ ReturnType
+>[number];
+
+describe('RateLimitLibrary', function () {
+ const WINDOW_1D = days(1);
+ const WINDOW_2D = days(2);
+ const MIN_WINDOW = 60;
+
+ const rateLimitLibraryFixture = async () => {
+ const [signer] = await ethers.getSigners();
+ const tester = await new RateLimitLibraryTester__factory(signer).deploy();
+ return { tester, signer };
+ };
+
+ const getStatusByWindow = (
+ statuses: WindowStatus[],
+ window: BigNumberish,
+ ): WindowStatus | undefined => statuses.find((s) => s.window.eq(window));
+
+ const findOneWeiMulDivSplitGap = (
+ limit: BigNumberish,
+ window: number,
+ ): { leftElapsed: number; rightElapsed: number } => {
+ for (let leftElapsed = 1; leftElapsed < window; leftElapsed++) {
+ for (
+ let rightElapsed = 1;
+ leftElapsed + rightElapsed < window;
+ rightElapsed++
+ ) {
+ const singleDecay = mulDiv(limit, leftElapsed + rightElapsed, window);
+ const splitDecay = mulDiv(limit, leftElapsed, window).add(
+ mulDiv(limit, rightElapsed, window),
+ );
+ if (singleDecay.sub(splitDecay).eq(1)) {
+ return { leftElapsed, rightElapsed };
+ }
+ }
+ }
+
+ throw new Error('Failed to find 1 wei mulDiv split gap');
+ };
+
+ const expectStatusMatchesCapacity = async (
+ tester: RateLimitLibraryTester,
+ window: BigNumberish,
+ storedAmountInFlight: BigNumberish,
+ limit: BigNumberish,
+ windowDuration: BigNumberish,
+ lastUpdated: BigNumberish,
+ ) => {
+ const now = await getCurrentBlockTimestamp();
+ const { inFlight, remaining } = calculateWindowRateLimitCapacity({
+ amountInFlight: storedAmountInFlight,
+ lastUpdated,
+ limit,
+ window: windowDuration,
+ now,
+ });
+
+ const statuses = await tester.getWindowStatusesPublic();
+ const status = getStatusByWindow(statuses, window);
+
+ expect(status).not.eq(undefined);
+ expect(status!.inFlight).eq(inFlight);
+ expect(status!.remaining).eq(remaining);
+ expect(status!.limit).eq(limit);
+ expect(status!.window).eq(windowDuration);
+ expect(status!.lastUpdated).eq(lastUpdated);
+ };
+
+ describe('setWindowLimit()', () => {
+ it('should add a new window, emit WindowLimitSet and return previousLimit 0', async () => {
+ const { tester } = await loadFixture(rateLimitLibraryFixture);
+ const limit = parseUnits('1000');
+
+ await expect(tester.setWindowLimitPublic(WINDOW_1D, limit))
+ .to.emit(tester, 'WindowLimitSet')
+ .withArgs(WINDOW_1D, limit);
+
+ expect(await tester.windowCountPublic()).eq(1);
+ expect(await tester.hasWindowPublic(WINDOW_1D)).eq(true);
+ });
+
+ it('should initialize storage with full remaining capacity', async () => {
+ const { tester } = await loadFixture(rateLimitLibraryFixture);
+ const limit = parseUnits('1000');
+
+ await tester.setWindowLimitPublic(WINDOW_1D, limit);
+
+ const [storedLimit, amountInFlight, lastUpdated, windowDuration] =
+ await tester.getWindowConfigPublic(WINDOW_1D);
+
+ expect(storedLimit).eq(limit);
+ expect(amountInFlight).eq(0);
+ expect(windowDuration).eq(WINDOW_1D);
+ expect(lastUpdated).eq(await getCurrentBlockTimestamp());
+
+ const statuses = await tester.getWindowStatusesPublic();
+ const status = getStatusByWindow(statuses, WINDOW_1D);
+
+ expect(status!.inFlight).eq(0);
+ expect(status!.remaining).eq(limit);
+ });
+
+ it('should allow limit 0', async () => {
+ const { tester } = await loadFixture(rateLimitLibraryFixture);
+
+ await tester.setWindowLimitPublic(WINDOW_1D, 0);
+
+ const statuses = await tester.getWindowStatusesPublic();
+ expect(getStatusByWindow(statuses, WINDOW_1D)!.remaining).eq(0);
+ });
+
+ it('should fail: window shorter than 1 minute', async () => {
+ const { tester } = await loadFixture(rateLimitLibraryFixture);
+
+ await expect(tester.setWindowLimitPublic(MIN_WINDOW - 1, 1))
+ .to.be.revertedWithCustomError(tester, 'WindowTooShort')
+ .withArgs(MIN_WINDOW - 1);
+ });
+
+ it('should allow window exactly 1 minute', async () => {
+ const { tester } = await loadFixture(rateLimitLibraryFixture);
+
+ await expect(tester.setWindowLimitPublic(MIN_WINDOW, 1)).not.reverted;
+ expect(await tester.hasWindowPublic(MIN_WINDOW)).eq(true);
+ });
+
+ it('should return previous limit when updating an existing window', async () => {
+ const { tester } = await loadFixture(rateLimitLibraryFixture);
+ const initialLimit = parseUnits('1000');
+ const newLimit = parseUnits('500');
+
+ await tester.setWindowLimitPublic(WINDOW_1D, initialLimit);
+
+ await expect(tester.setWindowLimitPublic(WINDOW_1D, newLimit))
+ .to.emit(tester, 'WindowLimitSet')
+ .withArgs(WINDOW_1D, newLimit);
+
+ const previousLimit = await tester.callStatic.setWindowLimitPublic(
+ WINDOW_1D,
+ newLimit,
+ );
+ expect(previousLimit).eq(newLimit);
+
+ const [, , , windowDuration] = await tester.getWindowConfigPublic(
+ WINDOW_1D,
+ );
+ expect(windowDuration).eq(WINDOW_1D);
+
+ const statuses = await tester.getWindowStatusesPublic();
+ expect(getStatusByWindow(statuses, WINDOW_1D)!.limit).eq(newLimit);
+ });
+
+ it('should checkpoint stored in-flight using the new limit on update', async () => {
+ const { tester } = await loadFixture(rateLimitLibraryFixture);
+ const initialLimit = parseUnits('1000');
+ const newLimit = parseUnits('500');
+ const consumed = parseUnits('800');
+
+ await tester.setWindowLimitPublic(WINDOW_1D, initialLimit);
+ await tester.consumeLimitPublic(consumed);
+
+ const [, , lastUpdatedAfterConsume] = await tester.getWindowConfigPublic(
+ WINDOW_1D,
+ );
+ await time.increase(hours(12));
+
+ await tester.setWindowLimitPublic(WINDOW_1D, newLimit);
+
+ const [, amountInFlight, lastUpdatedAfterSet] =
+ await tester.getWindowConfigPublic(WINDOW_1D);
+
+ const { inFlight: expectedStored } = calculateWindowRateLimitCapacity({
+ amountInFlight: consumed,
+ lastUpdated: lastUpdatedAfterConsume,
+ limit: newLimit,
+ window: WINDOW_1D,
+ now: lastUpdatedAfterSet,
+ });
+
+ expect(amountInFlight).eq(expectedStored);
+ expect(lastUpdatedAfterSet).eq(await getCurrentBlockTimestamp());
+
+ const statuses = await tester.getWindowStatusesPublic();
+ const status = getStatusByWindow(statuses, WINDOW_1D);
+
+ expect(status!.limit).eq(newLimit);
+ expect(status!.inFlight).eq(expectedStored);
+ expect(status!.remaining).eq(
+ newLimit.lte(expectedStored) ? 0 : newLimit.sub(expectedStored),
+ );
+ });
+
+ it('should support multiple independent windows', async () => {
+ const { tester } = await loadFixture(rateLimitLibraryFixture);
+
+ await tester.setWindowLimitPublic(WINDOW_1D, parseUnits('1000'));
+ await tester.setWindowLimitPublic(WINDOW_2D, parseUnits('2000'));
+
+ expect(await tester.windowCountPublic()).eq(2);
+
+ const statuses = await tester.getWindowStatusesPublic();
+ expect(statuses.length).eq(2);
+ expect(getStatusByWindow(statuses, WINDOW_1D)!.limit).eq(
+ parseUnits('1000'),
+ );
+ expect(getStatusByWindow(statuses, WINDOW_2D)!.limit).eq(
+ parseUnits('2000'),
+ );
+ });
+ });
+
+ describe('removeWindowLimit()', () => {
+ it('should remove an existing window and emit WindowLimitRemoved', async () => {
+ const { tester } = await loadFixture(rateLimitLibraryFixture);
+
+ await tester.setWindowLimitPublic(WINDOW_1D, parseUnits('1000'));
+
+ await expect(tester.removeWindowLimitPublic(WINDOW_1D))
+ .to.emit(tester, 'WindowLimitRemoved')
+ .withArgs(WINDOW_1D);
+
+ expect(await tester.windowCountPublic()).eq(0);
+ expect(await tester.hasWindowPublic(WINDOW_1D)).eq(false);
+ expect((await tester.getWindowStatusesPublic()).length).eq(0);
+ });
+
+ it('should fail: unknown window', async () => {
+ const { tester } = await loadFixture(rateLimitLibraryFixture);
+
+ await expect(tester.removeWindowLimitPublic(WINDOW_1D))
+ .to.be.revertedWithCustomError(tester, 'UnknownWindowLimit')
+ .withArgs(WINDOW_1D);
+ });
+
+ it('should fail: removing the same window twice', async () => {
+ const { tester } = await loadFixture(rateLimitLibraryFixture);
+
+ await tester.setWindowLimitPublic(WINDOW_1D, parseUnits('1000'));
+ await tester.removeWindowLimitPublic(WINDOW_1D);
+
+ await expect(tester.removeWindowLimitPublic(WINDOW_1D))
+ .to.be.revertedWithCustomError(tester, 'UnknownWindowLimit')
+ .withArgs(WINDOW_1D);
+ });
+
+ it('should not affect other windows', async () => {
+ const { tester } = await loadFixture(rateLimitLibraryFixture);
+
+ await tester.setWindowLimitPublic(WINDOW_1D, parseUnits('1000'));
+ await tester.setWindowLimitPublic(WINDOW_2D, parseUnits('2000'));
+
+ await tester.removeWindowLimitPublic(WINDOW_1D);
+
+ expect(await tester.windowCountPublic()).eq(1);
+ expect(await tester.hasWindowPublic(WINDOW_2D)).eq(true);
+
+ const statuses = await tester.getWindowStatusesPublic();
+ expect(statuses.length).eq(1);
+ expect(statuses[0].window).eq(WINDOW_2D);
+ });
+ });
+
+ describe('consumeLimit()', () => {
+ it('should consume amount and update stored in-flight', async () => {
+ const { tester } = await loadFixture(rateLimitLibraryFixture);
+ const limit = parseUnits('1000');
+ const amount = parseUnits('300');
+
+ await tester.setWindowLimitPublic(WINDOW_1D, limit);
+ await tester.consumeLimitPublic(amount);
+
+ const [, amountInFlight, lastUpdated] =
+ await tester.getWindowConfigPublic(WINDOW_1D);
+
+ expect(amountInFlight).eq(amount);
+ expect(lastUpdated).eq(await getCurrentBlockTimestamp());
+
+ const statuses = await tester.getWindowStatusesPublic();
+ const status = getStatusByWindow(statuses, WINDOW_1D);
+
+ expect(status!.inFlight).eq(amount);
+ expect(status!.remaining).eq(limit.sub(amount));
+ });
+
+ it('should allow consuming the full limit', async () => {
+ const { tester } = await loadFixture(rateLimitLibraryFixture);
+ const limit = parseUnits('1000');
+
+ await tester.setWindowLimitPublic(WINDOW_1D, limit);
+ await tester.consumeLimitPublic(limit);
+
+ const statuses = await tester.getWindowStatusesPublic();
+ expect(getStatusByWindow(statuses, WINDOW_1D)!.remaining).eq(0);
+ expect(getStatusByWindow(statuses, WINDOW_1D)!.inFlight).eq(limit);
+ });
+
+ it('should fail: amount exceeds remaining', async () => {
+ const { tester } = await loadFixture(rateLimitLibraryFixture);
+ const limit = parseUnits('1000');
+
+ await tester.setWindowLimitPublic(WINDOW_1D, limit);
+ await tester.consumeLimitPublic(parseUnits('800'));
+
+ await expect(
+ tester.consumeLimitPublic(parseUnits('500')),
+ ).to.be.revertedWithCustomError(tester, 'WindowLimitExceeded');
+ });
+
+ it('should fail: any positive consume when limit is 0', async () => {
+ const { tester } = await loadFixture(rateLimitLibraryFixture);
+
+ await tester.setWindowLimitPublic(WINDOW_1D, 0);
+
+ await expect(tester.consumeLimitPublic(1))
+ .to.be.revertedWithCustomError(tester, 'WindowLimitExceeded')
+ .withArgs(WINDOW_1D, 0, 1);
+ });
+
+ it('should allow zero amount consume as checkpoint only', async () => {
+ const { tester } = await loadFixture(rateLimitLibraryFixture);
+ const limit = parseUnits('1000');
+
+ await tester.setWindowLimitPublic(WINDOW_1D, limit);
+ await tester.consumeLimitPublic(parseUnits('500'));
+
+ const [, amountInFlightBefore, lastUpdatedAfterConsume] =
+ await tester.getWindowConfigPublic(WINDOW_1D);
+
+ await time.increase(hours(6));
+
+ await tester.consumeLimitPublic(0);
+
+ const [, amountInFlightAfter, lastUpdatedAfterCheckpoint] =
+ await tester.getWindowConfigPublic(WINDOW_1D);
+
+ const { inFlight: expectedInFlight } = calculateWindowRateLimitCapacity({
+ amountInFlight: amountInFlightBefore,
+ lastUpdated: lastUpdatedAfterConsume,
+ limit,
+ window: WINDOW_1D,
+ now: lastUpdatedAfterCheckpoint,
+ });
+
+ expect(amountInFlightAfter).eq(expectedInFlight);
+ expect(amountInFlightAfter).lt(amountInFlightBefore);
+ expect(lastUpdatedAfterCheckpoint).eq(await getCurrentBlockTimestamp());
+ });
+
+ it('should charge every configured window', async () => {
+ const { tester } = await loadFixture(rateLimitLibraryFixture);
+ const amount = parseUnits('100');
+
+ await tester.setWindowLimitPublic(WINDOW_1D, parseUnits('1000'));
+ await tester.setWindowLimitPublic(WINDOW_2D, parseUnits('2000'));
+
+ await tester.consumeLimitPublic(amount);
+
+ const statuses = await tester.getWindowStatusesPublic();
+
+ expect(getStatusByWindow(statuses, WINDOW_1D)!.inFlight).eq(amount);
+ expect(getStatusByWindow(statuses, WINDOW_2D)!.inFlight).eq(amount);
+ });
+
+ it('should fail: when any window lacks headroom', async () => {
+ const { tester } = await loadFixture(rateLimitLibraryFixture);
+
+ await tester.setWindowLimitPublic(WINDOW_1D, parseUnits('100'));
+ await tester.setWindowLimitPublic(WINDOW_2D, parseUnits('1000'));
+
+ await expect(
+ tester.consumeLimitPublic(parseUnits('200')),
+ ).to.be.revertedWithCustomError(tester, 'WindowLimitExceeded');
+ });
+
+ it('should allow consume after partial linear decay', async () => {
+ const { tester } = await loadFixture(rateLimitLibraryFixture);
+ const limit = parseUnits('1000');
+
+ await tester.setWindowLimitPublic(WINDOW_1D, limit);
+ await tester.consumeLimitPublic(limit);
+
+ await time.increase(hours(1));
+
+ const statusesBefore = await tester.getWindowStatusesPublic();
+ const remainingBefore = getStatusByWindow(
+ statusesBefore,
+ WINDOW_1D,
+ )!.remaining;
+
+ expect(remainingBefore).gt(0);
+ expect(remainingBefore).lt(limit);
+
+ await tester.consumeLimitPublic(remainingBefore);
+
+ const statusesAfter = await tester.getWindowStatusesPublic();
+ const statusAfter = getStatusByWindow(statusesAfter, WINDOW_1D)!;
+
+ expect(statusAfter.inFlight.add(statusAfter.remaining)).eq(limit);
+ });
+ });
+
+ describe('getWindowStatuses()', () => {
+ it('should return an empty array when no windows are configured', async () => {
+ const { tester } = await loadFixture(rateLimitLibraryFixture);
+
+ expect((await tester.getWindowStatusesPublic()).length).eq(0);
+ });
+
+ it('should reflect linear decay after time passes', async () => {
+ const { tester } = await loadFixture(rateLimitLibraryFixture);
+ const limit = parseUnits('1000');
+
+ await tester.setWindowLimitPublic(WINDOW_1D, limit);
+ await tester.consumeLimitPublic(limit);
+
+ const [, , lastUpdatedBefore] = await tester.getWindowConfigPublic(
+ WINDOW_1D,
+ );
+
+ await time.increase(hours(1));
+
+ await expectStatusMatchesCapacity(
+ tester,
+ WINDOW_1D,
+ limit,
+ limit,
+ WINDOW_1D,
+ lastUpdatedBefore,
+ );
+ });
+
+ it('should restore full remaining after a full window elapses', async () => {
+ const { tester } = await loadFixture(rateLimitLibraryFixture);
+ const limit = parseUnits('1000');
+
+ await tester.setWindowLimitPublic(WINDOW_1D, limit);
+ await tester.consumeLimitPublic(limit);
+
+ await time.increase(WINDOW_1D);
+
+ const statuses = await tester.getWindowStatusesPublic();
+ const status = getStatusByWindow(statuses, WINDOW_1D);
+
+ expect(status!.inFlight).eq(0);
+ expect(status!.remaining).eq(limit);
+ });
+
+ it('should return zero remaining when decayed in-flight equals limit', async () => {
+ const { tester } = await loadFixture(rateLimitLibraryFixture);
+ const limit = parseUnits('1000');
+
+ await tester.setWindowLimitPublic(WINDOW_1D, limit);
+ await tester.consumeLimitPublic(limit);
+
+ const statuses = await tester.getWindowStatusesPublic();
+ expect(getStatusByWindow(statuses, WINDOW_1D)!.remaining).eq(0);
+ });
+
+ it('should return multiple statuses after multiple windows are added', async () => {
+ const { tester } = await loadFixture(rateLimitLibraryFixture);
+
+ await tester.setWindowLimitPublic(WINDOW_1D, parseUnits('1000'));
+ await tester.setWindowLimitPublic(WINDOW_2D, parseUnits('2000'));
+
+ const statuses = await tester.getWindowStatusesPublic();
+
+ expect(statuses.length).eq(2);
+
+ const windows = statuses.map((s) => s.window.toNumber()).sort();
+ expect(windows).deep.eq([WINDOW_1D, WINDOW_2D].sort());
+ });
+
+ it('should match decay math when elapsed is between 0 and window', async () => {
+ const { tester } = await loadFixture(rateLimitLibraryFixture);
+ const limit = parseUnits('100000');
+ const window = days(1);
+
+ await tester.setWindowLimitPublic(window, limit);
+ await tester.consumeLimitPublic(parseUnits('80000'));
+
+ const [, amountInFlight, lastUpdated] =
+ await tester.getWindowConfigPublic(window);
+
+ await time.increase(minutes(30));
+
+ await expectStatusMatchesCapacity(
+ tester,
+ window,
+ amountInFlight,
+ limit,
+ window,
+ lastUpdated,
+ );
+ });
+
+ it('should treat stored in-flight above limit as zero remaining', async () => {
+ const { tester } = await loadFixture(rateLimitLibraryFixture);
+ const limit = parseUnits('500');
+
+ await tester.setWindowLimitPublic(WINDOW_1D, parseUnits('1000'));
+ await tester.consumeLimitPublic(parseUnits('800'));
+
+ await tester.setWindowLimitPublic(WINDOW_1D, limit);
+
+ const statuses = await tester.getWindowStatusesPublic();
+ const status = getStatusByWindow(statuses, WINDOW_1D);
+
+ expect(status!.limit).eq(limit);
+ expect(status!.remaining).eq(0);
+ });
+
+ it('should show a 1 wei floor gap between split and single mulDiv', async () => {
+ const limit = parseUnits('1');
+ const window = MIN_WINDOW + 1;
+ const { leftElapsed, rightElapsed } = findOneWeiMulDivSplitGap(
+ limit,
+ window,
+ );
+
+ const singleDecay = mulDiv(limit, leftElapsed + rightElapsed, window);
+ const splitDecay = mulDiv(limit, leftElapsed, window).add(
+ mulDiv(limit, rightElapsed, window),
+ );
+
+ expect(singleDecay.sub(splitDecay)).eq(1);
+ });
+
+ it('should reproduce snapshot-based 1 wei drift while on-chain keeps exact stored-state math', async () => {
+ const { tester } = await loadFixture(rateLimitLibraryFixture);
+ const limit = parseUnits('1');
+ const window = MIN_WINDOW + 1;
+ const consumed = parseUnits('1');
+ const addedAmount = 0;
+ const { leftElapsed, rightElapsed } = findOneWeiMulDivSplitGap(
+ limit,
+ window,
+ );
+
+ await tester.setWindowLimitPublic(window, limit);
+ await tester.consumeLimitPublic(consumed);
+
+ const [, storedBefore, lastUpdatedStoredBefore] =
+ await tester.getWindowConfigPublic(window);
+
+ await time.increase(leftElapsed);
+ const t1 = await getCurrentBlockTimestamp();
+
+ const snapshotAtT1 = getStatusByWindow(
+ await tester.getWindowStatusesPublic(),
+ window,
+ )!;
+
+ await time.increase(rightElapsed);
+
+ await tester.consumeLimitPublic(addedAmount);
+
+ const [, storedAfter, lastUpdatedAfter] =
+ await tester.getWindowConfigPublic(window);
+
+ const correctAtCheckpoint = calculateWindowRateLimitCapacity({
+ amountInFlight: storedBefore,
+ lastUpdated: lastUpdatedStoredBefore,
+ limit,
+ window,
+ now: lastUpdatedAfter,
+ }).inFlight;
+ const expectedStoredAfter = correctAtCheckpoint.add(addedAmount);
+
+ const snapshotBasedAtCheckpoint = calculateWindowRateLimitCapacity({
+ amountInFlight: snapshotAtT1.inFlight,
+ lastUpdated: t1,
+ limit,
+ window,
+ now: lastUpdatedAfter,
+ }).inFlight;
+ const naiveExpectedStoredAfter =
+ snapshotBasedAtCheckpoint.add(addedAmount);
+
+ const driftAbs = expectedStoredAfter.gte(naiveExpectedStoredAfter)
+ ? expectedStoredAfter.sub(naiveExpectedStoredAfter)
+ : naiveExpectedStoredAfter.sub(expectedStoredAfter);
+ expect(driftAbs).eq(1);
+ expect(storedAfter).eq(expectedStoredAfter);
+ const onchainVsNaiveAbs = storedAfter.gte(naiveExpectedStoredAfter)
+ ? storedAfter.sub(naiveExpectedStoredAfter)
+ : naiveExpectedStoredAfter.sub(storedAfter);
+ expect(onchainVsNaiveAbs).eq(1);
+ });
+ });
+});
diff --git a/test/unit/RedemptionVault.test.ts b/test/unit/RedemptionVault.test.ts
index 424610e4..31717614 100644
--- a/test/unit/RedemptionVault.test.ts
+++ b/test/unit/RedemptionVault.test.ts
@@ -1,7178 +1,243 @@
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
-import { expect } from 'chai';
-import { constants } from 'ethers';
-import { parseUnits } from 'ethers/lib/utils';
-import { ethers } from 'hardhat';
+import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
-import { encodeFnSelector } from '../../helpers/utils';
import {
- ManageableVaultTester__factory,
- MBasisRedemptionVault__factory,
+ baseInitParamsRv,
+ redemptionVaultSuits,
+} from './suits/redemption-vault.suits';
+
+import {
+ RedemptionVault__factory,
RedemptionVaultTest__factory,
} from '../../typechain-types';
-import { acErrors, blackList, greenList } from '../common/ac.helpers';
import {
approveBase18,
mintToken,
- pauseVault,
- pauseVaultFn,
+ validateImplementation,
} from '../common/common.helpers';
-import {
- setMinGrowthApr,
- setRoundDataGrowth,
-} from '../common/custom-feed-growth.helpers';
import { setRoundData } from '../common/data-feed.helpers';
import { defaultDeploy, mTokenPermissionedFixture } from '../common/fixtures';
import {
addPaymentTokenTest,
- addWaivedFeeAccountTest,
- changeTokenAllowanceTest,
- removePaymentTokenTest,
- removeWaivedFeeAccountTest,
setInstantFeeTest,
- setInstantDailyLimitTest,
- setMinAmountTest,
- setVariabilityToleranceTest,
- withdrawTest,
- changeTokenFeeTest,
- setTokensReceiverTest,
- setFeeReceiverTest,
} from '../common/manageable-vault.helpers';
-import {
- approveRedeemRequestTest,
- redeemFiatRequestTest,
- redeemInstantTest,
- redeemRequestTest,
- rejectRedeemRequestTest,
- safeApproveRedeemRequestTest,
- safeBulkApproveRequestTest,
- setFiatAdditionalFeeTest,
- setFiatFlatFeeTest,
- setMinFiatRedeemAmountTest,
- setRequestRedeemerTest,
-} from '../common/redemption-vault.helpers';
-import { sanctionUser } from '../common/with-sanctions-list.helpers';
-
-describe('RedemptionVault', function () {
- it('deployment', async () => {
- const {
- redemptionVault,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- roles,
- } = await loadFixture(defaultDeploy);
-
- expect(await redemptionVault.mToken()).eq(mTBILL.address);
-
- expect(await redemptionVault.ONE_HUNDRED_PERCENT()).eq('10000');
-
- expect(await redemptionVault.paused()).eq(false);
-
- expect(await redemptionVault.tokensReceiver()).eq(tokensReceiver.address);
- expect(await redemptionVault.feeReceiver()).eq(feeReceiver.address);
-
- expect(await redemptionVault.minAmount()).eq(1000);
- expect(await redemptionVault.minFiatRedeemAmount()).eq(1000);
-
- expect(await redemptionVault.instantFee()).eq('100');
-
- expect(await redemptionVault.instantDailyLimit()).eq(parseUnits('100000'));
-
- expect(await redemptionVault.mTokenDataFeed()).eq(
- mTokenToUsdDataFeed.address,
- );
- expect(await redemptionVault.variationTolerance()).eq(1);
-
- expect(await redemptionVault.vaultRole()).eq(
- roles.tokenRoles.mTBILL.redemptionVaultAdmin,
- );
-
- expect(await redemptionVault.MANUAL_FULLFILMENT_TOKEN()).eq(
- ethers.constants.AddressZero,
- );
- });
-
- it('failing deployment', async () => {
- const {
- redemptionVault,
- mTokenToUsdDataFeed,
- feeReceiver,
- tokensReceiver,
- mockedSanctionsList,
- requestRedeemer,
- accessControl,
- owner,
- mTBILL,
- } = await loadFixture(defaultDeploy);
-
- const redemptionVaultUninitialized = await new RedemptionVaultTest__factory(
- owner,
- ).deploy();
-
- await expect(
- redemptionVaultUninitialized.initialize(
- ethers.constants.AddressZero,
- {
- mToken: ethers.constants.AddressZero,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- {
- fiatAdditionalFee: 100,
- fiatFlatFee: parseUnits('1'),
- minFiatRedeemAmount: parseUnits('100'),
- },
- requestRedeemer.address,
- ),
- ).to.be.reverted;
- await expect(
- redemptionVaultUninitialized.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: ethers.constants.AddressZero,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- {
- fiatAdditionalFee: 100,
- fiatFlatFee: parseUnits('1'),
- minFiatRedeemAmount: parseUnits('100'),
- },
- requestRedeemer.address,
- ),
- ).to.be.reverted;
- await expect(
- redemptionVaultUninitialized.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: ethers.constants.AddressZero,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- {
- fiatAdditionalFee: 100,
- fiatFlatFee: parseUnits('1'),
- minFiatRedeemAmount: parseUnits('100'),
- },
- requestRedeemer.address,
- ),
- ).to.be.reverted;
- await expect(
- redemptionVaultUninitialized.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: ethers.constants.AddressZero,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- {
- fiatAdditionalFee: 100,
- fiatFlatFee: parseUnits('1'),
- minFiatRedeemAmount: parseUnits('100'),
- },
- requestRedeemer.address,
- ),
- ).to.be.reverted;
- await expect(
- redemptionVaultUninitialized.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 10001,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- {
- fiatAdditionalFee: 100,
- fiatFlatFee: parseUnits('1'),
- minFiatRedeemAmount: parseUnits('100'),
- },
- requestRedeemer.address,
- ),
- ).to.be.reverted;
- await expect(
- redemptionVaultUninitialized.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- {
- fiatAdditionalFee: 10001,
- fiatFlatFee: parseUnits('1'),
- minFiatRedeemAmount: parseUnits('100'),
- },
- requestRedeemer.address,
- ),
- ).to.be.reverted;
-
- await expect(
- redemptionVaultUninitialized.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- {
- fiatAdditionalFee: 100,
- fiatFlatFee: parseUnits('1'),
- minFiatRedeemAmount: parseUnits('100'),
- },
- constants.AddressZero,
- ),
- ).to.be.reverted;
-
- await expect(
- redemptionVault.initializeWithoutInitializer(
- ethers.constants.AddressZero,
- {
- mToken: ethers.constants.AddressZero,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- {
- fiatAdditionalFee: 100,
- fiatFlatFee: parseUnits('1'),
- minFiatRedeemAmount: parseUnits('100'),
- },
- requestRedeemer.address,
- ),
- ).to.be.revertedWith('Initializable: contract is not initializing');
- });
-
- describe('MBasisRedemptionVault', () => {
- describe('deployment', () => {
- it('vaultRole', async () => {
- const fixture = await loadFixture(defaultDeploy);
-
- const tester = await new MBasisRedemptionVault__factory(
- fixture.owner,
- ).deploy();
-
- expect(await tester.vaultRole()).eq(
- await tester.M_BASIS_REDEMPTION_VAULT_ADMIN_ROLE(),
- );
- });
- });
- });
-
- describe('initialization', () => {
- it('should fail: cal; initialize() when already initialized', async () => {
- const { redemptionVault } = await loadFixture(defaultDeploy);
-
- await expect(
- redemptionVault.initialize(
- constants.AddressZero,
- {
- mToken: constants.AddressZero,
- mTokenDataFeed: constants.AddressZero,
- },
- {
- feeReceiver: constants.AddressZero,
- tokensReceiver: constants.AddressZero,
- },
- {
- instantFee: 0,
- instantDailyLimit: 0,
- },
- constants.AddressZero,
- 0,
- 0,
- {
- fiatAdditionalFee: 0,
- fiatFlatFee: 0,
- minFiatRedeemAmount: 0,
- },
- constants.AddressZero,
- ),
- ).revertedWith('Initializable: contract is already initialized');
- });
-
- it('should fail: call with initializing == false', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initializeWithoutInitializer(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- 1000,
- ),
- ).revertedWith('Initializable: contract is not initializing');
- });
-
- it('should fail: when _tokensReceiver == address(this)', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- feeReceiver,
- mTokenToUsdDataFeed,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: vault.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- 1000,
- ),
- ).revertedWith('invalid address');
- });
- it('should fail: when _feeReceiver == address(this)', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- mTokenToUsdDataFeed,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: vault.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- 1000,
- ),
- ).revertedWith('invalid address');
- });
- it('should fail: when limit = 0', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: 0,
- },
- mockedSanctionsList.address,
- 1,
- 1000,
- ),
- ).revertedWith('zero limit');
- });
- it('should fail: when mToken dataFeed address zero', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: constants.AddressZero,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- 1000,
- ),
- ).revertedWith('zero address');
- });
- it('should fail: when variationTolarance zero', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mockedSanctionsList,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 0,
- 1000,
- ),
- ).revertedWith('fee == 0');
- });
- });
-
- describe('setTokensReceiver()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVault, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
-
- await setTokensReceiverTest(
- { vault: redemptionVault, owner },
- regularAccounts[0].address,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- },
- );
- });
-
- it('should fail: call with zero address receiver', async () => {
- const { owner, redemptionVault } = await loadFixture(defaultDeploy);
-
- await setTokensReceiverTest(
- { vault: redemptionVault, owner },
- constants.AddressZero,
- {
- revertMessage: 'zero address',
- },
- );
- });
-
- it('should fail: call with address(this) receiver', async () => {
- const { owner, redemptionVault } = await loadFixture(defaultDeploy);
-
- await setTokensReceiverTest(
- { vault: redemptionVault, owner },
- redemptionVault.address,
- {
- revertMessage: 'invalid address',
- },
- );
- });
-
- it('call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVault, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
-
- await setTokensReceiverTest(
- { vault: redemptionVault, owner },
- regularAccounts[0].address,
- );
- });
- });
-
- describe('setMinAmount()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVault, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
-
- await setMinAmountTest({ vault: redemptionVault, owner }, 1.1, {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- });
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVault } = await loadFixture(defaultDeploy);
- await setMinAmountTest({ vault: redemptionVault, owner }, 1.1);
- });
- });
-
- describe('setMinFiatRedeemAmount()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVault, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
-
- await setMinFiatRedeemAmountTest({ redemptionVault, owner }, 1.1, {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- });
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVault } = await loadFixture(defaultDeploy);
- await setMinFiatRedeemAmountTest({ redemptionVault, owner }, 1.1);
- });
- });
-
- describe('setFeeReceiver()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVault, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
-
- await setFeeReceiverTest(
- { vault: redemptionVault, owner },
- regularAccounts[0].address,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- },
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVault, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await setFeeReceiverTest(
- { vault: redemptionVault, owner },
- regularAccounts[0].address,
- );
- });
- });
-
- describe('sanctionsListAdminRole()', () => {
- it('should return same role as vaultRole()', async () => {
- const { redemptionVault } = await loadFixture(defaultDeploy);
- const vaultRole = await redemptionVault.vaultRole();
- const sanctionsListAdminRole =
- await redemptionVault.sanctionsListAdminRole();
-
- expect(sanctionsListAdminRole).eq(vaultRole);
- });
- });
-
- describe('setFiatFlatFee()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVault, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
-
- await setFiatFlatFeeTest({ redemptionVault, owner }, 100, {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- });
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVault } = await loadFixture(defaultDeploy);
- await setFiatFlatFeeTest({ redemptionVault, owner }, 100);
- });
- });
-
- describe('setFiatAdditionalFee()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVault, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
-
- await setFiatAdditionalFeeTest({ redemptionVault, owner }, 100, {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- });
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVault } = await loadFixture(defaultDeploy);
- await setFiatAdditionalFeeTest({ redemptionVault, owner }, 100);
- });
- });
-
- describe('setInstantDailyLimit()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVault, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
-
- await setInstantDailyLimitTest(
- { vault: redemptionVault, owner },
- parseUnits('1000'),
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- },
- );
- });
-
- it('should fail: try to set 0 limit', async () => {
- const { owner, redemptionVault } = await loadFixture(defaultDeploy);
-
- await setInstantDailyLimitTest(
- { vault: redemptionVault, owner },
- constants.Zero,
- {
- revertMessage: 'MV: limit zero',
- },
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVault } = await loadFixture(defaultDeploy);
- await setInstantDailyLimitTest(
- { vault: redemptionVault, owner },
- parseUnits('1000'),
- );
- });
- });
-
- describe('addPaymentToken()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVault, regularAccounts, owner } = await loadFixture(
- defaultDeploy,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- ethers.constants.AddressZero,
- ethers.constants.AddressZero,
- 0,
- true,
- constants.MaxUint256,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
-
- it('should fail: when token is already added', async () => {
- const { redemptionVault, stableCoins, owner, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- constants.MaxUint256,
- {
- revertMessage: 'MV: already added',
- },
- );
- });
-
- it('should fail: when token dataFeed address zero', async () => {
- const { redemptionVault, stableCoins, owner } = await loadFixture(
- defaultDeploy,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- constants.AddressZero,
- 0,
- true,
- constants.MaxUint256,
- {
- revertMessage: 'zero address',
- },
- );
- });
-
- it('call when allowance is zero', async () => {
- const { redemptionVault, stableCoins, owner, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- false,
- constants.Zero,
- );
- });
-
- it('call when allowance is not uint256 max', async () => {
- const { redemptionVault, stableCoins, owner, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- false,
- parseUnits('100'),
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVault, stableCoins, owner, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role and add 3 options on a row', async () => {
- const { redemptionVault, stableCoins, owner, dataFeed } =
- await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.usdt,
- dataFeed.address,
- 0,
- true,
- );
- });
- });
-
- describe('addWaivedFeeAccount()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVault, regularAccounts, owner } = await loadFixture(
- defaultDeploy,
- );
- await addWaivedFeeAccountTest(
- { vault: redemptionVault, owner },
- ethers.constants.AddressZero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('should fail: if account fee already waived', async () => {
- const { redemptionVault, owner } = await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: redemptionVault, owner },
- owner.address,
- );
- await addWaivedFeeAccountTest(
- { vault: redemptionVault, owner },
- owner.address,
- { revertMessage: 'MV: already added' },
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVault, owner } = await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: redemptionVault, owner },
- owner.address,
- );
- });
- });
-
- describe('removeWaivedFeeAccount()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVault, regularAccounts, owner } = await loadFixture(
- defaultDeploy,
- );
- await removeWaivedFeeAccountTest(
- { vault: redemptionVault, owner },
- ethers.constants.AddressZero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('should fail: if account not found in restriction', async () => {
- const { redemptionVault, owner } = await loadFixture(defaultDeploy);
- await removeWaivedFeeAccountTest(
- { vault: redemptionVault, owner },
- owner.address,
- { revertMessage: 'MV: not found' },
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVault, owner } = await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: redemptionVault, owner },
- owner.address,
- );
- await removeWaivedFeeAccountTest(
- { vault: redemptionVault, owner },
- owner.address,
- );
- });
- });
-
- describe('setFee()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVault, regularAccounts, owner } = await loadFixture(
- defaultDeploy,
- );
- await setInstantFeeTest(
- { vault: redemptionVault, owner },
- ethers.constants.Zero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
-
- it('should fail: if new value greater then 100%', async () => {
- const { redemptionVault, owner } = await loadFixture(defaultDeploy);
- await setInstantFeeTest({ vault: redemptionVault, owner }, 10001, {
- revertMessage: 'fee > 100%',
+import { redeemInstantTest } from '../common/redemption-vault.helpers';
+import { initializeRv } from '../common/vault-initializer.helpers';
+
+redemptionVaultSuits(
+ 'RedemptionVault',
+ defaultDeploy,
+ {
+ createNew: async (owner: SignerWithAddress) =>
+ new RedemptionVaultTest__factory(owner).deploy(),
+ key: 'redemptionVault',
+ },
+ async () => {
+ await validateImplementation(RedemptionVault__factory);
+ },
+ {
+ deployUninitialized: (fixture) =>
+ new RedemptionVaultTest__factory(fixture.owner).deploy(),
+ initialize: async (fixture, params, opt) => {
+ await initializeRv(
+ { ...baseInitParamsRv(fixture), ...params },
+ opt?.contract,
+ opt,
+ );
+ },
+ },
+ () => {
+ describe('RedemptionVault', () => {
+ describe('redeemInstant() with permissioned mToken', () => {
+ it('with permissioned mToken - burns/transfers mToken from greenlisted user', async () => {
+ const {
+ owner,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ mTokenPermissioned,
+ mTokenPermissionedRoles,
+ accessControl,
+ mTokenPermissionedRedemptionVault,
+ } = await loadFixture(mTokenPermissionedFixture);
+
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenPermissionedRoles.greenlisted,
+ owner.address,
+ );
+
+ await mintToken(mTokenPermissioned, owner, 100_000);
+ await setInstantFeeTest(
+ { vault: mTokenPermissionedRedemptionVault, owner },
+ 1000,
+ );
+ await approveBase18(
+ owner,
+ mTokenPermissioned,
+ mTokenPermissionedRedemptionVault,
+ 100_000,
+ );
+
+ await mintToken(
+ stableCoins.dai,
+ mTokenPermissionedRedemptionVault,
+ 100_000,
+ );
+ await addPaymentTokenTest(
+ { vault: mTokenPermissionedRedemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+
+ await redeemInstantTest(
+ {
+ redemptionVault: mTokenPermissionedRedemptionVault,
+ owner,
+ mTBILL: mTokenPermissioned,
+ mTokenToUsdDataFeed,
+ },
+ stableCoins.dai,
+ 999,
+ );
+ });
+
+ it('should fail: with permissioned mToken - instant fee is 0, burns/transfers mToken from non-greenlisted user', async () => {
+ const {
+ owner,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ mTokenPermissioned,
+ mTokenPermissionedRoles,
+ accessControl,
+ mTokenPermissionedRedemptionVault,
+ } = await loadFixture(mTokenPermissionedFixture);
+
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenPermissionedRoles.greenlisted,
+ owner.address,
+ );
+ await mintToken(mTokenPermissioned, owner, 100_000);
+ await accessControl.revokeRole(
+ mTokenPermissionedRoles.greenlisted,
+ owner.address,
+ );
+ await setInstantFeeTest(
+ { vault: mTokenPermissionedRedemptionVault, owner },
+ 0,
+ );
+ await approveBase18(
+ owner,
+ mTokenPermissioned,
+ mTokenPermissionedRedemptionVault,
+ 100_000,
+ );
+
+ await mintToken(
+ stableCoins.dai,
+ mTokenPermissionedRedemptionVault,
+ 100_000,
+ );
+ await addPaymentTokenTest(
+ { vault: mTokenPermissionedRedemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+
+ await redeemInstantTest(
+ {
+ redemptionVault: mTokenPermissionedRedemptionVault,
+ owner,
+ mTBILL: mTokenPermissioned,
+ mTokenToUsdDataFeed,
+ },
+ stableCoins.dai,
+ 999,
+ {
+ revertCustomError: { customErrorName: 'NotGreenlisted' },
+ },
+ );
+ });
+
+ it('should fail: with permissioned mToken - redeem instant burns mToken from non-greenlisted user when fee is not 0', async () => {
+ const {
+ owner,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ mTokenPermissioned,
+ mTokenPermissionedRedemptionVault,
+ mTokenPermissionedRoles,
+ accessControl,
+ } = await loadFixture(mTokenPermissionedFixture);
+
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenPermissionedRoles.greenlisted,
+ owner.address,
+ );
+ await mintToken(mTokenPermissioned, owner, 100_000);
+ await setInstantFeeTest(
+ { vault: mTokenPermissionedRedemptionVault, owner },
+ 1000,
+ );
+ await accessControl.revokeRole(
+ mTokenPermissionedRoles.greenlisted,
+ owner.address,
+ );
+ await approveBase18(
+ owner,
+ mTokenPermissioned,
+ mTokenPermissionedRedemptionVault,
+ 100_000,
+ );
+
+ await mintToken(
+ stableCoins.dai,
+ mTokenPermissionedRedemptionVault,
+ 100_000,
+ );
+ await addPaymentTokenTest(
+ { vault: mTokenPermissionedRedemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+
+ await redeemInstantTest(
+ {
+ redemptionVault: mTokenPermissionedRedemptionVault,
+ owner,
+ mTBILL: mTokenPermissioned,
+ mTokenToUsdDataFeed,
+ },
+ stableCoins.dai,
+ 999,
+ {
+ revertCustomError: { customErrorName: 'NotGreenlisted' },
+ },
+ );
+ });
});
});
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVault, owner } = await loadFixture(defaultDeploy);
- await setInstantFeeTest({ vault: redemptionVault, owner }, 100);
- });
- });
-
- describe('setVariabilityTolerance()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVault, regularAccounts, owner } = await loadFixture(
- defaultDeploy,
- );
- await setVariabilityToleranceTest(
- { vault: redemptionVault, owner },
- ethers.constants.Zero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('should fail: if new value zero', async () => {
- const { redemptionVault, owner } = await loadFixture(defaultDeploy);
- await setVariabilityToleranceTest(
- { vault: redemptionVault, owner },
- ethers.constants.Zero,
- { revertMessage: 'fee == 0' },
- );
- });
-
- it('should fail: if new value greater then 100%', async () => {
- const { redemptionVault, owner } = await loadFixture(defaultDeploy);
- await setVariabilityToleranceTest(
- { vault: redemptionVault, owner },
- 10001,
- { revertMessage: 'fee > 100%' },
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVault, owner } = await loadFixture(defaultDeploy);
- await setVariabilityToleranceTest({ vault: redemptionVault, owner }, 100);
- });
- });
-
- describe('setRequestRedeemer()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVault, regularAccounts, owner } = await loadFixture(
- defaultDeploy,
- );
- await setRequestRedeemerTest(
- { redemptionVault, owner },
- ethers.constants.AddressZero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('should fail: if redeemer address zero', async () => {
- const { redemptionVault, owner } = await loadFixture(defaultDeploy);
- await setRequestRedeemerTest(
- { redemptionVault, owner },
- ethers.constants.AddressZero,
- { revertMessage: 'zero address' },
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVault, owner } = await loadFixture(defaultDeploy);
- await setRequestRedeemerTest({ redemptionVault, owner }, owner.address);
- });
- });
-
- describe('removePaymentToken()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVault, regularAccounts, owner } = await loadFixture(
- defaultDeploy,
- );
- await removePaymentTokenTest(
- { vault: redemptionVault, owner },
- ethers.constants.AddressZero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
-
- it('should fail: when token is not exists', async () => {
- const { owner, redemptionVault, stableCoins } = await loadFixture(
- defaultDeploy,
- );
- await removePaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai.address,
- { revertMessage: 'MV: not exists' },
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVault, stableCoins, owner, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await removePaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai.address,
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role and add 3 options on a row', async () => {
- const { redemptionVault, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.usdt,
- dataFeed.address,
- 0,
- true,
- );
-
- await removePaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai.address,
- );
- await removePaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.usdc.address,
- );
- await removePaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.usdt.address,
- );
-
- await removePaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.usdt.address,
- { revertMessage: 'MV: not exists' },
- );
- });
- });
-
- describe('withdrawToken()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVault, regularAccounts, owner } = await loadFixture(
- defaultDeploy,
- );
- await withdrawTest(
- { vault: redemptionVault, owner },
- ethers.constants.AddressZero,
- 0,
- ethers.constants.AddressZero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
-
- it('should fail: when there is no token in vault', async () => {
- const { owner, redemptionVault, regularAccounts, stableCoins } =
- await loadFixture(defaultDeploy);
- await withdrawTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- 1,
- regularAccounts[0],
- { revertMessage: 'ERC20: transfer amount exceeds balance' },
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVault, regularAccounts, stableCoins, owner } =
- await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, redemptionVault, 1);
- await withdrawTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- 1,
- regularAccounts[0],
- );
- });
- });
-
- describe('freeFromMinAmount()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const { redemptionVault, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- redemptionVault
- .connect(regularAccounts[0])
- .freeFromMinAmount(regularAccounts[1].address, true),
- ).to.be.revertedWith('WMAC: hasnt role');
- });
- it('should not fail', async () => {
- const { redemptionVault, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- redemptionVault.freeFromMinAmount(regularAccounts[0].address, true),
- ).to.not.reverted;
-
- expect(
- await redemptionVault.isFreeFromMinAmount(regularAccounts[0].address),
- ).to.eq(true);
- });
- it('should fail: already in list', async () => {
- const { redemptionVault, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- redemptionVault.freeFromMinAmount(regularAccounts[0].address, true),
- ).to.not.reverted;
-
- expect(
- await redemptionVault.isFreeFromMinAmount(regularAccounts[0].address),
- ).to.eq(true);
-
- await expect(
- redemptionVault.freeFromMinAmount(regularAccounts[0].address, true),
- ).to.revertedWith('DV: already free');
- });
- });
-
- describe('changeTokenAllowance()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVault, regularAccounts, owner } = await loadFixture(
- defaultDeploy,
- );
- await changeTokenAllowanceTest(
- { vault: redemptionVault, owner },
- ethers.constants.AddressZero,
- 0,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('should fail: token not exist', async () => {
- const { redemptionVault, owner, stableCoins } = await loadFixture(
- defaultDeploy,
- );
- await changeTokenAllowanceTest(
- { vault: redemptionVault, owner },
- stableCoins.dai.address,
- 0,
- { revertMessage: 'MV: token not exists' },
- );
- });
- it('should fail: allowance zero', async () => {
- const { redemptionVault, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: redemptionVault, owner },
- stableCoins.dai.address,
- 0,
- { revertMessage: 'MV: zero allowance' },
- );
- });
- it('should fail: if mint exceed allowance', async () => {
- const {
- redemptionVault,
- stableCoins,
- owner,
- dataFeed,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, owner, 100000);
- await approveBase18(owner, stableCoins.dai, redemptionVault, 100000);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: redemptionVault, owner },
- stableCoins.dai.address,
- 100,
- );
-
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'MV: exceed allowance',
- },
- );
-
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'MV: exceed allowance',
- },
- );
- });
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVault, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: redemptionVault, owner },
- stableCoins.dai.address,
- 100000000,
- );
- });
- it('should decrease if allowance < UINT_MAX', async () => {
- const {
- redemptionVault,
- stableCoins,
- owner,
- dataFeed,
- mTBILL,
- mTokenToUsdDataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, owner, 100000);
- await approveBase18(owner, mTBILL, redemptionVault, 100000);
-
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: redemptionVault, owner },
- stableCoins.dai.address,
- parseUnits('1000'),
- );
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
-
- const tokenConfigBefore = await redemptionVault.tokensConfig(
- stableCoins.dai.address,
- );
-
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 999,
- );
-
- const tokenConfigAfter = await redemptionVault.tokensConfig(
- stableCoins.dai.address,
- );
-
- expect(tokenConfigBefore.allowance.sub(tokenConfigAfter.allowance)).eq(
- parseUnits('999'),
- );
- });
- it('should not decrease if allowance = UINT_MAX', async () => {
- const {
- redemptionVault,
- stableCoins,
- owner,
- dataFeed,
- mTBILL,
- mTokenToUsdDataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, owner, 100000);
- await approveBase18(owner, mTBILL, redemptionVault, 100000);
-
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: redemptionVault, owner },
- stableCoins.dai.address,
- constants.MaxUint256,
- );
-
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
-
- const tokenConfigBefore = await redemptionVault.tokensConfig(
- stableCoins.dai.address,
- );
-
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 999,
- );
-
- const tokenConfigAfter = await redemptionVault.tokensConfig(
- stableCoins.dai.address,
- );
-
- expect(tokenConfigBefore.allowance).eq(tokenConfigAfter.allowance);
- });
- });
-
- describe('changeTokenFee()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVault, regularAccounts, owner } = await loadFixture(
- defaultDeploy,
- );
- await changeTokenFeeTest(
- { vault: redemptionVault, owner },
- ethers.constants.AddressZero,
- 0,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('should fail: token not exist', async () => {
- const { redemptionVault, owner, stableCoins } = await loadFixture(
- defaultDeploy,
- );
- await changeTokenFeeTest(
- { vault: redemptionVault, owner },
- stableCoins.dai.address,
- 0,
- { revertMessage: 'MV: token not exists' },
- );
- });
- it('should fail: fee > 100%', async () => {
- const { redemptionVault, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenFeeTest(
- { vault: redemptionVault, owner },
- stableCoins.dai.address,
- 10001,
- { revertMessage: 'fee > 100%' },
- );
- });
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVault, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenFeeTest(
- { vault: redemptionVault, owner },
- stableCoins.dai.address,
- 100,
- );
- });
- });
-
- describe('redeemInstant()', () => {
- it('should fail: when there is no token in vault', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'MV: token not exists',
- },
- );
- });
-
- it('should fail: when trying to redeem 0 amount', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 0,
- {
- revertMessage: 'RV: invalid amount',
- },
- );
- });
-
- it('should fail: when function paused', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- redemptionVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector(
- 'redeemInstant(address,uint256,uint256)',
- );
- await pauseVaultFn(redemptionVault, selector);
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: call with insufficient allowance', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'ERC20: insufficient allowance',
- },
- );
- });
-
- it('should fail: call with insufficient balance', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'ERC20: burn amount exceeds balance',
- },
- );
- });
-
- it('should fail: dataFeed rate 0 ', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, stableCoins.dai, redemptionVault, 10);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await mintToken(mTBILL, owner, 100_000);
- await setRoundData({ mockedAggregator }, 0);
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'DF: feed is deprecated',
- },
- );
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 0);
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'DF: feed is deprecated',
- },
- );
- });
-
- it('should fail: call for amount < minAmount', async () => {
- const {
- redemptionVault,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mTBILL, owner, 100_000);
- await approveBase18(owner, mTBILL, redemptionVault, 100_000);
-
- await setMinAmountTest({ vault: redemptionVault, owner }, 100_000);
-
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'RV: amount < min',
- },
- );
- });
-
- it('should fail: if exceed allowance of deposit by token', async () => {
- const {
- redemptionVault,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(mTBILL, owner, 100_000);
- await changeTokenAllowanceTest(
- { vault: redemptionVault, owner },
- stableCoins.dai.address,
- 100,
- );
- await approveBase18(owner, mTBILL, redemptionVault, 100_000);
-
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'MV: exceed allowance',
- },
- );
- });
-
- it('should fail: if redeem daily limit exceeded', async () => {
- const {
- redemptionVault,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(mTBILL, owner, 100_000);
- await setInstantDailyLimitTest({ vault: redemptionVault, owner }, 1000);
-
- await approveBase18(owner, mTBILL, redemptionVault, 100_000);
-
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'MV: exceed limit',
- },
- );
- });
-
- it('should fail: if min receive amount greater then actual', async () => {
- const {
- redemptionVault,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(mTBILL, owner, 100_000);
-
- await approveBase18(owner, mTBILL, redemptionVault, 100_000);
-
- await redeemInstantTest(
- {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- minAmount: parseUnits('1000000'),
- },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'RV: minReceiveAmount > actual',
- },
- );
- });
-
- it('should fail: if some fee = 100%', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 10000,
- true,
- );
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'RV: amountMTokenIn < fee',
- },
- );
-
- await removePaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setInstantFeeTest({ vault: redemptionVault, owner }, 10000);
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- { revertMessage: 'RV: amountMTokenIn < fee' },
- );
- });
-
- it('should fail: greenlist enabled and user not in greenlist ', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redemptionVault.setGreenlistEnable(true);
-
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: user in blacklist ', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: user in sanctions list', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- regularAccounts[0],
- );
-
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('should fail: when function with custom recipient is paused', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- customRecipient,
- } = await loadFixture(defaultDeploy);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- redemptionVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector(
- 'redeemInstant(address,uint256,uint256,address)',
- );
- await pauseVaultFn(redemptionVault, selector);
- await redeemInstantTest(
- {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: greenlist enabled and recipient not in greenlist (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await redemptionVault.setGreenlistEnable(true);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await redeemInstantTest(
- {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: recipient in blacklist (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- customRecipient,
- );
-
- await redeemInstantTest(
- {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: recipient in sanctions list (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- customRecipient,
- );
-
- await redeemInstantTest(
- {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('should fail: user try to instant redeem fiat', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
-
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
-
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- await redemptionVault.MANUAL_FULLFILMENT_TOKEN(),
- 100,
- {
- revertMessage: 'MV: token not exists',
- },
- );
- });
-
- it('redeem 100 mTBILL, greenlist enabled and user in greenlist ', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- greenListableTester,
- mTokenToUsdDataFeed,
- accessControl,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redemptionVault.setGreenlistEnable(true);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem 100 mTBILL when 10% growth is applied', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customFeedGrowth,
- } = await loadFixture(defaultDeploy);
-
- await mTokenToUsdDataFeed.changeAggregator(customFeedGrowth.address);
- await setRoundDataGrowth({ owner, customFeedGrowth }, 1, -1000, 10);
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantTest(
- {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- expectedAmountOut: parseUnits('99.000314820', 9),
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem 100 mTBILL when -10% growth is applied', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customFeedGrowth,
- } = await loadFixture(defaultDeploy);
-
- await setMinGrowthApr({ owner, customFeedGrowth }, -10);
- await mTokenToUsdDataFeed.changeAggregator(customFeedGrowth.address);
- await setRoundDataGrowth({ owner, customFeedGrowth }, 1, -1000, -10);
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantTest(
- {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- expectedAmountOut: parseUnits('98.999685180', 9),
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- });
-
- it('redeem 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$ and token fee 1%', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- });
-
- it('redeem 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$ without checking of minDepositAmount', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await redemptionVault.freeFromMinAmount(owner.address, true);
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- });
-
- it('redeem 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$ and user in waivedFeeRestriction', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await addWaivedFeeAccountTest(
- { vault: redemptionVault, owner },
- owner.address,
- );
- await redeemInstantTest(
- {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- waivedFee: true,
- },
- stableCoins.dai,
- 100,
- );
- });
-
- it('redeem 100 mTBILL (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantTest(
- {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem 100 mTBILL when recipient == msg.sender (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantTest(
- {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient: regularAccounts[0],
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem 100 mTBILL when other fn overload is paused (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- redemptionVault,
- encodeFnSelector('redeemInstant(address,uint256,uint256)'),
- );
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantTest(
- {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem 100 mTBILL when other fn overload is paused', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- redemptionVault,
- encodeFnSelector('redeemInstant(address,uint256,uint256,address)'),
- );
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantTest(
- {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('with permissioned mToken - burns/transfers mToken from greenlisted user and fee recipient', async () => {
- const {
- owner,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- mTokenPermissioned,
- mTokenPermissionedRoles,
- accessControl,
- mTokenPermissionedRedemptionVault,
- } = await loadFixture(mTokenPermissionedFixture);
-
- await accessControl.grantRole(
- mTokenPermissionedRoles.greenlisted,
- owner.address,
- );
-
- await accessControl.grantRole(
- mTokenPermissionedRoles.greenlisted,
- await mTokenPermissionedRedemptionVault.feeReceiver(),
- );
- await mintToken(mTokenPermissioned, owner, 100_000);
- await setInstantFeeTest(
- { vault: mTokenPermissionedRedemptionVault, owner },
- 1000,
- );
- await approveBase18(
- owner,
- mTokenPermissioned,
- mTokenPermissionedRedemptionVault,
- 100_000,
- );
-
- await mintToken(
- stableCoins.dai,
- mTokenPermissionedRedemptionVault,
- 100_000,
- );
- await addPaymentTokenTest(
- { vault: mTokenPermissionedRedemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
-
- await redeemInstantTest(
- {
- redemptionVault: mTokenPermissionedRedemptionVault,
- owner,
- mTBILL: mTokenPermissioned,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 999,
- );
- });
-
- it('with permissioned mToken - instant fee is 0, burns/transfers mToken from non-greenlisted user', async () => {
- const {
- owner,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- mTokenPermissioned,
- mTokenPermissionedRoles,
- accessControl,
- mTokenPermissionedRedemptionVault,
- } = await loadFixture(mTokenPermissionedFixture);
-
- await accessControl.grantRole(
- mTokenPermissionedRoles.greenlisted,
- owner.address,
- );
- await mintToken(mTokenPermissioned, owner, 100_000);
- await accessControl.revokeRole(
- mTokenPermissionedRoles.greenlisted,
- owner.address,
- );
- await setInstantFeeTest(
- { vault: mTokenPermissionedRedemptionVault, owner },
- 0,
- );
- await approveBase18(
- owner,
- mTokenPermissioned,
- mTokenPermissionedRedemptionVault,
- 100_000,
- );
-
- await mintToken(
- stableCoins.dai,
- mTokenPermissionedRedemptionVault,
- 100_000,
- );
- await addPaymentTokenTest(
- { vault: mTokenPermissionedRedemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
-
- await redeemInstantTest(
- {
- redemptionVault: mTokenPermissionedRedemptionVault,
- owner,
- mTBILL: mTokenPermissioned,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 999,
- );
- });
-
- it('should fail: with permissioned mToken - burns/transfers mToken from greenlisted user but fee recipient is not greenlisted', async () => {
- const {
- owner,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- mTokenPermissioned,
- mTokenPermissionedRoles,
- accessControl,
- mTokenPermissionedRedemptionVault,
- } = await loadFixture(mTokenPermissionedFixture);
-
- await accessControl.grantRole(
- mTokenPermissionedRoles.greenlisted,
- owner.address,
- );
- await mintToken(mTokenPermissioned, owner, 100_000);
- await setInstantFeeTest(
- { vault: mTokenPermissionedRedemptionVault, owner },
- 1000,
- );
- await approveBase18(
- owner,
- mTokenPermissioned,
- mTokenPermissionedRedemptionVault,
- 100_000,
- );
-
- await mintToken(
- stableCoins.dai,
- mTokenPermissionedRedemptionVault,
- 100_000,
- );
- await addPaymentTokenTest(
- { vault: mTokenPermissionedRedemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
-
- await redeemInstantTest(
- {
- redemptionVault: mTokenPermissionedRedemptionVault,
- owner,
- mTBILL: mTokenPermissioned,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 999,
- {
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- },
- );
- });
-
- it('should fail: with permissioned mToken - redeem instant burns/transfers mToken from non-greenlisted user', async () => {
- const {
- owner,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- mTokenPermissioned,
- mTokenPermissionedRedemptionVault,
- mTokenPermissionedRoles,
- accessControl,
- } = await loadFixture(mTokenPermissionedFixture);
-
- await accessControl.grantRole(
- mTokenPermissionedRoles.greenlisted,
- owner.address,
- );
- await mintToken(mTokenPermissioned, owner, 100_000);
- await setInstantFeeTest(
- { vault: mTokenPermissionedRedemptionVault, owner },
- 1000,
- );
- await accessControl.revokeRole(
- mTokenPermissionedRoles.greenlisted,
- owner.address,
- );
- await approveBase18(
- owner,
- mTokenPermissioned,
- mTokenPermissionedRedemptionVault,
- 100_000,
- );
-
- await mintToken(
- stableCoins.dai,
- mTokenPermissionedRedemptionVault,
- 100_000,
- );
- await addPaymentTokenTest(
- { vault: mTokenPermissionedRedemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
-
- await redeemInstantTest(
- {
- redemptionVault: mTokenPermissionedRedemptionVault,
- owner,
- mTBILL: mTokenPermissioned,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 999,
- {
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- },
- );
- });
- });
-
- describe('redeemRequest()', () => {
- it('should fail: when there is no token in vault', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'MV: token not exists',
- },
- );
- });
-
- it('should fail: when trying to redeem 0 amount', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 0,
- {
- revertMessage: 'RV: invalid amount',
- },
- );
- });
-
- it('should fail: when function paused', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- redemptionVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector('redeemRequest(address,uint256)');
- await pauseVaultFn(redemptionVault, selector);
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: call with insufficient allowance', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'ERC20: insufficient allowance',
- },
- );
- });
-
- it('should fail: call with insufficient balance', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'ERC20: transfer amount exceeds balance',
- },
- );
- });
-
- it('should fail: dataFeed rate 0 ', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, stableCoins.dai, redemptionVault, 10);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await mintToken(mTBILL, owner, 100_000);
- await setRoundData({ mockedAggregator }, 0);
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'DF: feed is deprecated',
- },
- );
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 0);
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'DF: feed is deprecated',
- },
- );
- });
-
- it('should fail: call for amount < minAmount', async () => {
- const {
- redemptionVault,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mTBILL, owner, 100_000);
- await approveBase18(owner, mTBILL, redemptionVault, 100_000);
-
- await setMinAmountTest({ vault: redemptionVault, owner }, 100_000);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'RV: amount < min',
- },
- );
- });
-
- it('should fail: if some fee = 100%', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 10000,
- true,
- );
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'RV: amountMTokenIn < fee',
- },
- );
- });
-
- it('should fail: greenlist enabled and user not in greenlist ', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redemptionVault.setGreenlistEnable(true);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: user in blacklist ', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: user in sanctions list', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- regularAccounts[0],
- );
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('should fail: when function paused (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- customRecipient,
- } = await loadFixture(defaultDeploy);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- redemptionVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector(
- 'redeemRequest(address,uint256,address)',
- );
- await pauseVaultFn(redemptionVault, selector);
- await redeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: greenlist enabled and recipient not in greenlist (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await redemptionVault.setGreenlistEnable(true);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await redeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: recipient in blacklist (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- customRecipient,
- );
-
- await redeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: recipient in sanctions list (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- customRecipient,
- );
-
- await redeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('should fail: user try to redeem fiat in basic request (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
-
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
-
- await redeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- await redemptionVault.MANUAL_FULLFILMENT_TOKEN(),
- 100,
- {
- revertMessage: 'RV: tokenOut == fiat',
- },
- );
- });
-
- it('should fail: user try to redeem fiat in basic request', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
-
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- await redemptionVault.MANUAL_FULLFILMENT_TOKEN(),
- 100,
- {
- revertMessage: 'RV: tokenOut == fiat',
- },
- );
- });
-
- it('redeem request 100 mTBILL, greenlist enabled and user in greenlist ', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- greenListableTester,
- mTokenToUsdDataFeed,
- accessControl,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redemptionVault.setGreenlistEnable(true);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem request with 10% growth is applied', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customFeedGrowth,
- } = await loadFixture(defaultDeploy);
-
- await mTokenToUsdDataFeed.changeAggregator(customFeedGrowth.address);
- await setRoundDataGrowth({ owner, customFeedGrowth }, 1, -1000, 10);
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem request with -10% growth is applied', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customFeedGrowth,
- } = await loadFixture(defaultDeploy);
-
- await mTokenToUsdDataFeed.changeAggregator(customFeedGrowth.address);
- await setMinGrowthApr({ owner, customFeedGrowth }, -10);
- await setRoundDataGrowth({ owner, customFeedGrowth }, 1, -1000, -10);
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem request 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- });
-
- it('redeem request 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$ and token fee 1%', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- });
-
- it('redeem request 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$ without checking of minDepositAmount', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await redemptionVault.freeFromMinAmount(owner.address, true);
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- });
-
- it('redeem request 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$ and user in waivedFeeRestriction', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await addWaivedFeeAccountTest(
- { vault: redemptionVault, owner },
- owner.address,
- );
- await redeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- waivedFee: true,
- },
- stableCoins.dai,
- 100,
- );
- });
- it('redeem request 100 mTBILL (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem request 100 mTBILL when recipient == msg.sender (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient: regularAccounts[0],
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem request 100 mTBILL when other fn overload is paused (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- redemptionVault,
- encodeFnSelector('redeemRequest(address,uint256)'),
- );
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem request 100 mTBILL when other fn overload is paused', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- redemptionVault,
- encodeFnSelector('redeemRequest(address,uint256,address)'),
- );
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
- });
-
- describe('redeemFiatRequest()', () => {
- it('should fail: when trying to redeem 0 amount', async () => {
- const {
- owner,
- redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 0,
- {
- from: regularAccounts[0],
- revertMessage: 'RV: invalid amount',
- },
- );
- });
-
- it('should fail: call with insufficient allowance', async () => {
- const {
- owner,
- redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100);
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 100,
- {
- revertMessage: 'ERC20: insufficient allowance',
- },
- );
- });
-
- it('should fail: when function paused', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- redemptionVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector('redeemFiatRequest(uint256)');
- await pauseVaultFn(redemptionVault, selector);
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: call with insufficient balance', async () => {
- const {
- owner,
- redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
-
- 100,
- {
- revertMessage: 'ERC20: transfer amount exceeds balance',
- },
- );
- });
-
- it('should fail: dataFeed rate 0 ', async () => {
- const {
- owner,
- redemptionVault,
- mTBILL,
- mockedAggregatorMToken,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100_000);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 0);
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 10,
- {
- revertMessage: 'DF: feed is deprecated',
- },
- );
- });
-
- it('should fail: call for amount < minFiatRedeemAmount', async () => {
- const {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100_000);
- await approveBase18(owner, mTBILL, redemptionVault, 100_000);
-
- await setMinFiatRedeemAmountTest({ redemptionVault, owner }, 100_000);
-
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 99_999,
- {
- revertMessage: 'RV: amount < min',
- },
- );
- });
-
- it('should fail: if some fee = 100%', async () => {
- const {
- owner,
- redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await setFiatAdditionalFeeTest({ redemptionVault, owner }, 10000);
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 100,
- {
- revertMessage: 'RV: amountMTokenIn < fee',
- },
- );
- });
-
- it('should fail: greenlist enabled and user not in greenlist ', async () => {
- const { owner, redemptionVault, mTBILL, mTokenToUsdDataFeed } =
- await loadFixture(defaultDeploy);
-
- await redemptionVault.setGreenlistEnable(true);
-
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: user in blacklist ', async () => {
- const {
- owner,
- redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- regularAccounts,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: user in sanctions list', async () => {
- const {
- owner,
- redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- regularAccounts[0],
- );
-
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('redeem fiat request 100 mTBILL, greenlist enabled and user in greenlist ', async () => {
- const {
- owner,
- redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await redemptionVault.setGreenlistEnable(true);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
-
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem fiat request 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 100,
- );
- });
-
- it('redeem fiat request 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$ and token fee 1%', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 100,
- );
- });
-
- it('redeem fiat request 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$ without checking of minDepositAmount', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await redemptionVault.freeFromMinAmount(owner.address, true);
-
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 100,
- );
- });
-
- it('redeem fiat request 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$ and user in waivedFeeRestriction', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await addWaivedFeeAccountTest(
- { vault: redemptionVault, owner },
- owner.address,
- );
- await redeemFiatRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- waivedFee: true,
- },
- 100,
- );
- });
- });
-
- describe('approveRequest()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const { redemptionVault, regularAccounts, mTokenToUsdDataFeed, mTBILL } =
- await loadFixture(defaultDeploy);
- await approveRedeemRequestTest(
- {
- redemptionVault,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 1,
- parseUnits('1'),
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await approveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 1,
- parseUnits('1'),
- {
- revertMessage: 'RV: request not exist',
- },
- );
- });
-
- it('should fail: request already processed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await approveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('1'),
- );
- await approveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('1'),
- { revertMessage: 'RV: request not pending' },
- );
- });
-
- it('approve request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await approveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('1'),
- );
- });
- });
-
- describe('approveRequest() with fiat', async () => {
- it('approve request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 100,
- );
- const requestId = 0;
-
- await changeTokenAllowanceTest(
- { vault: redemptionVault, owner },
- constants.AddressZero,
- parseUnits('100'),
- );
-
- await approveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('1'),
- );
- });
- });
-
- describe('safeApproveRequest()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const { redemptionVault, regularAccounts, mTokenToUsdDataFeed, mTBILL } =
- await loadFixture(defaultDeploy);
- await safeApproveRedeemRequestTest(
- {
- redemptionVault,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 1,
- parseUnits('1'),
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 1,
- parseUnits('1'),
- {
- revertMessage: 'RV: request not exist',
- },
- );
- });
-
- it('should fail: if new rate greater then variabilityTolerance', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('6'),
- { revertMessage: 'MV: exceed price diviation' },
- );
- });
-
- it('should fail: request already processed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('5.000001'),
- );
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('5.00001'),
- { revertMessage: 'RV: request not pending' },
- );
- });
-
- it('safe approve request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('5.000001'),
- );
- });
- });
-
- describe('safeBulkApproveRequestAtSavedRate()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const { redemptionVault, regularAccounts, mTokenToUsdDataFeed, mTBILL } =
- await loadFixture(defaultDeploy);
- await safeBulkApproveRequestTest(
- {
- redemptionVault,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: 1 }],
- 'request-rate',
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 1 }],
- 'request-rate',
- {
- revertMessage: 'RV: request not exist',
- },
- );
- });
-
- it('should fail: request already processed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: requestId }],
- 'request-rate',
- );
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: requestId }],
- 'request-rate',
- { revertMessage: 'RV: request not pending' },
- );
- });
-
- it('should fail: process multiple requests, when one of them already precessed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 200);
- await approveBase18(owner, mTBILL, redemptionVault, 200);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 0,
- parseUnits('5.000001'),
- );
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 1 }, { id: 0 }],
- 'request-rate',
- { revertMessage: 'RV: request not pending' },
- );
- });
-
- it('should fail: process multiple requests, when couple of them have equal id', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 200);
- await approveBase18(owner, mTBILL, redemptionVault, 200);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 0,
- parseUnits('5.000001'),
- );
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 1 }, { id: 1 }],
- 'request-rate',
- { revertMessage: 'RV: request not pending' },
- );
- });
-
- it('approve 1 request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: requestId }],
- 'request-rate',
- );
- });
-
- it('approve 2 request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 200);
- await approveBase18(owner, mTBILL, redemptionVault, 200);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 1 }, { id: 0 }],
- 'request-rate',
- );
- });
-
- it('approve 10 request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 1000);
- await approveBase18(owner, mTBILL, redemptionVault, 1000);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- for (let i = 0; i < 10; i++) {
- await redeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient: regularAccounts[i],
- },
- stableCoins.dai,
- 100,
- );
- }
-
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- Array.from({ length: 10 }, (_, i) => ({ id: i })),
- 'request-rate',
- );
- });
-
- it('approve 1 request when there is not enough liquidity', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: requestId, expectedToExecute: false }],
- 'request-rate',
- );
- });
-
- it('approve 2 request when there is enough liquidity only for first one', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 600);
-
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 600,
- );
- await mintToken(mTBILL, owner, 200);
- await approveBase18(owner, mTBILL, redemptionVault, 200);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [
- { id: 0, expectedToExecute: true },
- { id: 1, expectedToExecute: false },
- ],
- 'request-rate',
- );
- });
-
- it('approve 2 requests both with different payment tokens', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await mintToken(stableCoins.usdc, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await approveBase18(
- requestRedeemer,
- stableCoins.usdc,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 200);
- await approveBase18(owner, mTBILL, redemptionVault, 200);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.usdc,
- 100,
- );
-
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 1 }, { id: 0 }],
- 'request-rate',
- );
- });
-
- it('approve 2 requests both with different payment tokens when there is not enough liquidity for first one', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, requestRedeemer, 100000);
-
- await approveBase18(
- requestRedeemer,
- stableCoins.usdc,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 200);
- await approveBase18(owner, mTBILL, redemptionVault, 200);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.usdc,
- 100,
- );
-
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 1 }, { id: 0, expectedToExecute: false }],
- 'request-rate',
- );
- });
- });
-
- describe('safeBulkApproveRequest() (custom price overload)', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const { redemptionVault, regularAccounts, mTokenToUsdDataFeed, mTBILL } =
- await loadFixture(defaultDeploy);
- await safeBulkApproveRequestTest(
- {
- redemptionVault,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: 1 }],
- parseUnits('1'),
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 1 }],
- parseUnits('1'),
- {
- revertMessage: 'RV: request not exist',
- },
- );
- });
-
- it('should fail: if new rate greater then variabilityTolerance', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: requestId }],
- parseUnits('6'),
- { revertMessage: 'MV: exceed price diviation' },
- );
- });
-
- it('should fail: if new rate lower then variabilityTolerance', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: requestId }],
- parseUnits('4'),
- { revertMessage: 'MV: exceed price diviation' },
- );
- });
-
- it('should fail: request already processed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: requestId }],
- parseUnits('5.000001'),
- );
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: requestId }],
- parseUnits('5.00001'),
- { revertMessage: 'RV: request not pending' },
- );
- });
-
- it('should fail: process multiple requests, when one of them already precessed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 200);
- await approveBase18(owner, mTBILL, redemptionVault, 200);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 0,
- parseUnits('5.000001'),
- );
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 1 }, { id: 0 }],
- parseUnits('5.00001'),
- { revertMessage: 'RV: request not pending' },
- );
- });
-
- it('should fail: process multiple requests, when couple of them have equal id', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 200);
- await approveBase18(owner, mTBILL, redemptionVault, 200);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 0,
- parseUnits('5.000001'),
- );
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 1 }, { id: 1 }],
- parseUnits('5.00001'),
- { revertMessage: 'RV: request not pending' },
- );
- });
-
- it('approve 1 request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: requestId }],
- parseUnits('5.000001'),
- );
- });
-
- it('approve 2 request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 200);
- await approveBase18(owner, mTBILL, redemptionVault, 200);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 1 }, { id: 0 }],
- parseUnits('5.000001'),
- );
- });
-
- it('approve 10 request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 1000);
- await approveBase18(owner, mTBILL, redemptionVault, 1000);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- for (let i = 0; i < 10; i++) {
- await redeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient: regularAccounts[i],
- },
- stableCoins.dai,
- 100,
- );
- }
-
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- Array.from({ length: 10 }, (_, i) => ({ id: i })),
- parseUnits('5.000001'),
- );
- });
-
- it('approve 1 request when there is not enough liquidity', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: requestId, expectedToExecute: false }],
- parseUnits('5.000001'),
- );
- });
-
- it('approve 2 request when there is enough liquidity only for first one', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 600);
-
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 600,
- );
- await mintToken(mTBILL, owner, 200);
- await approveBase18(owner, mTBILL, redemptionVault, 200);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [
- { id: 0, expectedToExecute: true },
- { id: 1, expectedToExecute: false },
- ],
- parseUnits('5.000001'),
- );
- });
-
- it('approve 2 requests both with different payment tokens', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await mintToken(stableCoins.usdc, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await approveBase18(
- requestRedeemer,
- stableCoins.usdc,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 200);
- await approveBase18(owner, mTBILL, redemptionVault, 200);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.usdc,
- 100,
- );
-
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 1 }, { id: 0 }],
- parseUnits('5.000001'),
- );
- });
-
- it('approve 2 requests both with different payment tokens when there is not enough liquidity for first one', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, requestRedeemer, 100000);
-
- await approveBase18(
- requestRedeemer,
- stableCoins.usdc,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 200);
- await approveBase18(owner, mTBILL, redemptionVault, 200);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.usdc,
- 100,
- );
-
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 1 }, { id: 0, expectedToExecute: false }],
- parseUnits('5.000001'),
- );
- });
- });
-
- describe('safeBulkApproveRequest() (current price overload)', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const { redemptionVault, regularAccounts, mTokenToUsdDataFeed, mTBILL } =
- await loadFixture(defaultDeploy);
- await safeBulkApproveRequestTest(
- {
- redemptionVault,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- [{ id: 1 }],
- undefined,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 1 }],
- undefined,
- {
- revertMessage: 'RV: request not exist',
- },
- );
- });
-
- it('should fail: if new rate greater then variabilityTolerance', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 6);
-
- const requestId = 0;
-
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: requestId }],
- undefined,
- { revertMessage: 'MV: exceed price diviation' },
- );
- });
-
- it('should fail: if new rate lower then variabilityTolerance', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 4);
-
- const requestId = 0;
-
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: requestId }],
- undefined,
- { revertMessage: 'MV: exceed price diviation' },
- );
- });
-
- it('should fail: request already processed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: requestId }],
- undefined,
- );
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: requestId }],
- undefined,
- { revertMessage: 'RV: request not pending' },
- );
- });
-
- it('should fail: process multiple requests, when one of them already precessed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 200);
- await approveBase18(owner, mTBILL, redemptionVault, 200);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 0,
- parseUnits('5.000001'),
- );
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 1 }, { id: 0 }],
- undefined,
- { revertMessage: 'RV: request not pending' },
- );
- });
-
- it('should fail: process multiple requests, when couple of them have equal id', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 200);
- await approveBase18(owner, mTBILL, redemptionVault, 200);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 0,
- parseUnits('5.000001'),
- );
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 1 }, { id: 1 }],
- undefined,
- { revertMessage: 'RV: request not pending' },
- );
- });
-
- it('approve 1 request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: requestId }],
- undefined,
- );
- });
-
- it('approve 1 request from vaut admin account when 10% growth is applied', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- customFeedGrowth,
- } = await loadFixture(defaultDeploy);
-
- await mTokenToUsdDataFeed.changeAggregator(customFeedGrowth.address);
- await setRoundDataGrowth({ owner, customFeedGrowth }, 1, -1000, 10);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: requestId }],
- undefined,
- );
- });
-
- it('approve 1 request from vaut admin account when -10% growth is applied', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- customFeedGrowth,
- } = await loadFixture(defaultDeploy);
-
- await mTokenToUsdDataFeed.changeAggregator(customFeedGrowth.address);
- await setMinGrowthApr({ owner, customFeedGrowth }, -10);
- await setRoundDataGrowth({ owner, customFeedGrowth }, 1, -1000, -10);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: requestId }],
- undefined,
- );
- });
-
- it('approve 2 request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 200);
- await approveBase18(owner, mTBILL, redemptionVault, 200);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 1 }, { id: 0 }],
- undefined,
- );
- });
-
- it('approve 10 request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 1000);
- await approveBase18(owner, mTBILL, redemptionVault, 1000);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- for (let i = 0; i < 10; i++) {
- await redeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient: regularAccounts[i],
- },
- stableCoins.dai,
- 100,
- );
- }
-
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- Array.from({ length: 10 }, (_, i) => ({ id: i })),
- undefined,
- );
- });
-
- it('approve 1 request when there is not enough liquidity', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: requestId, expectedToExecute: false }],
- undefined,
- );
- });
-
- it('approve 2 request when there is enough liquidity only for first one', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 600);
-
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 600,
- );
- await mintToken(mTBILL, owner, 200);
- await approveBase18(owner, mTBILL, redemptionVault, 200);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [
- { id: 0, expectedToExecute: true },
- { id: 1, expectedToExecute: false },
- ],
- undefined,
- );
- });
-
- it('approve 2 requests both with different payment tokens', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await mintToken(stableCoins.usdc, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await approveBase18(
- requestRedeemer,
- stableCoins.usdc,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 200);
- await approveBase18(owner, mTBILL, redemptionVault, 200);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.usdc,
- 100,
- );
-
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 1 }, { id: 0 }],
- undefined,
- );
- });
-
- it('approve 2 requests both with different payment tokens when there is not enough liquidity for first one', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, requestRedeemer, 100000);
-
- await approveBase18(
- requestRedeemer,
- stableCoins.usdc,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 200);
- await approveBase18(owner, mTBILL, redemptionVault, 200);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.usdc,
- 100,
- );
-
- await safeBulkApproveRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- [{ id: 1 }, { id: 0, expectedToExecute: false }],
- undefined,
- );
- });
- });
-
- describe('rejectRequest()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const { redemptionVault, regularAccounts, mTokenToUsdDataFeed, mTBILL } =
- await loadFixture(defaultDeploy);
- await rejectRedeemRequestTest(
- {
- redemptionVault,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await rejectRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 1,
- {
- revertMessage: 'RV: request not exist',
- },
- );
- });
-
- it('should fail: request already processed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await rejectRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- );
- await rejectRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- { revertMessage: 'RV: request not pending' },
- );
- });
-
- it('safe approve request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await rejectRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- );
- });
- });
-
- describe('redeemInstant() complex', () => {
- it('should fail: when is paused', async () => {
- const {
- redemptionVault,
- owner,
- mTBILL,
- stableCoins,
- regularAccounts,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVault(redemptionVault);
- await mintToken(stableCoins.dai, redemptionVault, 100);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- redemptionVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: paused',
- },
- );
- });
-
- it('is on pause, but admin can use everything', async () => {
- const {
- redemptionVault,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVault(redemptionVault);
-
- await mintToken(stableCoins.dai, redemptionVault, 100);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, stableCoins.dai, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'Pausable: paused',
- },
- );
- });
-
- it('call for amount == minAmount', async () => {
- const {
- redemptionVault,
- mockedAggregator,
- mockedAggregatorMToken,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mTBILL, owner, 100_000);
- await mintToken(stableCoins.dai, redemptionVault, 100_000);
- await approveBase18(owner, mTBILL, redemptionVault, 100_000);
-
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setMinAmountTest({ vault: redemptionVault, owner }, 100_000);
-
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100_000,
- );
- });
-
- it('redeem 100 mtbill, when price is 5$, 125 mtbill when price is 5.1$, 114 mtbill when price is 5.4$', async () => {
- const {
- owner,
- mockedAggregator,
- redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- mockedAggregatorMToken,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100);
- await mintToken(mTBILL, owner, 125);
- await mintToken(mTBILL, owner, 114);
-
- await mintToken(stableCoins.dai, redemptionVault, 1000);
- await mintToken(stableCoins.usdc, redemptionVault, 1250);
- await mintToken(stableCoins.usdt, redemptionVault, 1140);
-
- await approveBase18(owner, mTBILL, redemptionVault, 100 + 125 + 114);
-
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.usdt,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setRoundData({ mockedAggregator }, 1.04);
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
-
- await setRoundData({ mockedAggregator }, 1);
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.usdc,
- 125,
- );
-
- await setRoundData({ mockedAggregator }, 1.01);
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.usdt,
- 114,
- );
- });
- });
-
- describe('redeemRequest() complex', () => {
- it('should fail: when is paused', async () => {
- const {
- redemptionVault,
- owner,
- mTBILL,
- stableCoins,
- regularAccounts,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVault(redemptionVault);
- await mintToken(stableCoins.dai, redemptionVault, 100);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- redemptionVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: paused',
- },
- );
- });
-
- it('is on pause, but admin can use everything', async () => {
- const {
- redemptionVault,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVault(redemptionVault);
-
- await mintToken(stableCoins.dai, redemptionVault, 1000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, stableCoins.dai, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'Pausable: paused',
- },
- );
- });
-
- it('call for amount == minAmount, then approve', async () => {
- const {
- redemptionVault,
- mockedAggregator,
- mockedAggregatorMToken,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mTBILL, owner, 100_000);
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await approveBase18(owner, mTBILL, redemptionVault, 100_000);
-
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setMinAmountTest({ vault: redemptionVault, owner }, 100_000);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100_000,
- );
-
- const requestId = 0;
-
- await approveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('1'),
- );
- });
-
- it('call for amount == minAmount, then safe approve', async () => {
- const {
- redemptionVault,
- mockedAggregator,
- mockedAggregatorMToken,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mTBILL, owner, 100_000);
- await mintToken(stableCoins.dai, requestRedeemer, 1000000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 1000000,
- );
- await approveBase18(owner, mTBILL, redemptionVault, 100_000);
-
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setMinAmountTest({ vault: redemptionVault, owner }, 100_000);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100_000,
- );
-
- const requestId = 0;
-
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('1.000001'),
- );
- });
-
- it('call for amount == minAmount, then reject', async () => {
- const {
- redemptionVault,
- mockedAggregator,
- mockedAggregatorMToken,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mTBILL, owner, 100_000);
- await mintToken(stableCoins.dai, redemptionVault, 100_000);
- await approveBase18(owner, mTBILL, redemptionVault, 100_000);
-
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setMinAmountTest({ vault: redemptionVault, owner }, 100_000);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100_000,
- );
-
- const requestId = 0;
-
- await rejectRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- );
- });
- });
-
- describe('_convertUsdToToken', () => {
- it('should fail: when amountUsd == 0', async () => {
- const { redemptionVault } = await loadFixture(defaultDeploy);
-
- await expect(
- redemptionVault.convertUsdToTokenTest(0, constants.AddressZero),
- ).revertedWith('RV: amount zero');
- });
-
- it('should fail: when tokenRate == 0', async () => {
- const { redemptionVault } = await loadFixture(defaultDeploy);
-
- await redemptionVault.setOverrideGetTokenRate(true);
- await redemptionVault.setGetTokenRateValue(0);
-
- await expect(
- redemptionVault.convertUsdToTokenTest(1, constants.AddressZero),
- ).revertedWith('RV: rate zero');
- });
- });
-
- describe('_convertMTokenToUsd', () => {
- it('should fail: when amountMToken == 0', async () => {
- const { redemptionVault } = await loadFixture(defaultDeploy);
-
- await expect(redemptionVault.convertMTokenToUsdTest(0)).revertedWith(
- 'RV: amount zero',
- );
- });
-
- it('should fail: when amountMToken == 0', async () => {
- const { redemptionVault } = await loadFixture(defaultDeploy);
-
- await redemptionVault.setOverrideGetTokenRate(true);
- await redemptionVault.setGetTokenRateValue(0);
-
- await expect(redemptionVault.convertMTokenToUsdTest(1)).revertedWith(
- 'RV: rate zero',
- );
- });
- });
-
- describe('_calcAndValidateRedeem', () => {
- it('should fail: when tokenOut is not MANUAL_FULLFILMENT_TOKEN but isFiat = true', async () => {
- const { redemptionVault, stableCoins } = await loadFixture(defaultDeploy);
-
- await expect(
- redemptionVault.calcAndValidateRedeemTest(
- constants.AddressZero,
- stableCoins.dai.address,
- parseUnits('100'),
- false,
- true,
- ),
- ).revertedWith('RV: tokenOut != fiat');
- });
- });
-});
+ },
+);
diff --git a/test/unit/RedemptionVaultWithAave.test.ts b/test/unit/RedemptionVaultWithAave.test.ts
index 72869bae..024350de 100644
--- a/test/unit/RedemptionVaultWithAave.test.ts
+++ b/test/unit/RedemptionVaultWithAave.test.ts
@@ -1,2703 +1,1180 @@
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
+import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { expect } from 'chai';
import { constants } from 'ethers';
import { parseUnits } from 'ethers/lib/utils';
import { ethers } from 'hardhat';
+import {
+ baseInitParamsRv,
+ redemptionVaultSuits,
+} from './suits/redemption-vault.suits';
+
import { encodeFnSelector } from '../../helpers/utils';
import {
- ManageableVaultTester__factory,
+ RedemptionVaultWithAave__factory,
RedemptionVaultWithAaveTest__factory,
} from '../../typechain-types';
-import { acErrors, blackList, greenList } from '../common/ac.helpers';
+import { acErrors } from '../common/ac.helpers';
import {
approveBase18,
mintToken,
pauseVaultFn,
+ validateImplementation,
} from '../common/common.helpers';
import { setRoundData } from '../common/data-feed.helpers';
import { defaultDeploy } from '../common/fixtures';
import {
addPaymentTokenTest,
setInstantFeeTest,
- setMinAmountTest,
- setInstantDailyLimitTest,
- addWaivedFeeAccountTest,
- removeWaivedFeeAccountTest,
- setVariabilityToleranceTest,
- removePaymentTokenTest,
- withdrawTest,
- changeTokenFeeTest,
- changeTokenAllowanceTest,
+ setWaivedFeeAccountTest,
} from '../common/manageable-vault.helpers';
import {
- approveRedeemRequestTest,
- redeemFiatRequestTest,
+ removeAavePoolTest,
+ setAavePoolTest,
+} from '../common/redemption-vault-aave.helpers';
+import {
redeemInstantTest,
- redeemRequestTest,
- rejectRedeemRequestTest,
- safeApproveRedeemRequestTest,
- setFiatAdditionalFeeTest,
- setMinFiatRedeemAmountTest,
+ setPreferLoanLiquidityTest,
+ setLoanLpTest,
} from '../common/redemption-vault.helpers';
-import { sanctionUser } from '../common/with-sanctions-list.helpers';
-
-describe('RedemptionVaultWithAave', function () {
- it('deployment', async () => {
- const {
- redemptionVaultWithAave,
- aavePoolMock,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- stableCoins,
- roles,
- } = await loadFixture(defaultDeploy);
-
- expect(await redemptionVaultWithAave.mToken()).eq(mTBILL.address);
-
- expect(await redemptionVaultWithAave.ONE_HUNDRED_PERCENT()).eq('10000');
-
- expect(await redemptionVaultWithAave.paused()).eq(false);
-
- expect(await redemptionVaultWithAave.tokensReceiver()).eq(
- tokensReceiver.address,
- );
- expect(await redemptionVaultWithAave.feeReceiver()).eq(feeReceiver.address);
-
- expect(await redemptionVaultWithAave.minAmount()).eq(1000);
- expect(await redemptionVaultWithAave.minFiatRedeemAmount()).eq(1000);
-
- expect(await redemptionVaultWithAave.instantFee()).eq('100');
-
- expect(await redemptionVaultWithAave.instantDailyLimit()).eq(
- parseUnits('100000'),
- );
-
- expect(await redemptionVaultWithAave.mTokenDataFeed()).eq(
- mTokenToUsdDataFeed.address,
- );
- expect(await redemptionVaultWithAave.variationTolerance()).eq(1);
-
- expect(await redemptionVaultWithAave.vaultRole()).eq(
- roles.tokenRoles.mTBILL.redemptionVaultAdmin,
- );
-
- expect(await redemptionVaultWithAave.MANUAL_FULLFILMENT_TOKEN()).eq(
- ethers.constants.AddressZero,
- );
-
+import { initializeRvWithAave } from '../common/vault-initializer.helpers';
+
+redemptionVaultSuits(
+ 'RedemptionVaultWithAave',
+ defaultDeploy,
+ {
+ createNew: async (owner: SignerWithAddress) =>
+ new RedemptionVaultWithAaveTest__factory(owner).deploy(),
+ key: 'redemptionVaultWithAave',
+ },
+ async (fixture) => {
+ const { redemptionVaultWithAave, stableCoins, aavePoolMock } = fixture;
expect(
await redemptionVaultWithAave.aavePools(stableCoins.usdc.address),
).eq(aavePoolMock.address);
- });
-
- it('failing deployment', async () => {
- const {
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- accessControl,
- mockedSanctionsList,
- owner,
- } = await loadFixture(defaultDeploy);
-
- const redemptionVaultWithAave =
- await new RedemptionVaultWithAaveTest__factory(owner).deploy();
-
- await expect(
- redemptionVaultWithAave.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- {
- fiatAdditionalFee: 10000,
- fiatFlatFee: parseUnits('1'),
- minFiatRedeemAmount: parseUnits('100'),
- },
- constants.AddressZero,
- ),
- ).to.be.reverted;
- });
-
- describe('initialization', () => {
- it('should fail: call initialize() when already initialized', async () => {
- const { redemptionVaultWithAave } = await loadFixture(defaultDeploy);
-
- await expect(
- redemptionVaultWithAave.initialize(
- constants.AddressZero,
- {
- mToken: constants.AddressZero,
- mTokenDataFeed: constants.AddressZero,
- },
- {
- feeReceiver: constants.AddressZero,
- tokensReceiver: constants.AddressZero,
- },
- {
- instantFee: 0,
- instantDailyLimit: 0,
- },
- constants.AddressZero,
- 0,
- 0,
- {
- fiatAdditionalFee: 0,
- fiatFlatFee: 0,
- minFiatRedeemAmount: 0,
- },
- constants.AddressZero,
- ),
- ).revertedWith('Initializable: contract is already initialized');
- });
-
- it('should fail: call with initializing == false', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initializeWithoutInitializer(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- 1000,
- ),
- ).revertedWith('Initializable: contract is not initializing');
- });
- });
-
- describe('setAavePool()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- redemptionVaultWithAave,
- regularAccounts,
- stableCoins,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
- await expect(
- redemptionVaultWithAave
- .connect(regularAccounts[0])
- .setAavePool(stableCoins.usdc.address, aavePoolMock.address),
- ).to.be.revertedWith('WMAC: hasnt role');
- });
-
- it('should fail: zero address', async () => {
- const { redemptionVaultWithAave, stableCoins } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- redemptionVaultWithAave.setAavePool(
- stableCoins.usdc.address,
- constants.AddressZero,
- ),
- ).to.be.revertedWith('zero address');
- });
-
- it('should succeed and emit SetAavePool event', async () => {
- const { redemptionVaultWithAave, owner, stableCoins, aavePoolMock } =
- await loadFixture(defaultDeploy);
-
- await expect(
- redemptionVaultWithAave.setAavePool(
- stableCoins.usdc.address,
- aavePoolMock.address,
- ),
- )
- .to.emit(redemptionVaultWithAave, 'SetAavePool')
- .withArgs(
- owner.address,
- stableCoins.usdc.address,
- aavePoolMock.address,
- );
-
- expect(
- await redemptionVaultWithAave.aavePools(stableCoins.usdc.address),
- ).eq(aavePoolMock.address);
- });
- });
-
- describe('removeAavePool()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { redemptionVaultWithAave, regularAccounts, stableCoins } =
- await loadFixture(defaultDeploy);
- await expect(
- redemptionVaultWithAave
- .connect(regularAccounts[0])
- .removeAavePool(stableCoins.usdc.address),
- ).to.be.revertedWith('WMAC: hasnt role');
- });
-
- it('should fail: pool not set', async () => {
- const { redemptionVaultWithAave, stableCoins } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- redemptionVaultWithAave.removeAavePool(stableCoins.dai.address),
- ).to.be.revertedWith('RVA: pool not set');
- });
-
- it('should succeed and emit RemoveAavePool event', async () => {
- const { redemptionVaultWithAave, owner, stableCoins } = await loadFixture(
- defaultDeploy,
- );
-
- await expect(
- redemptionVaultWithAave.removeAavePool(stableCoins.usdc.address),
- )
- .to.emit(redemptionVaultWithAave, 'RemoveAavePool')
- .withArgs(owner.address, stableCoins.usdc.address);
-
- expect(
- await redemptionVaultWithAave.aavePools(stableCoins.usdc.address),
- ).eq(constants.AddressZero);
- });
- });
-
- describe('setMinAmount()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { owner, redemptionVaultWithAave, regularAccounts } =
- await loadFixture(defaultDeploy);
-
- await setMinAmountTest({ vault: redemptionVaultWithAave, owner }, 1.1, {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
+ await validateImplementation(RedemptionVaultWithAave__factory);
+ },
+ {
+ deployUninitialized: (fixture) =>
+ new RedemptionVaultWithAaveTest__factory(fixture.owner).deploy(),
+ initialize: async (fixture, params, opt) => {
+ await initializeRvWithAave(
+ { ...baseInitParamsRv(fixture), ...params },
+ opt?.contract,
+ opt,
+ );
+ },
+ },
+ (defaultDeploy) => {
+ describe('RedemptionVaultWithAave', () => {
+ describe('setAavePool()', () => {
+ it('should fail: call from address without vault admin role', async () => {
+ const {
+ redemptionVaultWithAave,
+ regularAccounts,
+ stableCoins,
+ aavePoolMock,
+ } = await loadFixture(defaultDeploy);
+ await expect(
+ redemptionVaultWithAave
+ .connect(regularAccounts[0])
+ .setAavePool(stableCoins.usdc.address, aavePoolMock.address),
+ ).to.be.revertedWithCustomError(
+ redemptionVaultWithAave,
+ acErrors.WMAC_HASNT_PERMISSION().customErrorName,
+ );
+ });
+
+ it('should fail: zero address', async () => {
+ const { redemptionVaultWithAave, stableCoins } = await loadFixture(
+ defaultDeploy,
+ );
+ await expect(
+ redemptionVaultWithAave.setAavePool(
+ stableCoins.usdc.address,
+ constants.AddressZero,
+ ),
+ ).to.be.revertedWithCustomError(
+ redemptionVaultWithAave,
+ 'InvalidAddress',
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const {
+ redemptionVaultWithAave,
+ owner,
+ stableCoins,
+ aavePoolMock,
+ pauseManager,
+ } = await loadFixture(defaultDeploy);
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVaultWithAave,
+ encodeFnSelector('setAavePool(address,address)'),
+ );
+ await setAavePoolTest(
+ { redemptionVault: redemptionVaultWithAave, owner },
+ stableCoins.usdc.address,
+ aavePoolMock.address,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('should succeed and emit SetAavePool event', async () => {
+ const { redemptionVaultWithAave, owner, stableCoins, aavePoolMock } =
+ await loadFixture(defaultDeploy);
+
+ await expect(
+ redemptionVaultWithAave.setAavePool(
+ stableCoins.usdc.address,
+ aavePoolMock.address,
+ ),
+ )
+ .to.emit(redemptionVaultWithAave, 'SetAavePool')
+ .withArgs(stableCoins.usdc.address, aavePoolMock.address);
+
+ expect(
+ await redemptionVaultWithAave.aavePools(stableCoins.usdc.address),
+ ).eq(aavePoolMock.address);
+ });
});
- });
-
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithAave } = await loadFixture(
- defaultDeploy,
- );
- await setMinAmountTest({ vault: redemptionVaultWithAave, owner }, 1.1);
- });
- });
-
- describe('setMinFiatRedeemAmount()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { owner, redemptionVaultWithAave, regularAccounts } =
- await loadFixture(defaultDeploy);
-
- await setMinFiatRedeemAmountTest(
- { redemptionVault: redemptionVaultWithAave, owner },
- 1.1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- },
- );
- });
-
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithAave } = await loadFixture(
- defaultDeploy,
- );
- await setMinFiatRedeemAmountTest(
- { redemptionVault: redemptionVaultWithAave, owner },
- 1.1,
- );
- });
- });
-
- describe('setFiatAdditionalFee()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { owner, redemptionVaultWithAave, regularAccounts } =
- await loadFixture(defaultDeploy);
-
- await setFiatAdditionalFeeTest(
- { redemptionVault: redemptionVaultWithAave, owner },
- 100,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- },
- );
- });
-
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithAave } = await loadFixture(
- defaultDeploy,
- );
- await setFiatAdditionalFeeTest(
- { redemptionVault: redemptionVaultWithAave, owner },
- 100,
- );
- });
- });
-
- describe('setInstantDailyLimit()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { owner, redemptionVaultWithAave, regularAccounts } =
- await loadFixture(defaultDeploy);
-
- await setInstantDailyLimitTest(
- { vault: redemptionVaultWithAave, owner },
- parseUnits('1000'),
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- },
- );
- });
-
- it('should fail: try to set 0 limit', async () => {
- const { owner, redemptionVaultWithAave } = await loadFixture(
- defaultDeploy,
- );
-
- await setInstantDailyLimitTest(
- { vault: redemptionVaultWithAave, owner },
- constants.Zero,
- {
- revertMessage: 'MV: limit zero',
- },
- );
- });
-
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithAave } = await loadFixture(
- defaultDeploy,
- );
- await setInstantDailyLimitTest(
- { vault: redemptionVaultWithAave, owner },
- parseUnits('1000'),
- );
- });
- });
-
- describe('addPaymentToken()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { owner, redemptionVaultWithAave, regularAccounts } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- ethers.constants.AddressZero,
- ethers.constants.AddressZero,
- 0,
- true,
- constants.MaxUint256,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
-
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithAave, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- });
- });
-
- describe('removePaymentToken()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { owner, redemptionVaultWithAave, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await removePaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.dai,
- {
- from: (await ethers.getSigners())[10],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- },
- );
- });
-
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithAave, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await removePaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.dai,
- );
- });
- });
-
- describe('addWaivedFeeAccount()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { owner, redemptionVaultWithAave, regularAccounts } =
- await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithAave, owner },
- regularAccounts[0].address,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- },
- );
- });
-
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithAave, regularAccounts } =
- await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithAave, owner },
- regularAccounts[0].address,
- );
- });
- });
-
- describe('removeWaivedFeeAccount()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { owner, redemptionVaultWithAave, regularAccounts } =
- await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithAave, owner },
- regularAccounts[0].address,
- );
- await removeWaivedFeeAccountTest(
- { vault: redemptionVaultWithAave, owner },
- regularAccounts[0].address,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- },
- );
- });
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithAave, regularAccounts } =
- await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithAave, owner },
- regularAccounts[0].address,
- );
- await removeWaivedFeeAccountTest(
- { vault: redemptionVaultWithAave, owner },
- regularAccounts[0].address,
- );
- });
- });
-
- describe('setFee()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { owner, redemptionVaultWithAave, regularAccounts } =
- await loadFixture(defaultDeploy);
- await setInstantFeeTest({ vault: redemptionVaultWithAave, owner }, 100, {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
+ describe('removeAavePool()', () => {
+ it('should fail: call from address without vault admin role', async () => {
+ const { redemptionVaultWithAave, regularAccounts, stableCoins } =
+ await loadFixture(defaultDeploy);
+ await expect(
+ redemptionVaultWithAave
+ .connect(regularAccounts[0])
+ .removeAavePool(stableCoins.usdc.address),
+ ).to.be.revertedWithCustomError(
+ redemptionVaultWithAave,
+ acErrors.WMAC_HASNT_PERMISSION().customErrorName,
+ );
+ });
+
+ it('should fail: pool not set', async () => {
+ const { redemptionVaultWithAave, stableCoins } = await loadFixture(
+ defaultDeploy,
+ );
+ await expect(
+ redemptionVaultWithAave.removeAavePool(stableCoins.dai.address),
+ ).to.be.revertedWithCustomError(
+ redemptionVaultWithAave,
+ 'PoolNotSet',
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { redemptionVaultWithAave, owner, stableCoins, pauseManager } =
+ await loadFixture(defaultDeploy);
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVaultWithAave,
+ encodeFnSelector('removeAavePool(address)'),
+ );
+ await removeAavePoolTest(
+ { redemptionVault: redemptionVaultWithAave, owner },
+ stableCoins.usdc.address,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('should succeed and emit RemoveAavePool event', async () => {
+ const { redemptionVaultWithAave, owner, stableCoins } =
+ await loadFixture(defaultDeploy);
+
+ await expect(
+ redemptionVaultWithAave.removeAavePool(stableCoins.usdc.address),
+ )
+ .to.emit(redemptionVaultWithAave, 'RemoveAavePool')
+ .withArgs(stableCoins.usdc.address);
+
+ expect(
+ await redemptionVaultWithAave.aavePools(stableCoins.usdc.address),
+ ).eq(constants.AddressZero);
+ });
});
- });
-
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithAave } = await loadFixture(
- defaultDeploy,
- );
- await setInstantFeeTest({ vault: redemptionVaultWithAave, owner }, 100);
- });
- });
-
- describe('setVariabilityTolerance()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { owner, redemptionVaultWithAave, regularAccounts } =
- await loadFixture(defaultDeploy);
- await setVariabilityToleranceTest(
- { vault: redemptionVaultWithAave, owner },
- 100,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- },
- );
- });
-
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithAave } = await loadFixture(
- defaultDeploy,
- );
- await setVariabilityToleranceTest(
- { vault: redemptionVaultWithAave, owner },
- 100,
- );
- });
- });
-
- describe('withdrawToken()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { owner, redemptionVaultWithAave, stableCoins, regularAccounts } =
- await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, redemptionVaultWithAave, 1);
- await withdrawTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.dai,
- 1,
- regularAccounts[0],
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- },
- );
- });
-
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithAave, stableCoins, regularAccounts } =
- await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, redemptionVaultWithAave, 1);
- await withdrawTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.dai,
- 1,
- regularAccounts[0],
- );
- });
- });
-
- describe('freeFromMinAmount()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const { redemptionVaultWithAave, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- redemptionVaultWithAave
- .connect(regularAccounts[0])
- .freeFromMinAmount(regularAccounts[1].address, true),
- ).to.be.revertedWith('WMAC: hasnt role');
- });
- it('should not fail', async () => {
- const { redemptionVaultWithAave, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- redemptionVaultWithAave.freeFromMinAmount(
- regularAccounts[0].address,
- true,
- ),
- ).to.not.reverted;
-
- expect(
- await redemptionVaultWithAave.isFreeFromMinAmount(
- regularAccounts[0].address,
- ),
- ).to.eq(true);
- });
- });
-
- describe('changeTokenAllowance()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { owner, redemptionVaultWithAave, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.dai.address,
- 100,
- {
- from: (await ethers.getSigners())[10],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- },
- );
- });
-
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithAave, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.dai.address,
- 100,
- );
- });
- });
-
- describe('changeTokenFee()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { owner, redemptionVaultWithAave, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenFeeTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.dai.address,
- 100,
- {
- from: (await ethers.getSigners())[10],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- },
- );
- });
-
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithAave, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenFeeTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.dai.address,
- 100,
- );
- });
- });
-
- describe('checkAndRedeemAave()', () => {
- it('should not withdraw from Aave when contract has enough balance', async () => {
- const { redemptionVaultWithAave, stableCoins, aUSDC } = await loadFixture(
- defaultDeploy,
- );
-
- const usdcAmount = parseUnits('1000', 8);
- await stableCoins.usdc.mint(redemptionVaultWithAave.address, usdcAmount);
-
- const balanceBefore = await stableCoins.usdc.balanceOf(
- redemptionVaultWithAave.address,
- );
- const aTokenBefore = await aUSDC.balanceOf(
- redemptionVaultWithAave.address,
- );
-
- await redemptionVaultWithAave.checkAndRedeemAave(
- stableCoins.usdc.address,
- parseUnits('500', 8),
- );
-
- const balanceAfter = await stableCoins.usdc.balanceOf(
- redemptionVaultWithAave.address,
- );
- const aTokenAfter = await aUSDC.balanceOf(
- redemptionVaultWithAave.address,
- );
- expect(balanceAfter).to.equal(balanceBefore);
- expect(aTokenAfter).to.equal(aTokenBefore);
- });
-
- it('should withdraw missing amount from Aave', async () => {
- const { redemptionVaultWithAave, stableCoins, aUSDC } = await loadFixture(
- defaultDeploy,
- );
-
- // Vault has 500 USDC, needs 1000
- const initialUsdc = parseUnits('500', 8);
- await stableCoins.usdc.mint(redemptionVaultWithAave.address, initialUsdc);
-
- // Vault has 600 aUSDC
- const aTokenAmount = parseUnits('600', 8);
- await aUSDC.mint(redemptionVaultWithAave.address, aTokenAmount);
-
- await redemptionVaultWithAave.checkAndRedeemAave(
- stableCoins.usdc.address,
- parseUnits('1000', 8),
- );
-
- // Vault should now have 1000 USDC (500 original + 500 withdrawn from Aave)
- const usdcAfter = await stableCoins.usdc.balanceOf(
- redemptionVaultWithAave.address,
- );
- expect(usdcAfter).to.equal(parseUnits('1000', 8));
-
- // aToken balance should decrease by 500
- const aTokenAfter = await aUSDC.balanceOf(
- redemptionVaultWithAave.address,
- );
- expect(aTokenAfter).to.equal(parseUnits('100', 8));
- });
-
- it('should revert when no pool set for token', async () => {
- const { redemptionVaultWithAave, stableCoins } = await loadFixture(
- defaultDeploy,
- );
-
- await expect(
- redemptionVaultWithAave.checkAndRedeemAave(
- stableCoins.dai.address,
- parseUnits('1000', 9),
- ),
- ).to.be.revertedWith('RVA: no pool for token');
- });
-
- it('should revert when contract has insufficient aToken balance', async () => {
- const { redemptionVaultWithAave, stableCoins, aUSDC } = await loadFixture(
- defaultDeploy,
- );
-
- // Vault has 200 USDC, needs 1000
- await stableCoins.usdc.mint(
- redemptionVaultWithAave.address,
- parseUnits('200', 8),
- );
-
- // Vault has only 300 aUSDC (not enough for 800 missing)
- await aUSDC.mint(redemptionVaultWithAave.address, parseUnits('300', 8));
-
- await expect(
- redemptionVaultWithAave.checkAndRedeemAave(
- stableCoins.usdc.address,
- parseUnits('1000', 8),
- ),
- ).to.be.revertedWith('RVA: insufficient aToken balance');
- });
-
- it('should revert when Aave pool has insufficient underlying liquidity', async () => {
- const { redemptionVaultWithAave, stableCoins, aUSDC, aavePoolMock } =
- await loadFixture(defaultDeploy);
-
- // Vault needs to withdraw from Aave
- await stableCoins.usdc.mint(
- redemptionVaultWithAave.address,
- parseUnits('200', 8),
- );
-
- // Vault has enough aTokens
- await aUSDC.mint(redemptionVaultWithAave.address, parseUnits('1000', 8));
-
- // Drain the pool's USDC
- const poolBalance = await stableCoins.usdc.balanceOf(
- aavePoolMock.address,
- );
- await aavePoolMock.withdrawAdmin(
- stableCoins.usdc.address,
- (
- await ethers.getSigners()
- )[10].address,
- poolBalance,
- );
-
- await expect(
- redemptionVaultWithAave.checkAndRedeemAave(
- stableCoins.usdc.address,
- parseUnits('1000', 8),
- ),
- ).to.be.revertedWith('AaveV3PoolMock: InsufficientLiquidity');
- });
-
- it('should revert when Aave withdraws less than missing amount', async () => {
- const { redemptionVaultWithAave, stableCoins, aUSDC, aavePoolMock } =
- await loadFixture(defaultDeploy);
-
- // Vault has 200 USDC, needs 1000
- await stableCoins.usdc.mint(
- redemptionVaultWithAave.address,
- parseUnits('200', 8),
- );
-
- // Vault has enough aTokens to cover the gap
- await aUSDC.mint(redemptionVaultWithAave.address, parseUnits('1000', 8));
-
- // Simulate partial Aave withdrawal
- await aavePoolMock.setWithdrawReturnBps(5000);
-
- await expect(
- redemptionVaultWithAave.checkAndRedeemAave(
- stableCoins.usdc.address,
- parseUnits('1000', 8),
- ),
- ).to.be.revertedWith('RVA: withdrawn < needed');
- });
- });
-
- describe('redeemInstant()', () => {
- it('should fail: when there is no token in vault', async () => {
- const {
- owner,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 1,
- {
- revertMessage: 'MV: token not exists',
- },
- );
- });
-
- it('should fail: when trying to redeem 0 amount', async () => {
- const {
- owner,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 0,
- {
- revertMessage: 'RV: invalid amount',
- },
- );
- });
-
- it('should fail: when function paused', async () => {
- const {
- owner,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- redemptionVaultWithAave,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector(
- 'redeemInstant(address,uint256,uint256)',
- );
- await pauseVaultFn(redemptionVaultWithAave, selector);
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: call with insufficient allowance', async () => {
- const {
- owner,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- revertMessage: 'ERC20: insufficient allowance',
- },
- );
- });
-
- it('should fail: call with insufficient balance', async () => {
- const {
- owner,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, mTBILL, redemptionVaultWithAave, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- revertMessage: 'ERC20: burn amount exceeds balance',
- },
- );
- });
-
- it('should fail: dataFeed rate 0', async () => {
- const {
- owner,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- dataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, stableCoins.usdc, redemptionVaultWithAave, 10);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await mintToken(mTBILL, owner, 100_000);
- await setRoundData({ mockedAggregator }, 0);
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 1,
- {
- revertMessage: 'DF: feed is deprecated',
- },
- );
- });
-
- it('should fail: call for amount < minAmount', async () => {
- const {
- redemptionVaultWithAave,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mTBILL, owner, 100_000);
- await approveBase18(owner, mTBILL, redemptionVaultWithAave, 100_000);
-
- await setMinAmountTest(
- { vault: redemptionVaultWithAave, owner },
- 100_000,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 99_999,
- {
- revertMessage: 'RV: amount < min',
- },
- );
- });
-
- it('should fail: if exceeds token allowance', async () => {
- const {
- owner,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- dataFeed,
- mockedAggregator,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(mTBILL, owner, 100_000);
- await changeTokenAllowanceTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.usdc.address,
- 100,
- );
- await approveBase18(owner, mTBILL, redemptionVaultWithAave, 100_000);
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 99_999,
- {
- revertMessage: 'MV: exceed allowance',
- },
- );
- });
-
- it('should fail: if daily limit exceeded', async () => {
- const {
- owner,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- dataFeed,
- mockedAggregator,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(mTBILL, owner, 100_000);
- await setInstantDailyLimitTest(
- { vault: redemptionVaultWithAave, owner },
- 1000,
- );
-
- await approveBase18(owner, mTBILL, redemptionVaultWithAave, 100_000);
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 99_999,
- {
- revertMessage: 'MV: exceed limit',
- },
- );
- });
-
- it('should fail: if some fee = 100%', async () => {
- const {
- owner,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVaultWithAave, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 10000,
- true,
- );
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- revertMessage: 'RV: amountMTokenIn < fee',
- },
- );
- });
-
- it('should fail: greenlist enabled and user not in greenlist', async () => {
- const {
- owner,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redemptionVaultWithAave.setGreenlistEnable(true);
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: user in blacklist', async () => {
- const {
- owner,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: user in sanctions list', async () => {
- const {
- owner,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- mockedSanctionsList,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- regularAccounts[0],
- );
-
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mTBILL,
- redemptionVaultWithAave,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- // ── Happy path tests ─────────────────────────────────────────────────
-
- it('redeem 100 mTBILL when vault has enough USDC (no Aave needed)', async () => {
- const {
- owner,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- aUSDC,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, redemptionVaultWithAave, 100000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVaultWithAave, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- const aTokenBefore = await aUSDC.balanceOf(
- redemptionVaultWithAave.address,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
-
- // aToken balance should not change
- const aTokenAfter = await aUSDC.balanceOf(
- redemptionVaultWithAave.address,
- );
- expect(aTokenAfter).to.equal(aTokenBefore);
- });
-
- it('redeem 1000 mTBILL when vault has no USDC but has aTokens', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- aUSDC,
- } = await loadFixture(defaultDeploy);
-
- // Mint aTokens to vault (enough for redemption)
- await aUSDC.mint(redemptionVaultWithAave.address, parseUnits('9900', 8));
- await mintToken(mTBILL, owner, 1000);
- await approveBase18(owner, mTBILL, redemptionVaultWithAave, 1000);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setInstantFeeTest({ vault: redemptionVaultWithAave, owner }, 0);
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
-
- const aTokenBefore = await aUSDC.balanceOf(
- redemptionVaultWithAave.address,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 1000,
- );
-
- const aTokenAfter = await aUSDC.balanceOf(
- redemptionVaultWithAave.address,
- );
-
- // aTokens should decrease
- expect(aTokenAfter).to.be.lt(aTokenBefore);
- });
-
- it('redeem 1000 mTBILL when vault has 100 USDC and sufficient aTokens (partial Aave)', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- aUSDC,
- } = await loadFixture(defaultDeploy);
-
- // Vault has 100 USDC + 9900 aTokens
- await mintToken(stableCoins.usdc, redemptionVaultWithAave, 100);
- await aUSDC.mint(redemptionVaultWithAave.address, parseUnits('9900', 8));
- await mintToken(mTBILL, owner, 1000);
- await approveBase18(owner, mTBILL, redemptionVaultWithAave, 1000);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 1000,
- );
- });
-
- it('redeem 1000 mTBILL with different prices (stable 1.03$, mToken 5$) and partial Aave', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- aUSDC,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, redemptionVaultWithAave, 100);
- await aUSDC.mint(redemptionVaultWithAave.address, parseUnits('15000', 8));
- await mintToken(mTBILL, owner, 1000);
- await approveBase18(owner, mTBILL, redemptionVaultWithAave, 1000);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 100,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await redemptionVaultWithAave.freeFromMinAmount(owner.address, true);
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 1000,
- );
- });
-
- it('redeem 1000 mTBILL with waived fee and Aave withdrawal', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- aUSDC,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, redemptionVaultWithAave, 100);
- await aUSDC.mint(redemptionVaultWithAave.address, parseUnits('15000', 8));
- await mintToken(mTBILL, owner, 1000);
- await approveBase18(owner, mTBILL, redemptionVaultWithAave, 1000);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 100,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithAave, owner },
- owner.address,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- waivedFee: true,
- },
- stableCoins.usdc,
- 1000,
- );
- });
-
- it('should fail: insufficient aToken balance during redeemInstant', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- aUSDC,
- } = await loadFixture(defaultDeploy);
-
- // Vault has no USDC and only 10 aTokens (not enough for 1000 mTBILL redemption)
- await aUSDC.mint(redemptionVaultWithAave.address, parseUnits('10', 8));
- await mintToken(mTBILL, owner, 1000);
- await approveBase18(owner, mTBILL, redemptionVaultWithAave, 1000);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setInstantFeeTest({ vault: redemptionVaultWithAave, owner }, 0);
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
-
- await expect(
- redemptionVaultWithAave['redeemInstant(address,uint256,uint256)'](
- stableCoins.usdc.address,
- parseUnits('1000'),
- 0,
- ),
- ).to.be.revertedWith('RVA: insufficient aToken balance');
- });
-
- it('should fail: Aave pool has insufficient liquidity during redeemInstant', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- aUSDC,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
-
- // Vault has aTokens but pool has no liquidity
- await aUSDC.mint(redemptionVaultWithAave.address, parseUnits('10000', 8));
- await mintToken(mTBILL, owner, 100000);
- await approveBase18(owner, mTBILL, redemptionVaultWithAave, 100000);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setInstantFeeTest({ vault: redemptionVaultWithAave, owner }, 0);
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
-
- // Drain the pool
- const poolBalance = await stableCoins.usdc.balanceOf(
- aavePoolMock.address,
- );
- await aavePoolMock.withdrawAdmin(
- stableCoins.usdc.address,
- (
- await ethers.getSigners()
- )[10].address,
- poolBalance,
- );
-
- await expect(
- redemptionVaultWithAave['redeemInstant(address,uint256,uint256)'](
- stableCoins.usdc.address,
- parseUnits('1000'),
- 0,
- ),
- ).to.be.revertedWith('AaveV3PoolMock: InsufficientLiquidity');
- });
-
- it('should fail: short Aave withdrawal during redeemInstant', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- dataFeed,
- aUSDC,
- aavePoolMock,
- } = await loadFixture(defaultDeploy);
-
- await aUSDC.mint(redemptionVaultWithAave.address, parseUnits('10000', 8));
- await mintToken(mTBILL, owner, 100000);
- await approveBase18(owner, mTBILL, redemptionVaultWithAave, 100000);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setInstantFeeTest({ vault: redemptionVaultWithAave, owner }, 0);
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await aavePoolMock.setWithdrawReturnBps(5000);
-
- await expect(
- redemptionVaultWithAave['redeemInstant(address,uint256,uint256)'](
- stableCoins.usdc.address,
- parseUnits('1000'),
- 0,
- ),
- ).to.be.revertedWith('RVA: withdrawn < needed');
- });
-
- // ── Custom recipient tests ───────────────────────────────────────────
-
- it('redeem 100 mTBILL (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, redemptionVaultWithAave, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mTBILL,
- redemptionVaultWithAave,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem 100 mTBILL when other fn overload is paused (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- redemptionVaultWithAave,
- encodeFnSelector('redeemInstant(address,uint256,uint256)'),
- );
- await mintToken(stableCoins.usdc, redemptionVaultWithAave, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mTBILL,
- redemptionVaultWithAave,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem 100 mTBILL when other fn overload is paused', async () => {
- const {
- owner,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- redemptionVaultWithAave,
- encodeFnSelector('redeemInstant(address,uint256,uint256,address)'),
- );
- await mintToken(stableCoins.usdc, redemptionVaultWithAave, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mTBILL,
- redemptionVaultWithAave,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('should fail: when function with custom recipient is paused', async () => {
- const {
- owner,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- customRecipient,
- } = await loadFixture(defaultDeploy);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- redemptionVaultWithAave,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector(
- 'redeemInstant(address,uint256,uint256,address)',
- );
- await pauseVaultFn(redemptionVaultWithAave, selector);
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: greenlist enabled and recipient not in greenlist (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await redemptionVaultWithAave.setGreenlistEnable(true);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: recipient in blacklist (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- customRecipient,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: recipient in sanctions list (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- customRecipient,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
- });
-
- describe('redeemRequest()', () => {
- it('should fail: when there is no token in vault', async () => {
- const {
- owner,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'MV: token not exists',
- },
- );
- });
-
- it('should fail: when trying to redeem 0 amount', async () => {
- const {
- owner,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 0,
- {
- revertMessage: 'RV: invalid amount',
- },
- );
- });
-
- it('should fail: when function paused', async () => {
- const {
- owner,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- redemptionVaultWithAave,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector('redeemRequest(address,uint256)');
- await pauseVaultFn(redemptionVaultWithAave, selector);
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: call with insufficient balance', async () => {
- const {
- owner,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, mTBILL, redemptionVaultWithAave, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'ERC20: transfer amount exceeds balance',
- },
- );
- });
-
- it('redeem request: happy path', async () => {
- const {
- owner,
- redemptionVaultWithAave,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVaultWithAave, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
- });
- });
-
- describe('redeemFiatRequest()', () => {
- it('should fail: when function paused', async () => {
- const {
- owner,
- redemptionVaultWithAave,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
- await mintToken(mTBILL, regularAccounts[0], 100000);
- await approveBase18(
- regularAccounts[0],
- mTBILL,
- redemptionVaultWithAave,
- 100000,
- );
- const selector = encodeFnSelector('redeemFiatRequest(uint256)');
- await pauseVaultFn(redemptionVaultWithAave, selector);
- await redeemFiatRequestTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 100000,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('redeem fiat request: happy path', async () => {
- const { owner, redemptionVaultWithAave, mTBILL, mTokenToUsdDataFeed } =
- await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100000);
- await approveBase18(owner, mTBILL, redemptionVaultWithAave, 100000);
- await redeemFiatRequestTest(
- {
- redemptionVault: redemptionVaultWithAave,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 100000,
- );
- });
- });
-
- describe('approveRequest()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- redemptionVaultWithAave: redemptionVault,
- regularAccounts,
- mTokenToUsdDataFeed,
- mTBILL,
- } = await loadFixture(defaultDeploy);
- await approveRedeemRequestTest(
- {
- redemptionVault,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 1,
- parseUnits('1'),
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- redemptionVaultWithAave: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await approveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 1,
- parseUnits('1'),
- {
- revertMessage: 'RV: request not exist',
- },
- );
- });
-
- it('should fail: request already processed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithAave: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await approveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('1'),
- );
- await approveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('1'),
- { revertMessage: 'RV: request not pending' },
- );
- });
-
- it('approve request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithAave: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await approveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('1'),
- );
- });
- });
-
- describe('approveRequest() with fiat', async () => {
- it('approve request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithAave: redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 100,
- );
- const requestId = 0;
- await changeTokenAllowanceTest(
- { vault: redemptionVault, owner },
- constants.AddressZero,
- parseUnits('100'),
- );
-
- await approveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('1'),
- );
- });
- });
-
- describe('safeApproveRequest()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- redemptionVaultWithAave: redemptionVault,
- regularAccounts,
- mTokenToUsdDataFeed,
- mTBILL,
- } = await loadFixture(defaultDeploy);
- await safeApproveRedeemRequestTest(
- {
- redemptionVault,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 1,
- parseUnits('1'),
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- redemptionVaultWithAave: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 1,
- parseUnits('1'),
- {
- revertMessage: 'RV: request not exist',
- },
- );
- });
-
- it('should fail: if new rate greater then variabilityTolerance', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithAave: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('6'),
- { revertMessage: 'MV: exceed price diviation' },
- );
- });
-
- it('should fail: request already processed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithAave: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('5.000001'),
- );
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('5.00001'),
- { revertMessage: 'RV: request not pending' },
- );
- });
-
- it('safe approve request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithAave: redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('5.000001'),
- );
- });
- });
-
- describe('rejectRequest()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- redemptionVaultWithAave: redemptionVault,
- regularAccounts,
- mTokenToUsdDataFeed,
- mTBILL,
- } = await loadFixture(defaultDeploy);
- await rejectRedeemRequestTest(
- {
- redemptionVault,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- redemptionVaultWithAave: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await rejectRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 1,
- {
- revertMessage: 'RV: request not exist',
- },
- );
- });
-
- it('should fail: request already processed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithAave: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await rejectRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- );
- await rejectRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- { revertMessage: 'RV: request not pending' },
- );
- });
-
- it('reject request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithAave: redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
+ describe('checkAndRedeemAave()', () => {
+ it('should not withdraw from Aave when contract has enough balance', async () => {
+ const { redemptionVaultWithAave, stableCoins, aUSDC } =
+ await loadFixture(defaultDeploy);
+
+ const usdcAmount = parseUnits('1000', 8);
+ await stableCoins.usdc.mint(
+ redemptionVaultWithAave.address,
+ usdcAmount,
+ );
+
+ const balanceBefore = await stableCoins.usdc.balanceOf(
+ redemptionVaultWithAave.address,
+ );
+ const aTokenBefore = await aUSDC.balanceOf(
+ redemptionVaultWithAave.address,
+ );
+
+ await expect(
+ redemptionVaultWithAave.checkAndRedeemAave(
+ stableCoins.usdc.address,
+ parseUnits('500', 8),
+ ),
+ ).not.reverted;
+
+ const aTokenAfter = await aUSDC.balanceOf(
+ redemptionVaultWithAave.address,
+ );
+ expect(aTokenAfter).to.equal(aTokenBefore);
+
+ const usdcAfter = await stableCoins.usdc.balanceOf(
+ redemptionVaultWithAave.address,
+ );
+ expect(usdcAfter).to.equal(balanceBefore);
+ });
+
+ it('should withdraw missing amount from Aave', async () => {
+ const { redemptionVaultWithAave, stableCoins, aUSDC } =
+ await loadFixture(defaultDeploy);
+
+ // Vault has 500 USDC, needs 1000
+ const initialUsdc = parseUnits('500', 8);
+ await stableCoins.usdc.mint(
+ redemptionVaultWithAave.address,
+ initialUsdc,
+ );
+
+ // Vault has 600 aUSDC
+ const aTokenAmount = parseUnits('600', 8);
+ await aUSDC.mint(redemptionVaultWithAave.address, aTokenAmount);
+
+ await redemptionVaultWithAave.checkAndRedeemAave(
+ stableCoins.usdc.address,
+ parseUnits('1000', 8),
+ );
+
+ // Vault should now have 1000 USDC (500 original + 500 withdrawn from Aave)
+ const usdcAfter = await stableCoins.usdc.balanceOf(
+ redemptionVaultWithAave.address,
+ );
+ expect(usdcAfter).to.equal(parseUnits('1000', 8));
+
+ // aToken balance should decrease by 500
+ const aTokenAfter = await aUSDC.balanceOf(
+ redemptionVaultWithAave.address,
+ );
+ expect(aTokenAfter).to.equal(parseUnits('100', 8));
+ });
+
+ it('should withdraw missing amount from Aave when preferLoanLiquidity=true', async () => {
+ const { redemptionVaultWithAave, stableCoins, aUSDC, owner } =
+ await loadFixture(defaultDeploy);
+
+ // Vault has 500 USDC, needs 1000
+ const initialUsdc = parseUnits('500', 8);
+ await stableCoins.usdc.mint(
+ redemptionVaultWithAave.address,
+ initialUsdc,
+ );
+
+ // Vault has 600 aUSDC
+ const aTokenAmount = parseUnits('600', 8);
+ await aUSDC.mint(redemptionVaultWithAave.address, aTokenAmount);
+
+ await setPreferLoanLiquidityTest(
+ { redemptionVault: redemptionVaultWithAave, owner },
+ true,
+ );
+
+ await redemptionVaultWithAave.checkAndRedeemAave(
+ stableCoins.usdc.address,
+ parseUnits('1000', 8),
+ );
+
+ // Vault should now have 1000 USDC (500 original + 500 withdrawn from Aave)
+ const usdcAfter = await stableCoins.usdc.balanceOf(
+ redemptionVaultWithAave.address,
+ );
+ expect(usdcAfter).to.equal(parseUnits('1000', 8));
+
+ // aToken balance should decrease by 500
+ const aTokenAfter = await aUSDC.balanceOf(
+ redemptionVaultWithAave.address,
+ );
+ expect(aTokenAfter).to.equal(parseUnits('100', 8));
+ });
+
+ it('should revert: when Aave withdraws less than missing amount', async () => {
+ const { redemptionVaultWithAave, stableCoins, aUSDC, aavePoolMock } =
+ await loadFixture(defaultDeploy);
+
+ // Vault has 200 USDC, needs 1000
+ await stableCoins.usdc.mint(
+ redemptionVaultWithAave.address,
+ parseUnits('200', 8),
+ );
+
+ // Vault has enough aTokens to cover the gap
+ await aUSDC.mint(
+ redemptionVaultWithAave.address,
+ parseUnits('1000', 8),
+ );
+
+ // Simulate partial Aave withdrawal
+ await aavePoolMock.setWithdrawReturnBps(5000);
+
+ await expect(
+ redemptionVaultWithAave.checkAndRedeemAave(
+ stableCoins.usdc.address,
+ parseUnits('1000', 8),
+ ),
+ ).to.be.revertedWithCustomError(
+ redemptionVaultWithAave,
+ 'InsufficientWithdrawnAmount',
+ );
+ });
+ });
- await rejectRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- );
+ describe('redeemInstant()', () => {
+ describe('preferLoanLiquidity=true', () => {
+ it('redeem 100 mTBILL when vault has enough USDC (no Aave needed)', async () => {
+ const {
+ owner,
+ redemptionVaultWithAave,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ aUSDC,
+ } = await loadFixture(defaultDeploy);
+
+ await mintToken(stableCoins.usdc, redemptionVaultWithAave, 100000);
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVaultWithAave, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithAave, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ const aTokenBefore = await aUSDC.balanceOf(
+ redemptionVaultWithAave.address,
+ );
+ await setPreferLoanLiquidityTest(
+ { redemptionVault: redemptionVaultWithAave, owner },
+ true,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithAave,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ stableCoins.usdc,
+ 100,
+ );
+
+ // aToken balance should not change
+ const aTokenAfter = await aUSDC.balanceOf(
+ redemptionVaultWithAave.address,
+ );
+ expect(aTokenAfter).to.equal(aTokenBefore);
+ });
+ it('redeem 1000 mTBILL when vault has no USDC but has aTokens', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithAave,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ aUSDC,
+ } = await loadFixture(defaultDeploy);
+
+ // Mint aTokens to vault (enough for redemption)
+ await aUSDC.mint(
+ redemptionVaultWithAave.address,
+ parseUnits('9900', 8),
+ );
+
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithAave, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithAave, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setInstantFeeTest(
+ { vault: redemptionVaultWithAave, owner },
+ 0,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+
+ const aTokenBefore = await aUSDC.balanceOf(
+ redemptionVaultWithAave.address,
+ );
+
+ await setPreferLoanLiquidityTest(
+ { redemptionVault: redemptionVaultWithAave, owner },
+ true,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithAave,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ additionalLiquidity: async () => {
+ return await aUSDC.balanceOf(redemptionVaultWithAave.address);
+ },
+ },
+ stableCoins.usdc,
+ 1000,
+ );
+
+ const aTokenAfter = await aUSDC.balanceOf(
+ redemptionVaultWithAave.address,
+ );
+ // aTokens should decrease
+ expect(aTokenAfter).to.be.lt(aTokenBefore);
+ });
+ it('when vault has no USDC but has aTokens and LP liquidity, so LP liquidity should be used', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithAave,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ aUSDC,
+ loanLp,
+ mTokenLoan,
+ redemptionVaultLoanSwapper,
+ } = await loadFixture(defaultDeploy);
+
+ // Mint aTokens to vault (enough for redemption)
+ await aUSDC.mint(
+ redemptionVaultWithAave.address,
+ parseUnits('9900', 8),
+ );
+
+ await mintToken(
+ stableCoins.usdc,
+ redemptionVaultLoanSwapper,
+ 100000,
+ );
+ await mintToken(mTokenLoan, loanLp, 100000);
+ await approveBase18(
+ loanLp,
+ mTokenLoan,
+ redemptionVaultWithAave,
+ 100000,
+ );
+
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithAave, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithAave, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setInstantFeeTest(
+ { vault: redemptionVaultWithAave, owner },
+ 0,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+
+ const aTokenBefore = await aUSDC.balanceOf(
+ redemptionVaultWithAave.address,
+ );
+
+ await setPreferLoanLiquidityTest(
+ { redemptionVault: redemptionVaultWithAave, owner },
+ true,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithAave,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ additionalLiquidity: async () => {
+ return await aUSDC.balanceOf(redemptionVaultWithAave.address);
+ },
+ },
+ stableCoins.usdc,
+ 1000,
+ );
+
+ const aTokenAfter = await aUSDC.balanceOf(
+ redemptionVaultWithAave.address,
+ );
+ // aTokens should decrease
+ expect(aTokenAfter).to.be.eq(aTokenBefore);
+ });
+
+ it('when vault partially has USDC, partially has aTokens and partially has LP liquidity, so all the liquidity should be used', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithAave,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ aUSDC,
+ loanLp,
+ mTokenLoan,
+ redemptionVaultLoanSwapper,
+ } = await loadFixture(defaultDeploy);
+
+ // Mint aTokens to vault
+ await aUSDC.mint(
+ redemptionVaultWithAave.address,
+ parseUnits('300', 8),
+ );
+
+ await mintToken(stableCoins.usdc, redemptionVaultLoanSwapper, 300);
+ await mintToken(stableCoins.usdc, redemptionVaultWithAave, 400);
+ await mintToken(mTokenLoan, loanLp, 300);
+ await approveBase18(
+ loanLp,
+ mTokenLoan,
+ redemptionVaultWithAave,
+ 300,
+ );
+
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithAave, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithAave, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setInstantFeeTest(
+ { vault: redemptionVaultWithAave, owner },
+ 0,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+
+ await setPreferLoanLiquidityTest(
+ { redemptionVault: redemptionVaultWithAave, owner },
+ true,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithAave,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ additionalLiquidity: async () => {
+ return await aUSDC.balanceOf(redemptionVaultWithAave.address);
+ },
+ },
+ stableCoins.usdc,
+ 1000,
+ );
+
+ const aTokenAfter = await aUSDC.balanceOf(
+ redemptionVaultWithAave.address,
+ );
+
+ // liquidity should be used up
+ expect(aTokenAfter).to.be.eq(0);
+ expect(
+ await stableCoins.usdc.balanceOf(redemptionVaultWithAave.address),
+ ).to.be.eq(0);
+ expect(
+ await stableCoins.usdc.balanceOf(
+ redemptionVaultLoanSwapper.address,
+ ),
+ ).to.be.eq(0);
+ expect(await mTokenLoan.balanceOf(loanLp.address)).to.be.eq(0);
+ });
+ });
+
+ it('redeem 100 mTBILL when vault has enough USDC (no Aave needed)', async () => {
+ const {
+ owner,
+ redemptionVaultWithAave,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ aUSDC,
+ } = await loadFixture(defaultDeploy);
+
+ await mintToken(stableCoins.usdc, redemptionVaultWithAave, 100000);
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVaultWithAave, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithAave, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ const aTokenBefore = await aUSDC.balanceOf(
+ redemptionVaultWithAave.address,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithAave,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ stableCoins.usdc,
+ 100,
+ );
+
+ // aToken balance should not change
+ const aTokenAfter = await aUSDC.balanceOf(
+ redemptionVaultWithAave.address,
+ );
+ expect(aTokenAfter).to.equal(aTokenBefore);
+ });
+
+ it('redeem 1000 mTBILL when vault has no USDC but has aTokens', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithAave,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ aUSDC,
+ } = await loadFixture(defaultDeploy);
+
+ // Mint aTokens to vault (enough for redemption)
+ await aUSDC.mint(
+ redemptionVaultWithAave.address,
+ parseUnits('9900', 8),
+ );
+
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithAave, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithAave, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setInstantFeeTest({ vault: redemptionVaultWithAave, owner }, 0);
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+
+ const aTokenBefore = await aUSDC.balanceOf(
+ redemptionVaultWithAave.address,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithAave,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ additionalLiquidity: async () => {
+ return await aUSDC.balanceOf(redemptionVaultWithAave.address);
+ },
+ },
+ stableCoins.usdc,
+ 1000,
+ );
+
+ const aTokenAfter = await aUSDC.balanceOf(
+ redemptionVaultWithAave.address,
+ );
+ // aTokens should decrease
+ expect(aTokenAfter).to.be.lt(aTokenBefore);
+ });
+
+ it('redeem 1000 mTBILL when vault has 100 USDC and sufficient aTokens (partial Aave)', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithAave,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ aUSDC,
+ } = await loadFixture(defaultDeploy);
+
+ // Vault has 100 USDC + 9900 aTokens
+ await mintToken(stableCoins.usdc, redemptionVaultWithAave, 100);
+ await aUSDC.mint(
+ redemptionVaultWithAave.address,
+ parseUnits('9900', 8),
+ );
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithAave, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithAave, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithAave,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ additionalLiquidity: async () => {
+ return await aUSDC.balanceOf(redemptionVaultWithAave.address);
+ },
+ },
+ stableCoins.usdc,
+ 1000,
+ );
+ });
+
+ it('redeem 1000 mTBILL when vault has 100 USDC and insufficient aTokens (partial Aave, partial lp flow)', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithAave,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ aUSDC,
+ loanLp,
+ mTokenLoan,
+ redemptionVaultLoanSwapper,
+ } = await loadFixture(defaultDeploy);
+
+ // Vault has 100 USDC + 9900 aTokens
+ await mintToken(stableCoins.usdc, redemptionVaultWithAave, 100);
+ await mintToken(mTokenLoan, loanLp, 200);
+ await approveBase18(loanLp, mTokenLoan, redemptionVaultWithAave, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await mintToken(stableCoins.usdc, redemptionVaultLoanSwapper, 200);
+
+ await aUSDC.mint(
+ redemptionVaultWithAave.address,
+ parseUnits('700', 8),
+ );
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithAave, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithAave, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+
+ const loanRequestId =
+ await redemptionVaultWithAave.currentLoanRequestId();
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithAave,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ additionalLiquidity: async () => {
+ return await aUSDC.balanceOf(redemptionVaultWithAave.address);
+ },
+ },
+ stableCoins.usdc,
+ 1000,
+ );
+ const loanRequest = await redemptionVaultWithAave.loanRequests(
+ loanRequestId,
+ );
+ expect(loanRequest.status).eq(0);
+ expect(loanRequest.amountTokenOut).eq(parseUnits('198'));
+ });
+
+ it('redeem 1000 mTBILL with different prices (stable 1.03$, mToken 5$) and partial Aave', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithAave,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ aUSDC,
+ } = await loadFixture(defaultDeploy);
+
+ await mintToken(stableCoins.usdc, redemptionVaultWithAave, 100);
+ await aUSDC.mint(
+ redemptionVaultWithAave.address,
+ parseUnits('15000', 8),
+ );
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithAave, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithAave, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 100,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await redemptionVaultWithAave.freeFromMinAmount(owner.address, true);
+
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithAave,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ additionalLiquidity: async () => {
+ return await aUSDC.balanceOf(redemptionVaultWithAave.address);
+ },
+ },
+ stableCoins.usdc,
+ 1000,
+ );
+ });
+
+ it('redeem 1000 mTBILL with waived fee and Aave withdrawal', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithAave,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ aUSDC,
+ } = await loadFixture(defaultDeploy);
+
+ await mintToken(stableCoins.usdc, redemptionVaultWithAave, 100);
+ await aUSDC.mint(
+ redemptionVaultWithAave.address,
+ parseUnits('15000', 8),
+ );
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithAave, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithAave, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 100,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await setWaivedFeeAccountTest(
+ { vault: redemptionVaultWithAave, owner },
+ owner.address,
+ true,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithAave,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ waivedFee: true,
+ additionalLiquidity: async () => {
+ return await aUSDC.balanceOf(redemptionVaultWithAave.address);
+ },
+ },
+ stableCoins.usdc,
+ 1000,
+ );
+ });
+
+ it('should fail: insufficient aToken balance during redeemInstant and it hits loan lp flow', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithAave,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ aUSDC,
+ } = await loadFixture(defaultDeploy);
+
+ await setLoanLpTest(
+ { redemptionVault: redemptionVaultWithAave, owner },
+ ethers.constants.AddressZero,
+ );
+
+ // Vault has no USDC and only 10 aTokens (not enough for 1000 mTBILL redemption)
+ await aUSDC.mint(
+ redemptionVaultWithAave.address,
+ parseUnits('10', 8),
+ );
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithAave, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithAave, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setInstantFeeTest({ vault: redemptionVaultWithAave, owner }, 0);
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+
+ await expect(
+ redemptionVaultWithAave['redeemInstant(address,uint256,uint256)'](
+ stableCoins.usdc.address,
+ parseUnits('1000'),
+ 0,
+ ),
+ ).to.be.revertedWith('ERC20: transfer amount exceeds balance');
+ });
+
+ it('should fail: when aave pool is not configured and it hits loan lp flow', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithAave,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ } = await loadFixture(defaultDeploy);
+
+ await setLoanLpTest(
+ { redemptionVault: redemptionVaultWithAave, owner },
+ ethers.constants.AddressZero,
+ );
+ // Vault has no USDC and only 10 aTokens (not enough for 1000 mTBILL redemption)
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithAave, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithAave, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setInstantFeeTest({ vault: redemptionVaultWithAave, owner }, 0);
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+
+ await redemptionVaultWithAave.removeAavePool(
+ stableCoins.usdc.address,
+ );
+
+ await expect(
+ redemptionVaultWithAave['redeemInstant(address,uint256,uint256)'](
+ stableCoins.usdc.address,
+ parseUnits('1000'),
+ 0,
+ ),
+ ).to.be.revertedWith('ERC20: transfer amount exceeds balance');
+ });
+
+ it('should fail: when aave pool is configured but aToken is not in the pool and it hits loan lp flow', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithAave,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ aavePoolMock,
+ } = await loadFixture(defaultDeploy);
+
+ await setLoanLpTest(
+ { redemptionVault: redemptionVaultWithAave, owner },
+ ethers.constants.AddressZero,
+ );
+
+ // Vault has no USDC and only 10 aTokens (not enough for 1000 mTBILL redemption)
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithAave, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithAave, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setInstantFeeTest({ vault: redemptionVaultWithAave, owner }, 0);
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+
+ await aavePoolMock.setReserveAToken(
+ stableCoins.usdc.address,
+ ethers.constants.AddressZero,
+ );
+
+ await expect(
+ redemptionVaultWithAave['redeemInstant(address,uint256,uint256)'](
+ stableCoins.usdc.address,
+ parseUnits('1000'),
+ 0,
+ ),
+ ).to.be.revertedWith('ERC20: transfer amount exceeds balance');
+ });
+
+ it('should fail: Aave pool has insufficient liquidity during redeemInstant', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithAave,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed: _mTokenToUsdDataFeed,
+ aUSDC,
+ aavePoolMock,
+ } = await loadFixture(defaultDeploy);
+
+ // Vault has aTokens but pool has no liquidity
+ await aUSDC.mint(
+ redemptionVaultWithAave.address,
+ parseUnits('10000', 8),
+ );
+ await mintToken(mTBILL, owner, 100000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithAave, 100000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithAave, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setInstantFeeTest({ vault: redemptionVaultWithAave, owner }, 0);
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+
+ // Drain the pool
+ const poolBalance = await stableCoins.usdc.balanceOf(
+ aavePoolMock.address,
+ );
+ await aavePoolMock.withdrawAdmin(
+ stableCoins.usdc.address,
+ (
+ await ethers.getSigners()
+ )[10].address,
+ poolBalance,
+ );
+
+ await expect(
+ redemptionVaultWithAave['redeemInstant(address,uint256,uint256)'](
+ stableCoins.usdc.address,
+ parseUnits('1000'),
+ 0,
+ ),
+ ).to.be.revertedWith('ERC20: transfer amount exceeds balance');
+ });
+
+ it('should fail: short Aave withdrawal during redeemInstant', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithAave,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ aUSDC,
+ aavePoolMock,
+ } = await loadFixture(defaultDeploy);
+
+ await aUSDC.mint(
+ redemptionVaultWithAave.address,
+ parseUnits('10000', 8),
+ );
+ await mintToken(mTBILL, owner, 100000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithAave, 100000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithAave, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setInstantFeeTest({ vault: redemptionVaultWithAave, owner }, 0);
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await aavePoolMock.setWithdrawReturnBps(5000);
+
+ await expect(
+ redemptionVaultWithAave['redeemInstant(address,uint256,uint256)'](
+ stableCoins.usdc.address,
+ parseUnits('1000'),
+ 0,
+ ),
+ ).to.be.revertedWith('ERC20: transfer amount exceeds balance');
+ });
+ });
});
- });
-});
+ },
+);
diff --git a/test/unit/RedemptionVaultWithBUIDL.test.ts b/test/unit/RedemptionVaultWithBUIDL.test.ts
deleted file mode 100644
index 36f92de5..00000000
--- a/test/unit/RedemptionVaultWithBUIDL.test.ts
+++ /dev/null
@@ -1,4724 +0,0 @@
-import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
-import { expect } from 'chai';
-import { constants } from 'ethers';
-import { parseUnits } from 'ethers/lib/utils';
-import { ethers } from 'hardhat';
-
-import { encodeFnSelector } from '../../helpers/utils';
-import {
- EUsdRedemptionVaultWithBUIDL__factory,
- ManageableVaultTester__factory,
- MBasisRedemptionVaultWithBUIDL__factory,
- RedemptionVaultWithBUIDLTest__factory,
-} from '../../typechain-types';
-import { acErrors, blackList, greenList } from '../common/ac.helpers';
-import {
- approveBase18,
- mintToken,
- pauseVault,
- pauseVaultFn,
-} from '../common/common.helpers';
-import { setRoundData } from '../common/data-feed.helpers';
-import { defaultDeploy } from '../common/fixtures';
-import {
- addPaymentTokenTest,
- addWaivedFeeAccountTest,
- changeTokenAllowanceTest,
- removePaymentTokenTest,
- removeWaivedFeeAccountTest,
- setInstantFeeTest,
- setInstantDailyLimitTest,
- setMinAmountTest,
- setVariabilityToleranceTest,
- withdrawTest,
- changeTokenFeeTest,
- setMinBuidlToRedeem,
-} from '../common/manageable-vault.helpers';
-import {
- approveRedeemRequestTest,
- redeemFiatRequestTest,
- redeemInstantTest,
- redeemRequestTest,
- rejectRedeemRequestTest,
- safeApproveRedeemRequestTest,
- setFiatAdditionalFeeTest,
- setMinFiatRedeemAmountTest,
-} from '../common/redemption-vault.helpers';
-import { sanctionUser } from '../common/with-sanctions-list.helpers';
-
-describe('RedemptionVaultWithBUIDL', function () {
- it('deployment', async () => {
- const {
- redemptionVaultWithBUIDL,
- buidlRedemption,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- roles,
- } = await loadFixture(defaultDeploy);
-
- expect(await redemptionVaultWithBUIDL.mToken()).eq(mTBILL.address);
-
- expect(await redemptionVaultWithBUIDL.ONE_HUNDRED_PERCENT()).eq('10000');
-
- expect(await redemptionVaultWithBUIDL.paused()).eq(false);
-
- expect(await redemptionVaultWithBUIDL.tokensReceiver()).eq(
- tokensReceiver.address,
- );
- expect(await redemptionVaultWithBUIDL.feeReceiver()).eq(
- feeReceiver.address,
- );
-
- expect(await redemptionVaultWithBUIDL.minAmount()).eq(1000);
- expect(await redemptionVaultWithBUIDL.minFiatRedeemAmount()).eq(1000);
-
- expect(await redemptionVaultWithBUIDL.instantFee()).eq('100');
-
- expect(await redemptionVaultWithBUIDL.instantDailyLimit()).eq(
- parseUnits('100000'),
- );
-
- expect(await redemptionVaultWithBUIDL.mTokenDataFeed()).eq(
- mTokenToUsdDataFeed.address,
- );
- expect(await redemptionVaultWithBUIDL.variationTolerance()).eq(1);
-
- expect(await redemptionVaultWithBUIDL.vaultRole()).eq(
- roles.tokenRoles.mTBILL.redemptionVaultAdmin,
- );
-
- expect(await redemptionVaultWithBUIDL.MANUAL_FULLFILMENT_TOKEN()).eq(
- ethers.constants.AddressZero,
- );
-
- expect(await redemptionVaultWithBUIDL.buidlRedemption()).eq(
- buidlRedemption.address,
- );
- expect(await redemptionVaultWithBUIDL.minBuidlToRedeem()).eq(
- parseUnits('250000', 6),
- );
- });
-
- it('failing deployment', async () => {
- const {
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- accessControl,
- mockedSanctionsList,
- owner,
- } = await loadFixture(defaultDeploy);
-
- const redemptionVaultWithBUIDL =
- await new RedemptionVaultWithBUIDLTest__factory(owner).deploy();
-
- await expect(
- redemptionVaultWithBUIDL[
- 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,(uint256,uint256,uint256),address)'
- ](
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- {
- fiatAdditionalFee: 10000,
- fiatFlatFee: parseUnits('1'),
- minFiatRedeemAmount: parseUnits('100'),
- },
- constants.AddressZero,
- ),
- ).to.be.reverted;
- });
-
- it('MBasisRedemptionVault', async () => {
- const fixture = await loadFixture(defaultDeploy);
-
- const tester = await new MBasisRedemptionVaultWithBUIDL__factory(
- fixture.owner,
- ).deploy();
-
- expect(await tester.vaultRole()).eq(
- await tester.M_BASIS_REDEMPTION_VAULT_ADMIN_ROLE(),
- );
- });
-
- it('EUsdRedemptionVault', async () => {
- const fixture = await loadFixture(defaultDeploy);
-
- const tester = await new EUsdRedemptionVaultWithBUIDL__factory(
- fixture.owner,
- ).deploy();
-
- expect(await tester.vaultRole()).eq(
- await tester.E_USD_REDEMPTION_VAULT_ADMIN_ROLE(),
- );
-
- expect(await tester.greenlistedRole()).eq(
- await tester.E_USD_GREENLISTED_ROLE(),
- );
- });
-
- describe('initialization', () => {
- it('should fail: cal; initialize() when already initialized', async () => {
- const { redemptionVaultWithBUIDL } = await loadFixture(defaultDeploy);
-
- await expect(
- redemptionVaultWithBUIDL[
- 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,(uint256,uint256,uint256),address,address,uint256,uint256)'
- ](
- constants.AddressZero,
- {
- mToken: constants.AddressZero,
- mTokenDataFeed: constants.AddressZero,
- },
- {
- feeReceiver: constants.AddressZero,
- tokensReceiver: constants.AddressZero,
- },
- {
- instantFee: 0,
- instantDailyLimit: 0,
- },
- constants.AddressZero,
- 0,
- 0,
- {
- fiatAdditionalFee: 0,
- fiatFlatFee: 0,
- minFiatRedeemAmount: 0,
- },
- constants.AddressZero,
- constants.AddressZero,
- 0,
- 0,
- ),
- ).revertedWith('Initializable: contract is already initialized');
- });
-
- it('should fail: call with initializing == false', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initializeWithoutInitializer(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- 1000,
- ),
- ).revertedWith('Initializable: contract is not initializing');
- });
-
- it('should fail: when _tokensReceiver == address(this)', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- feeReceiver,
- mTokenToUsdDataFeed,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: vault.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- 1000,
- ),
- ).revertedWith('invalid address');
- });
- it('should fail: when _feeReceiver == address(this)', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- mTokenToUsdDataFeed,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: vault.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- 1000,
- ),
- ).revertedWith('invalid address');
- });
- it('should fail: when limit = 0', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: 0,
- },
- mockedSanctionsList.address,
- 1,
- 1000,
- ),
- ).revertedWith('zero limit');
- });
- it('should fail: when mToken dataFeed address zero', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: constants.AddressZero,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- 1000,
- ),
- ).revertedWith('zero address');
- });
- it('should fail: when variationTolarance zero', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mockedSanctionsList,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 0,
- 1000,
- ),
- ).revertedWith('fee == 0');
- });
- });
-
- describe('setMinBuidlToRedeem()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVaultWithBUIDL, regularAccounts } =
- await loadFixture(defaultDeploy);
-
- await setMinBuidlToRedeem(
- { vault: redemptionVaultWithBUIDL, owner },
- parseUnits('100000', 6),
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- },
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVaultWithBUIDL } = await loadFixture(
- defaultDeploy,
- );
- await setMinBuidlToRedeem(
- { vault: redemptionVaultWithBUIDL, owner },
- parseUnits('100000', 6),
- );
- });
- });
-
- describe('setMinAmount()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVaultWithBUIDL, regularAccounts } =
- await loadFixture(defaultDeploy);
-
- await setMinAmountTest({ vault: redemptionVaultWithBUIDL, owner }, 1.1, {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- });
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVaultWithBUIDL } = await loadFixture(
- defaultDeploy,
- );
- await setMinAmountTest({ vault: redemptionVaultWithBUIDL, owner }, 1.1);
- });
- });
-
- describe('setMinFiatRedeemAmount()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVaultWithBUIDL, regularAccounts } =
- await loadFixture(defaultDeploy);
-
- await setMinFiatRedeemAmountTest(
- { redemptionVault: redemptionVaultWithBUIDL, owner },
- 1.1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- },
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVaultWithBUIDL } = await loadFixture(
- defaultDeploy,
- );
- await setMinFiatRedeemAmountTest(
- { redemptionVault: redemptionVaultWithBUIDL, owner },
- 1.1,
- );
- });
- });
-
- describe('setFiatAdditionalFee()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVaultWithBUIDL, regularAccounts } =
- await loadFixture(defaultDeploy);
-
- await setFiatAdditionalFeeTest(
- { redemptionVault: redemptionVaultWithBUIDL, owner },
- 100,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- },
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVaultWithBUIDL } = await loadFixture(
- defaultDeploy,
- );
- await setFiatAdditionalFeeTest(
- { redemptionVault: redemptionVaultWithBUIDL, owner },
- 100,
- );
- });
- });
-
- describe('setInstantDailyLimit()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVaultWithBUIDL, regularAccounts } =
- await loadFixture(defaultDeploy);
-
- await setInstantDailyLimitTest(
- { vault: redemptionVaultWithBUIDL, owner },
- parseUnits('1000'),
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- },
- );
- });
-
- it('should fail: try to set 0 limit', async () => {
- const { owner, redemptionVaultWithBUIDL } = await loadFixture(
- defaultDeploy,
- );
-
- await setInstantDailyLimitTest(
- { vault: redemptionVaultWithBUIDL, owner },
- constants.Zero,
- {
- revertMessage: 'MV: limit zero',
- },
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVaultWithBUIDL } = await loadFixture(
- defaultDeploy,
- );
- await setInstantDailyLimitTest(
- { vault: redemptionVaultWithBUIDL, owner },
- parseUnits('1000'),
- );
- });
- });
-
- describe('addPaymentToken()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithBUIDL, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- ethers.constants.AddressZero,
- ethers.constants.AddressZero,
- 0,
- true,
- constants.MaxUint256,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
-
- it('should fail: when token is already added', async () => {
- const { redemptionVaultWithBUIDL, stableCoins, owner, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- constants.MaxUint256,
- {
- revertMessage: 'MV: already added',
- },
- );
- });
-
- it('should fail: when token dataFeed address zero', async () => {
- const { redemptionVaultWithBUIDL, stableCoins, owner } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- constants.AddressZero,
- 0,
- true,
- constants.MaxUint256,
- {
- revertMessage: 'zero address',
- },
- );
- });
-
- it('call when allowance is zero', async () => {
- const { redemptionVaultWithBUIDL, stableCoins, owner, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- false,
- constants.Zero,
- );
- });
-
- it('call when allowance is not uint256 max', async () => {
- const { redemptionVaultWithBUIDL, stableCoins, owner, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- false,
- parseUnits('100'),
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithBUIDL, stableCoins, owner, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role and add 3 options on a row', async () => {
- const { redemptionVaultWithBUIDL, stableCoins, owner, dataFeed } =
- await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdt,
- dataFeed.address,
- 0,
- true,
- );
- });
- });
-
- describe('addWaivedFeeAccount()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithBUIDL, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithBUIDL, owner },
- ethers.constants.AddressZero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('should fail: if account fee already waived', async () => {
- const { redemptionVaultWithBUIDL, owner } = await loadFixture(
- defaultDeploy,
- );
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithBUIDL, owner },
- owner.address,
- );
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithBUIDL, owner },
- owner.address,
- { revertMessage: 'MV: already added' },
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithBUIDL, owner } = await loadFixture(
- defaultDeploy,
- );
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithBUIDL, owner },
- owner.address,
- );
- });
- });
-
- describe('removeWaivedFeeAccount()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithBUIDL, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await removeWaivedFeeAccountTest(
- { vault: redemptionVaultWithBUIDL, owner },
- ethers.constants.AddressZero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('should fail: if account not found in restriction', async () => {
- const { redemptionVaultWithBUIDL, owner } = await loadFixture(
- defaultDeploy,
- );
- await removeWaivedFeeAccountTest(
- { vault: redemptionVaultWithBUIDL, owner },
- owner.address,
- { revertMessage: 'MV: not found' },
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithBUIDL, owner } = await loadFixture(
- defaultDeploy,
- );
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithBUIDL, owner },
- owner.address,
- );
- await removeWaivedFeeAccountTest(
- { vault: redemptionVaultWithBUIDL, owner },
- owner.address,
- );
- });
- });
-
- describe('setFee()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithBUIDL, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await setInstantFeeTest(
- { vault: redemptionVaultWithBUIDL, owner },
- ethers.constants.Zero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
-
- it('should fail: if new value greater then 100%', async () => {
- const { redemptionVaultWithBUIDL, owner } = await loadFixture(
- defaultDeploy,
- );
- await setInstantFeeTest(
- { vault: redemptionVaultWithBUIDL, owner },
- 10001,
- {
- revertMessage: 'fee > 100%',
- },
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithBUIDL, owner } = await loadFixture(
- defaultDeploy,
- );
- await setInstantFeeTest({ vault: redemptionVaultWithBUIDL, owner }, 100);
- });
- });
-
- describe('setVariabilityTolerance()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithBUIDL, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await setVariabilityToleranceTest(
- { vault: redemptionVaultWithBUIDL, owner },
- ethers.constants.Zero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('should fail: if new value zero', async () => {
- const { redemptionVaultWithBUIDL, owner } = await loadFixture(
- defaultDeploy,
- );
- await setVariabilityToleranceTest(
- { vault: redemptionVaultWithBUIDL, owner },
- ethers.constants.Zero,
- { revertMessage: 'fee == 0' },
- );
- });
-
- it('should fail: if new value greater then 100%', async () => {
- const { redemptionVaultWithBUIDL, owner } = await loadFixture(
- defaultDeploy,
- );
- await setVariabilityToleranceTest(
- { vault: redemptionVaultWithBUIDL, owner },
- 10001,
- { revertMessage: 'fee > 100%' },
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithBUIDL, owner } = await loadFixture(
- defaultDeploy,
- );
- await setVariabilityToleranceTest(
- { vault: redemptionVaultWithBUIDL, owner },
- 100,
- );
- });
- });
-
- describe('removePaymentToken()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithBUIDL, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await removePaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- ethers.constants.AddressZero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
-
- it('should fail: when token is not exists', async () => {
- const { owner, redemptionVaultWithBUIDL, stableCoins } =
- await loadFixture(defaultDeploy);
- await removePaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai.address,
- { revertMessage: 'MV: not exists' },
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithBUIDL, stableCoins, owner, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await removePaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai.address,
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role and add 3 options on a row', async () => {
- const { redemptionVaultWithBUIDL, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdt,
- dataFeed.address,
- 0,
- true,
- );
-
- await removePaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai.address,
- );
- await removePaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc.address,
- );
- await removePaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdt.address,
- );
-
- await removePaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdt.address,
- { revertMessage: 'MV: not exists' },
- );
- });
- });
-
- describe('withdrawToken()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithBUIDL, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await withdrawTest(
- { vault: redemptionVaultWithBUIDL, owner },
- ethers.constants.AddressZero,
- 0,
- ethers.constants.AddressZero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
-
- it('should fail: when there is no token in vault', async () => {
- const { owner, redemptionVaultWithBUIDL, regularAccounts, stableCoins } =
- await loadFixture(defaultDeploy);
- await withdrawTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- 1,
- regularAccounts[0],
- { revertMessage: 'ERC20: transfer amount exceeds balance' },
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithBUIDL, regularAccounts, stableCoins, owner } =
- await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, redemptionVaultWithBUIDL, 1);
- await withdrawTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- 1,
- regularAccounts[0],
- );
- });
- });
-
- describe('freeFromMinAmount()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const { redemptionVaultWithBUIDL, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- redemptionVaultWithBUIDL
- .connect(regularAccounts[0])
- .freeFromMinAmount(regularAccounts[1].address, true),
- ).to.be.revertedWith('WMAC: hasnt role');
- });
- it('should not fail', async () => {
- const { redemptionVaultWithBUIDL, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- redemptionVaultWithBUIDL.freeFromMinAmount(
- regularAccounts[0].address,
- true,
- ),
- ).to.not.reverted;
-
- expect(
- await redemptionVaultWithBUIDL.isFreeFromMinAmount(
- regularAccounts[0].address,
- ),
- ).to.eq(true);
- });
- it('should fail: already in list', async () => {
- const { redemptionVaultWithBUIDL, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- redemptionVaultWithBUIDL.freeFromMinAmount(
- regularAccounts[0].address,
- true,
- ),
- ).to.not.reverted;
-
- expect(
- await redemptionVaultWithBUIDL.isFreeFromMinAmount(
- regularAccounts[0].address,
- ),
- ).to.eq(true);
-
- await expect(
- redemptionVaultWithBUIDL.freeFromMinAmount(
- regularAccounts[0].address,
- true,
- ),
- ).to.revertedWith('DV: already free');
- });
- });
-
- describe('changeTokenAllowance()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithBUIDL, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await changeTokenAllowanceTest(
- { vault: redemptionVaultWithBUIDL, owner },
- ethers.constants.AddressZero,
- 0,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('should fail: token not exist', async () => {
- const { redemptionVaultWithBUIDL, owner, stableCoins } =
- await loadFixture(defaultDeploy);
- await changeTokenAllowanceTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai.address,
- 0,
- { revertMessage: 'MV: token not exists' },
- );
- });
- it('should fail: allowance zero', async () => {
- const { redemptionVaultWithBUIDL, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai.address,
- 0,
- { revertMessage: 'MV: zero allowance' },
- );
- });
- it('should fail: if mint exceed allowance', async () => {
- const {
- redemptionVaultWithBUIDL,
- stableCoins,
- owner,
- dataFeed,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await mintToken(mTBILL, owner, 100000);
- await mintToken(stableCoins.usdc, redemptionVaultWithBUIDL, 100000);
- await approveBase18(owner, mTBILL, redemptionVaultWithBUIDL, 100000);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc.address,
- 100,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 99_999,
- {
- revertMessage: 'MV: exceed allowance',
- },
- );
- });
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithBUIDL, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai.address,
- 100000000,
- );
- });
- it('should decrease if allowance < UINT_MAX', async () => {
- const {
- redemptionVaultWithBUIDL,
- stableCoins,
- owner,
- dataFeed,
- mTBILL,
- mTokenToUsdDataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.usdc, redemptionVaultWithBUIDL, 100000);
- await mintToken(mTBILL, owner, 100000);
- await approveBase18(owner, mTBILL, redemptionVaultWithBUIDL, 100000);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc.address,
- parseUnits('1000'),
- );
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
-
- const tokenConfigBefore = await redemptionVaultWithBUIDL.tokensConfig(
- stableCoins.usdc.address,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 999,
- );
-
- const tokenConfigAfter = await redemptionVaultWithBUIDL.tokensConfig(
- stableCoins.usdc.address,
- );
-
- expect(tokenConfigBefore.allowance.sub(tokenConfigAfter.allowance)).eq(
- parseUnits('999'),
- );
- });
- it('should not decrease if allowance = UINT_MAX', async () => {
- const {
- redemptionVaultWithBUIDL,
- stableCoins,
- owner,
- dataFeed,
- mTBILL,
- mTokenToUsdDataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.usdc, redemptionVaultWithBUIDL, 100000);
- await mintToken(mTBILL, owner, 100000);
- await approveBase18(owner, mTBILL, redemptionVaultWithBUIDL, 100000);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc.address,
- constants.MaxUint256,
- );
-
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
-
- const tokenConfigBefore = await redemptionVaultWithBUIDL.tokensConfig(
- stableCoins.usdc.address,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 999,
- );
-
- const tokenConfigAfter = await redemptionVaultWithBUIDL.tokensConfig(
- stableCoins.usdc.address,
- );
-
- expect(tokenConfigBefore.allowance).eq(tokenConfigAfter.allowance);
- });
- });
-
- describe('changeTokenFee()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithBUIDL, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await changeTokenFeeTest(
- { vault: redemptionVaultWithBUIDL, owner },
- ethers.constants.AddressZero,
- 0,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('should fail: token not exist', async () => {
- const { redemptionVaultWithBUIDL, owner, stableCoins } =
- await loadFixture(defaultDeploy);
- await changeTokenFeeTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai.address,
- 0,
- { revertMessage: 'MV: token not exists' },
- );
- });
- it('should fail: fee > 100%', async () => {
- const { redemptionVaultWithBUIDL, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenFeeTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai.address,
- 10001,
- { revertMessage: 'fee > 100%' },
- );
- });
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithBUIDL, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenFeeTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai.address,
- 100,
- );
- });
- });
-
- describe('redeemInstant()', () => {
- it('should fail: when there is no token in vault', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 1,
- {
- revertMessage: 'MV: token not exists',
- },
- );
- });
-
- it('should fail: when trying to redeem 0 amount', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 0,
- {
- revertMessage: 'RV: invalid amount',
- },
- );
- });
-
- it('should fail: when function paused', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- redemptionVaultWithBUIDL,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector(
- 'redeemInstant(address,uint256,uint256)',
- );
- await pauseVaultFn(redemptionVaultWithBUIDL, selector);
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: call with insufficient allowance', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- revertMessage: 'ERC20: insufficient allowance',
- },
- );
- });
-
- it('should fail: call with insufficient balance', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, mTBILL, redemptionVaultWithBUIDL, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- revertMessage: 'ERC20: burn amount exceeds balance',
- },
- );
- });
-
- it('should fail: dataFeed rate 0 ', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- dataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(
- owner,
- stableCoins.usdc,
- redemptionVaultWithBUIDL,
- 10,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await mintToken(mTBILL, owner, 100_000);
- await setRoundData({ mockedAggregator }, 0);
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 1,
- {
- revertMessage: 'DF: feed is deprecated',
- },
- );
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 0);
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 1,
- {
- revertMessage: 'DF: feed is deprecated',
- },
- );
- });
-
- it('should fail: if min receive amount greater then actual', async () => {
- const {
- redemptionVaultWithBUIDL,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(mTBILL, owner, 100_000);
-
- await approveBase18(owner, mTBILL, redemptionVaultWithBUIDL, 100_000);
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- minAmount: parseUnits('1000000'),
- },
- stableCoins.usdc,
- 99_999,
- {
- revertMessage: 'RVB: minReceiveAmount > actual',
- },
- );
- });
-
- it('should fail: call for amount < minAmount', async () => {
- const {
- redemptionVaultWithBUIDL,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mTBILL, owner, 100_000);
- await approveBase18(owner, mTBILL, redemptionVaultWithBUIDL, 100_000);
-
- await setMinAmountTest(
- { vault: redemptionVaultWithBUIDL, owner },
- 100_000,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 99_999,
- {
- revertMessage: 'RV: amount < min',
- },
- );
- });
-
- it('should fail: call when token is invalid', async () => {
- const {
- redemptionVaultWithBUIDL,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'RVB: invalid token',
- },
- );
- });
-
- it('should fail: if exceed allowance of redeem by token', async () => {
- const {
- redemptionVaultWithBUIDL,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(mTBILL, owner, 100_000);
- await changeTokenAllowanceTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc.address,
- 100,
- );
- await approveBase18(owner, mTBILL, redemptionVaultWithBUIDL, 100_000);
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 99_999,
- {
- revertMessage: 'MV: exceed allowance',
- },
- );
- });
-
- it('should fail: if redeem daily limit exceeded', async () => {
- const {
- redemptionVaultWithBUIDL,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(mTBILL, owner, 100_000);
- await setInstantDailyLimitTest(
- { vault: redemptionVaultWithBUIDL, owner },
- 1000,
- );
-
- await approveBase18(owner, mTBILL, redemptionVaultWithBUIDL, 100_000);
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 99_999,
- {
- revertMessage: 'MV: exceed limit',
- },
- );
- });
-
- it('should fail: if some fee = 100%', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVaultWithBUIDL, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 10000,
- true,
- );
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- revertMessage: 'RV: amountMTokenIn < fee',
- },
- );
-
- await removePaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setInstantFeeTest(
- { vault: redemptionVaultWithBUIDL, owner },
- 10000,
- );
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- { revertMessage: 'RV: amountMTokenIn < fee' },
- );
- });
-
- it('should fail: greenlist enabled and user not in greenlist ', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redemptionVaultWithBUIDL.setGreenlistEnable(true);
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: user in blacklist ', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: user in sanctions list', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- regularAccounts[0],
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('should fail: when function with custom recipient is paused', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- customRecipient,
- } = await loadFixture(defaultDeploy);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- redemptionVaultWithBUIDL,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector(
- 'redeemInstant(address,uint256,uint256,address)',
- );
- await pauseVaultFn(redemptionVaultWithBUIDL, selector);
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: greenlist enabled and recipient not in greenlist (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await redemptionVaultWithBUIDL.setGreenlistEnable(true);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: recipient in blacklist (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- customRecipient,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: recipient in sanctions list (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- customRecipient,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('should fail: user try to instant redeem more then contract can redeem', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- buidl,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100000);
- await mintToken(stableCoins.usdc, redemptionVaultWithBUIDL, 100);
- await mintToken(buidl, redemptionVaultWithBUIDL, 100);
-
- await approveBase18(owner, mTBILL, redemptionVaultWithBUIDL, 100000);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 100,
- true,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100000,
- {
- revertMessage: 'RVB: buidlToRedeem > balance',
- },
- );
- });
-
- it('redeem 100 mTBILL, when price of stable is 1$ and mToken price is 1$ and contract have 100 USDC', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, redemptionVaultWithBUIDL, 100);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVaultWithBUIDL, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
- });
-
- it('redeem 1000 mTBILL, when price of stable is 1$ and mToken price is 1$ and contract do not have USDC, but have 9900 BUIDL', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- buidl,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(buidl, redemptionVaultWithBUIDL, 9900);
- await mintToken(mTBILL, owner, 1000);
- await approveBase18(owner, mTBILL, redemptionVaultWithBUIDL, 1000);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setInstantFeeTest({ vault: redemptionVaultWithBUIDL, owner }, 0);
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- const buidlBalanceBefore = await buidl.balanceOf(
- redemptionVaultWithBUIDL.address,
- );
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 1000,
- );
- const buidlBalanceAfter = await buidl.balanceOf(
- redemptionVaultWithBUIDL.address,
- );
- expect(buidlBalanceAfter).eq(
- buidlBalanceBefore.sub(parseUnits('250000', 6)),
- );
- });
-
- it('redeem 1000 mTBILL, when price of stable is 1$ and mToken price is 1$ and contract have 100 USDC and 9900 BUIDL', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- buidl,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, redemptionVaultWithBUIDL, 100);
- await mintToken(buidl, redemptionVaultWithBUIDL, 9900);
- await mintToken(mTBILL, owner, 1000);
- await approveBase18(owner, mTBILL, redemptionVaultWithBUIDL, 1000);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 1000,
- );
- });
-
- it('redeem 1000 mTBILL, when price of stable is 1.03$ and mToken price is 5$ and contract have 100 USDC and 15000 BUIDL without checking of minDepositAmount', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- buidl,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, redemptionVaultWithBUIDL, 100);
- await mintToken(buidl, redemptionVaultWithBUIDL, 15000);
- await mintToken(mTBILL, owner, 1000);
- await approveBase18(owner, mTBILL, redemptionVaultWithBUIDL, 1000);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 100,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await redemptionVaultWithBUIDL.freeFromMinAmount(owner.address, true);
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 1000,
- );
- });
-
- it('redeem 1000 mTBILL, when price of stable is 1.03$ and mToken price is 5$ and contract have 100 USDC and 15000 BUIDL and user in waivedFeeRestriction', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- buidl,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, redemptionVaultWithBUIDL, 100);
- await mintToken(buidl, redemptionVaultWithBUIDL, 15000);
- await mintToken(mTBILL, owner, 1000);
- await approveBase18(owner, mTBILL, redemptionVaultWithBUIDL, 1000);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 100,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithBUIDL, owner },
- owner.address,
- );
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- waivedFee: true,
- },
- stableCoins.usdc,
- 1000,
- );
- });
-
- it('redeem 100 mTBILL (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, redemptionVaultWithBUIDL, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mTBILL,
- redemptionVaultWithBUIDL,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem 100 mTBILL when recipient == msg.sender (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, redemptionVaultWithBUIDL, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mTBILL,
- redemptionVaultWithBUIDL,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient: regularAccounts[0],
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem 100 mTBILL when other fn overload is paused (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- redemptionVaultWithBUIDL,
- encodeFnSelector('redeemInstant(address,uint256,uint256)'),
- );
- await mintToken(stableCoins.usdc, redemptionVaultWithBUIDL, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mTBILL,
- redemptionVaultWithBUIDL,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem 100 mTBILL when other fn overload is paused', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- redemptionVaultWithBUIDL,
- encodeFnSelector('redeemInstant(address,uint256,uint256,address)'),
- );
- await mintToken(stableCoins.usdc, redemptionVaultWithBUIDL, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mTBILL,
- redemptionVaultWithBUIDL,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
- });
-
- describe('redeemRequest()', () => {
- it('should fail: when there is no token in vault', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'MV: token not exists',
- },
- );
- });
-
- it('should fail: when trying to redeem 0 amount', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 0,
- {
- revertMessage: 'RV: invalid amount',
- },
- );
- });
-
- it('should fail: when function paused', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- redemptionVaultWithBUIDL,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector('redeemRequest(address,uint256)');
- await pauseVaultFn(redemptionVaultWithBUIDL, selector);
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: call with insufficient allowance', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'ERC20: insufficient allowance',
- },
- );
- });
-
- it('should fail: call with insufficient balance', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, mTBILL, redemptionVaultWithBUIDL, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'ERC20: transfer amount exceeds balance',
- },
- );
- });
-
- it('should fail: dataFeed rate 0 ', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- dataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, stableCoins.dai, redemptionVaultWithBUIDL, 10);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await mintToken(mTBILL, owner, 100_000);
- await setRoundData({ mockedAggregator }, 0);
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'DF: feed is deprecated',
- },
- );
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 0);
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'DF: feed is deprecated',
- },
- );
- });
-
- it('should fail: call for amount < minAmount', async () => {
- const {
- redemptionVaultWithBUIDL,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mTBILL, owner, 100_000);
- await approveBase18(owner, mTBILL, redemptionVaultWithBUIDL, 100_000);
-
- await setMinAmountTest(
- { vault: redemptionVaultWithBUIDL, owner },
- 100_000,
- );
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'RV: amount < min',
- },
- );
- });
-
- it('should fail: if some fee = 100%', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVaultWithBUIDL, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 10000,
- true,
- );
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'RV: amountMTokenIn < fee',
- },
- );
- });
-
- it('should fail: greenlist enabled and user not in greenlist ', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redemptionVaultWithBUIDL.setGreenlistEnable(true);
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: user in blacklist ', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: user in sanctions list', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- regularAccounts[0],
- );
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('should fail: when function paused (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- customRecipient,
- } = await loadFixture(defaultDeploy);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- redemptionVaultWithBUIDL,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector(
- 'redeemRequest(address,uint256,address)',
- );
- await pauseVaultFn(redemptionVaultWithBUIDL, selector);
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: greenlist enabled and recipient not in greenlist (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await redemptionVaultWithBUIDL.setGreenlistEnable(true);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: recipient in blacklist (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- customRecipient,
- );
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: recipient in sanctions list (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- customRecipient,
- );
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('should fail: user try to redeem fiat in basic request (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVaultWithBUIDL, 100);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- await redemptionVaultWithBUIDL.MANUAL_FULLFILMENT_TOKEN(),
- 100,
- {
- revertMessage: 'RV: tokenOut == fiat',
- },
- );
- });
-
- it('should fail: user try to redeem fiat in basic request', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVaultWithBUIDL, 100);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- await redemptionVaultWithBUIDL.MANUAL_FULLFILMENT_TOKEN(),
- 100,
- {
- revertMessage: 'RV: tokenOut == fiat',
- },
- );
- });
-
- it('redeem request 100 mTBILL, greenlist enabled and user in greenlist ', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- greenListableTester,
- mTokenToUsdDataFeed,
- accessControl,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redemptionVaultWithBUIDL.setGreenlistEnable(true);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await mintToken(stableCoins.dai, redemptionVaultWithBUIDL, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mTBILL,
- redemptionVaultWithBUIDL,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem request 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVaultWithBUIDL, 100000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVaultWithBUIDL, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- });
-
- it('redeem request 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$ and token fee 1%', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVaultWithBUIDL, 100000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVaultWithBUIDL, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- });
-
- it('redeem request 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$ without checking of minDepositAmount', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVaultWithBUIDL, 100000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVaultWithBUIDL, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await redemptionVaultWithBUIDL.freeFromMinAmount(owner.address, true);
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- });
-
- it('redeem request 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$ and user in waivedFeeRestriction', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVaultWithBUIDL, 100000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVaultWithBUIDL, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithBUIDL, owner },
- owner.address,
- );
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- waivedFee: true,
- },
- stableCoins.dai,
- 100,
- );
- });
-
- it('redeem request 100 mTBILL (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, redemptionVaultWithBUIDL, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mTBILL,
- redemptionVaultWithBUIDL,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem request 100 mTBILL when recipient == msg.sender (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, redemptionVaultWithBUIDL, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mTBILL,
- redemptionVaultWithBUIDL,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient: regularAccounts[0],
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem request 100 mTBILL when other fn overload is paused (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- redemptionVaultWithBUIDL,
- encodeFnSelector('redeemRequest(address,uint256)'),
- );
- await mintToken(stableCoins.usdc, redemptionVaultWithBUIDL, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mTBILL,
- redemptionVaultWithBUIDL,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem request 100 mTBILL when other fn overload is paused', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- redemptionVaultWithBUIDL,
- encodeFnSelector('redeemRequest(address,uint256,address)'),
- );
- await mintToken(stableCoins.usdc, redemptionVaultWithBUIDL, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mTBILL,
- redemptionVaultWithBUIDL,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithBUIDL, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithBUIDL,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
- });
-
- describe('redeemFiatRequest()', () => {
- it('should fail: when trying to redeem 0 amount', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL: redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 0,
- {
- from: regularAccounts[0],
- revertMessage: 'RV: invalid amount',
- },
- );
- });
-
- it('should fail: call with insufficient allowance', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL: redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100);
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 100,
- {
- revertMessage: 'ERC20: insufficient allowance',
- },
- );
- });
-
- it('should fail: when function paused', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- redemptionVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector('redeemFiatRequest(uint256)');
- await pauseVaultFn(redemptionVault, selector);
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: call with insufficient balance', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL: redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
-
- 100,
- {
- revertMessage: 'ERC20: transfer amount exceeds balance',
- },
- );
- });
-
- it('should fail: dataFeed rate 0 ', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL: redemptionVault,
- mTBILL,
- mockedAggregatorMToken,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100_000);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 0);
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 10,
- {
- revertMessage: 'DF: feed is deprecated',
- },
- );
- });
-
- it('should fail: call for amount < minFiatRedeemAmount', async () => {
- const {
- redemptionVaultWithBUIDL: redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100_000);
- await approveBase18(owner, mTBILL, redemptionVault, 100_000);
-
- await setMinFiatRedeemAmountTest({ redemptionVault, owner }, 100_000);
-
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 99_999,
- {
- revertMessage: 'RV: amount < min',
- },
- );
- });
-
- it('should fail: if some fee = 100%', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL: redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await setFiatAdditionalFeeTest({ redemptionVault, owner }, 10000);
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 100,
- {
- revertMessage: 'RV: amountMTokenIn < fee',
- },
- );
- });
-
- it('should fail: greenlist enabled and user not in greenlist ', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL: redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redemptionVault.setGreenlistEnable(true);
-
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: user in blacklist ', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL: redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- regularAccounts,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: user in sanctions list', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL: redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- regularAccounts[0],
- );
-
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('redeem fiat request 100 mTBILL, greenlist enabled and user in greenlist ', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL: redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await redemptionVault.setGreenlistEnable(true);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
-
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem fiat request 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithBUIDL: redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 100,
- );
- });
-
- it('redeem fiat request 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$ and token fee 1%', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithBUIDL: redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 100,
- );
- });
-
- it('redeem fiat request 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$ without checking of minDepositAmount', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithBUIDL: redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await redemptionVault.freeFromMinAmount(owner.address, true);
-
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 100,
- );
- });
-
- it('redeem fiat request 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$ and user in waivedFeeRestriction', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithBUIDL: redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await addWaivedFeeAccountTest(
- { vault: redemptionVault, owner },
- owner.address,
- );
- await redeemFiatRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- waivedFee: true,
- },
- 100,
- );
- });
- });
-
- describe('approveRequest()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- redemptionVaultWithBUIDL: redemptionVault,
- regularAccounts,
- mTokenToUsdDataFeed,
- mTBILL,
- } = await loadFixture(defaultDeploy);
- await approveRedeemRequestTest(
- {
- redemptionVault,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 1,
- parseUnits('1'),
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await approveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 1,
- parseUnits('1'),
- {
- revertMessage: 'RV: request not exist',
- },
- );
- });
-
- it('should fail: request already processed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithBUIDL: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await approveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('1'),
- );
- await approveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('1'),
- { revertMessage: 'RV: request not pending' },
- );
- });
-
- it('approve request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithBUIDL: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await approveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('1'),
- );
- });
- });
-
- describe('approveRequest() with fiat', async () => {
- it('approve request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithBUIDL: redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 100,
- );
- const requestId = 0;
- await changeTokenAllowanceTest(
- { vault: redemptionVault, owner },
- constants.AddressZero,
- parseUnits('100'),
- );
-
- await approveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('1'),
- );
- });
- });
-
- describe('safeApproveRequest()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- redemptionVaultWithBUIDL: redemptionVault,
- regularAccounts,
- mTokenToUsdDataFeed,
- mTBILL,
- } = await loadFixture(defaultDeploy);
- await safeApproveRedeemRequestTest(
- {
- redemptionVault,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 1,
- parseUnits('1'),
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 1,
- parseUnits('1'),
- {
- revertMessage: 'RV: request not exist',
- },
- );
- });
-
- it('should fail: if new rate greater then variabilityTolerance', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithBUIDL: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('6'),
- { revertMessage: 'MV: exceed price diviation' },
- );
- });
-
- it('should fail: request already processed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithBUIDL: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('5.000001'),
- );
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('5.00001'),
- { revertMessage: 'RV: request not pending' },
- );
- });
-
- it('safe approve request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithBUIDL: redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('5.000001'),
- );
- });
- });
-
- describe('rejectRequest()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- redemptionVaultWithBUIDL: redemptionVault,
- regularAccounts,
- mTokenToUsdDataFeed,
- mTBILL,
- } = await loadFixture(defaultDeploy);
- await rejectRedeemRequestTest(
- {
- redemptionVault,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- redemptionVaultWithBUIDL: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await rejectRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 1,
- {
- revertMessage: 'RV: request not exist',
- },
- );
- });
-
- it('should fail: request already processed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithBUIDL: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await rejectRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- );
- await rejectRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- { revertMessage: 'RV: request not pending' },
- );
- });
-
- it('safe approve request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithBUIDL: redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await rejectRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- );
- });
- });
-
- describe('redeemInstant() complex', () => {
- it('should fail: when is paused', async () => {
- const {
- redemptionVaultWithBUIDL: redemptionVault,
- owner,
- mTBILL,
- stableCoins,
- regularAccounts,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVault(redemptionVault);
- await mintToken(stableCoins.usdc, redemptionVault, 100);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- redemptionVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: paused',
- },
- );
- });
-
- it('is on pause, but admin can use everything', async () => {
- const {
- redemptionVaultWithBUIDL: redemptionVault,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVault(redemptionVault);
-
- await mintToken(stableCoins.usdc, redemptionVault, 100);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.usdc,
- 100,
- {
- revertMessage: 'Pausable: paused',
- },
- );
- });
-
- it('call for amount == minAmount, if USDC balance minAmount/2 and BUIDL balance minAmount/2', async () => {
- const {
- redemptionVaultWithBUIDL: redemptionVault,
- mockedAggregator,
- mockedAggregatorMToken,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- buidl,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mTBILL, owner, 100_000);
- await mintToken(stableCoins.usdc, redemptionVault, 50_000);
- await mintToken(buidl, redemptionVault, 50_000);
- await approveBase18(owner, mTBILL, redemptionVault, 100_000);
-
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setMinAmountTest({ vault: redemptionVault, owner }, 100_000);
-
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.usdc,
- 100_000,
- );
- });
-
- it('redeem 100 mtbill, when price is 5$ and contract balance 100 USDC and 100000 BUIDL, 125 mtbill when price is 5.1$, 114 mtbill when price is 5.4$', async () => {
- const {
- owner,
- mockedAggregator,
- redemptionVaultWithBUIDL: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- mockedAggregatorMToken,
- buidl,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100 + 125 + 114);
-
- await mintToken(stableCoins.usdc, redemptionVault, 100);
- await mintToken(buidl, redemptionVault, 100000);
-
- await approveBase18(owner, mTBILL, redemptionVault, 100 + 125 + 114);
-
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setRoundData({ mockedAggregator }, 1.04);
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.usdc,
- 100,
- );
-
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5.1);
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.usdc,
- 125,
- );
-
- await setRoundData({ mockedAggregator }, 1.01);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5.4);
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.usdc,
- 114,
- );
- });
- });
-
- describe('redeemRequest() complex', () => {
- it('should fail: when is paused', async () => {
- const {
- redemptionVaultWithBUIDL: redemptionVault,
- owner,
- mTBILL,
- stableCoins,
- regularAccounts,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVault(redemptionVault);
- await mintToken(stableCoins.dai, redemptionVault, 100);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- redemptionVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: paused',
- },
- );
- });
-
- it('is on pause, but admin can use everything', async () => {
- const {
- redemptionVaultWithBUIDL: redemptionVault,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVault(redemptionVault);
-
- await mintToken(stableCoins.dai, redemptionVault, 1000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, stableCoins.dai, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'Pausable: paused',
- },
- );
- });
-
- it('call for amount == minAmount, then approve', async () => {
- const {
- redemptionVaultWithBUIDL: redemptionVault,
- mockedAggregator,
- mockedAggregatorMToken,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mTBILL, owner, 100_000);
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await approveBase18(owner, mTBILL, redemptionVault, 100_000);
-
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setMinAmountTest({ vault: redemptionVault, owner }, 100_000);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100_000,
- );
-
- const requestId = 0;
-
- await approveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('1'),
- );
- });
-
- it('call for amount == minAmount, then safe approve', async () => {
- const {
- redemptionVaultWithBUIDL: redemptionVault,
- mockedAggregator,
- mockedAggregatorMToken,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- requestRedeemer,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mTBILL, owner, 100_000);
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await approveBase18(owner, mTBILL, redemptionVault, 100_000);
-
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setMinAmountTest({ vault: redemptionVault, owner }, 10_000);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 10_000,
- );
-
- const requestId = 0;
-
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('1.000001'),
- );
- });
-
- it('call for amount == minAmount, then reject', async () => {
- const {
- redemptionVaultWithBUIDL: redemptionVault,
- mockedAggregator,
- mockedAggregatorMToken,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mTBILL, owner, 100_000);
- await mintToken(stableCoins.dai, redemptionVault, 100_000);
- await approveBase18(owner, mTBILL, redemptionVault, 100_000);
-
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setMinAmountTest({ vault: redemptionVault, owner }, 100_000);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100_000,
- );
-
- const requestId = 0;
-
- await rejectRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- );
- });
- });
-});
diff --git a/test/unit/RedemptionVaultWithMToken.test.ts b/test/unit/RedemptionVaultWithMToken.test.ts
index 0210ccb1..5c661b49 100644
--- a/test/unit/RedemptionVaultWithMToken.test.ts
+++ b/test/unit/RedemptionVaultWithMToken.test.ts
@@ -1,19 +1,27 @@
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
+import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { expect } from 'chai';
import { BigNumber, constants } from 'ethers';
import { parseUnits } from 'ethers/lib/utils';
-import { ethers } from 'hardhat';
+
+import {
+ baseInitParamsRv,
+ redemptionVaultSuits,
+} from './suits/redemption-vault.suits';
import { encodeFnSelector } from '../../helpers/utils';
import {
- ManageableVaultTester__factory,
+ RedemptionVaultWithMToken__factory,
+ RedemptionVaultWithMTokenTest,
RedemptionVaultWithMTokenTest__factory,
} from '../../typechain-types';
-import { acErrors, blackList, greenList } from '../common/ac.helpers';
+import { acErrors } from '../common/ac.helpers';
import {
approveBase18,
+ InitializeParamCase,
mintToken,
pauseVaultFn,
+ validateImplementation,
} from '../common/common.helpers';
import { setRoundData } from '../common/data-feed.helpers';
import { defaultDeploy } from '../common/fixtures';
@@ -21,4105 +29,2435 @@ import {
addPaymentTokenTest,
setInstantFeeTest,
setMinAmountTest,
- setInstantDailyLimitTest,
- addWaivedFeeAccountTest,
- removeWaivedFeeAccountTest,
- setVariabilityToleranceTest,
- removePaymentTokenTest,
- withdrawTest,
- changeTokenFeeTest,
- changeTokenAllowanceTest,
+ setWaivedFeeAccountTest,
} from '../common/manageable-vault.helpers';
-import { redeemInstantWithMTokenTest } from '../common/redemption-vault-mtoken.helpers';
import {
- approveRedeemRequestTest,
- redeemFiatRequestTest,
- redeemRequestTest,
- rejectRedeemRequestTest,
- safeApproveRedeemRequestTest,
- setFiatAdditionalFeeTest,
- setMinFiatRedeemAmountTest,
+ redeemInstantWithMTokenTest,
+ setRedemptionVaultTest,
+} from '../common/redemption-vault-mtoken.helpers';
+import {
+ setLoanLpTest,
+ setLoanSwapperVaultTest,
+ redeemInstantTest,
+ setPreferLoanLiquidityTest,
} from '../common/redemption-vault.helpers';
-import { sanctionUser } from '../common/with-sanctions-list.helpers';
+import {
+ initializeRvWithMToken,
+ InitializerParamsRvWithMToken,
+} from '../common/vault-initializer.helpers';
+
+const baseInitParamsRvWithMToken = (
+ fixture: Parameters[0],
+): InitializerParamsRvWithMToken => ({
+ ...baseInitParamsRv(fixture),
+ redemptionVault: fixture.redemptionVaultLoanSwapper,
+});
-describe('RedemptionVaultWithMToken', function () {
- it('deployment', async () => {
- const {
- redemptionVaultWithMToken,
- redemptionVault,
- mFONE,
- tokensReceiver,
- feeReceiver,
- mFoneToUsdDataFeed,
- roles,
- } = await loadFixture(defaultDeploy);
+const rvWithMTokenInitializeParamCases: InitializeParamCase[] =
+ [
+ {
+ title: 'redemptionVault is zero address',
+ params: { redemptionVault: constants.AddressZero },
+ revertCustomError: {
+ customErrorName: 'InvalidAddress',
+ args: [constants.AddressZero],
+ },
+ },
+ ];
+
+redemptionVaultSuits(
+ 'RedemptionVaultWithMToken',
+ defaultDeploy,
+ {
+ createNew: async (owner: SignerWithAddress) =>
+ new RedemptionVaultWithMTokenTest__factory(owner).deploy(),
+ key: 'redemptionVaultWithMToken',
+ },
+ async (fixture) => {
+ const { redemptionVaultWithMToken, redemptionVaultLoanSwapper } = fixture;
+ expect(await redemptionVaultWithMToken.redemptionVault()).eq(
+ redemptionVaultLoanSwapper.address,
+ );
+ await validateImplementation(RedemptionVaultWithMToken__factory);
+ },
+ {
+ deployUninitialized: (fixture) =>
+ new RedemptionVaultWithMTokenTest__factory(fixture.owner).deploy(),
+ initialize: async (fixture, params, opt) => {
+ await initializeRvWithMToken(
+ { ...baseInitParamsRvWithMToken(fixture), ...params },
+ opt?.contract as RedemptionVaultWithMTokenTest,
+ opt,
+ );
+ },
+ extraParamCases: rvWithMTokenInitializeParamCases,
+ },
+ async (defaultDeploy) => {
+ describe('RedemptionVaultWithMToken', () => {
+ describe('setRedemptionVault()', () => {
+ it('should fail: call from address without vault admin role', async () => {
+ const { redemptionVaultWithMToken, regularAccounts } =
+ await loadFixture(defaultDeploy);
+ await expect(
+ redemptionVaultWithMToken
+ .connect(regularAccounts[0])
+ .setRedemptionVault(regularAccounts[1].address),
+ ).to.be.revertedWithCustomError(
+ redemptionVaultWithMToken,
+ acErrors.WMAC_HASNT_PERMISSION().customErrorName,
+ );
+ });
- expect(await redemptionVaultWithMToken.mToken()).eq(mFONE.address);
+ it('should fail: zero address', async () => {
+ const { redemptionVaultWithMToken } = await loadFixture(
+ defaultDeploy,
+ );
+ await expect(
+ redemptionVaultWithMToken.setRedemptionVault(constants.AddressZero),
+ ).to.be.revertedWithCustomError(
+ redemptionVaultWithMToken,
+ 'InvalidAddress',
+ );
+ });
- expect(await redemptionVaultWithMToken.ONE_HUNDRED_PERCENT()).eq('10000');
+ it('should fail: same address', async () => {
+ const { redemptionVaultWithMToken, redemptionVaultLoanSwapper } =
+ await loadFixture(defaultDeploy);
+ await expect(
+ redemptionVaultWithMToken.setRedemptionVault(
+ redemptionVaultLoanSwapper.address,
+ ),
+ ).to.be.revertedWithCustomError(
+ redemptionVaultWithMToken,
+ 'SameAddressValue',
+ );
+ });
- expect(await redemptionVaultWithMToken.paused()).eq(false);
+ it('should fail: when function is paused', async () => {
+ const {
+ redemptionVaultWithMToken,
+ owner,
+ regularAccounts,
+ pauseManager,
+ } = await loadFixture(defaultDeploy);
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVaultWithMToken,
+ encodeFnSelector('setRedemptionVault(address)'),
+ );
+ await setRedemptionVaultTest(
+ { vault: redemptionVaultWithMToken, owner },
+ regularAccounts[0].address,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
- expect(await redemptionVaultWithMToken.tokensReceiver()).eq(
- tokensReceiver.address,
- );
- expect(await redemptionVaultWithMToken.feeReceiver()).eq(
- feeReceiver.address,
- );
+ it('should succeed and emit SetRedemptionVault event', async () => {
+ const { redemptionVaultWithMToken, owner, regularAccounts } =
+ await loadFixture(defaultDeploy);
- expect(await redemptionVaultWithMToken.minAmount()).eq(1000);
- expect(await redemptionVaultWithMToken.minFiatRedeemAmount()).eq(1000);
+ const newVault = regularAccounts[0].address;
- expect(await redemptionVaultWithMToken.instantFee()).eq('100');
+ await expect(redemptionVaultWithMToken.setRedemptionVault(newVault))
+ .to.emit(redemptionVaultWithMToken, 'SetRedemptionVault')
+ .withArgs(newVault);
- expect(await redemptionVaultWithMToken.instantDailyLimit()).eq(
- parseUnits('100000'),
- );
+ expect(await redemptionVaultWithMToken.redemptionVault()).eq(
+ newVault,
+ );
+ });
+ });
- expect(await redemptionVaultWithMToken.mTokenDataFeed()).eq(
- mFoneToUsdDataFeed.address,
- );
- expect(await redemptionVaultWithMToken.variationTolerance()).eq(1);
+ describe('checkAndRedeemMToken()', () => {
+ it('should not redeem when vault has sufficient tokenOut balance', async () => {
+ const { redemptionVaultWithMToken, stableCoins, mTokenLoan } =
+ await loadFixture(defaultDeploy);
- expect(await redemptionVaultWithMToken.vaultRole()).eq(
- roles.tokenRoles.mTBILL.redemptionVaultAdmin,
- );
+ await mintToken(stableCoins.dai, redemptionVaultWithMToken, 1000);
+ await mintToken(mTokenLoan, redemptionVaultWithMToken, 1000);
- expect(await redemptionVaultWithMToken.MANUAL_FULLFILMENT_TOKEN()).eq(
- ethers.constants.AddressZero,
- );
+ await expect(
+ redemptionVaultWithMToken.checkAndRedeemMToken(
+ stableCoins.dai.address,
+ parseUnits('500', 9),
+ parseUnits('1'),
+ ),
+ ).not.reverted;
- expect(await redemptionVaultWithMToken.redemptionVault()).eq(
- redemptionVault.address,
- );
- });
-
- it('failing deployment', async () => {
- const {
- mFONE,
- tokensReceiver,
- feeReceiver,
- mFoneToUsdDataFeed,
- accessControl,
- mockedSanctionsList,
- owner,
- } = await loadFixture(defaultDeploy);
-
- const redemptionVaultWithMToken =
- await new RedemptionVaultWithMTokenTest__factory(owner).deploy();
-
- await expect(
- redemptionVaultWithMToken[
- 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,(uint256,uint256,uint256),address)'
- ](
- accessControl.address,
- {
- mToken: mFONE.address,
- mTokenDataFeed: mFoneToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- {
- fiatAdditionalFee: 10000,
- fiatFlatFee: parseUnits('1'),
- minFiatRedeemAmount: parseUnits('100'),
- },
- constants.AddressZero,
- ),
- ).to.be.reverted;
- });
-
- describe('initialization', () => {
- it('should fail: call initialize() when already initialized', async () => {
- const { redemptionVaultWithMToken } = await loadFixture(defaultDeploy);
-
- await expect(
- redemptionVaultWithMToken[
- 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,(uint256,uint256,uint256),address,address)'
- ](
- constants.AddressZero,
- {
- mToken: constants.AddressZero,
- mTokenDataFeed: constants.AddressZero,
- },
- {
- feeReceiver: constants.AddressZero,
- tokensReceiver: constants.AddressZero,
- },
- {
- instantFee: 0,
- instantDailyLimit: 0,
- },
- constants.AddressZero,
- 0,
- 0,
- {
- fiatAdditionalFee: 0,
- fiatFlatFee: 0,
- minFiatRedeemAmount: 0,
- },
- constants.AddressZero,
- constants.AddressZero,
- ),
- ).revertedWith('Initializable: contract is already initialized');
- });
+ expect(
+ await stableCoins.dai.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.eq(parseUnits('1000', 9));
+ expect(
+ await mTokenLoan.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.eq(parseUnits('1000', 18));
+ });
- it('should fail: call with initializing == false', async () => {
- const {
- owner,
- accessControl,
- mFONE,
- tokensReceiver,
- feeReceiver,
- mFoneToUsdDataFeed,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initializeWithoutInitializer(
- accessControl.address,
- {
- mToken: mFONE.address,
- mTokenDataFeed: mFoneToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- 1000,
- ),
- ).revertedWith('Initializable: contract is not initializing');
- });
+ it('should redeem missing amount via mToken RV', async () => {
+ const {
+ redemptionVaultWithMToken,
+ stableCoins,
+ mTokenLoan,
+ owner,
+ dataFeed,
+ redemptionVaultLoanSwapper,
+ } = await loadFixture(defaultDeploy);
- it('should fail: when redemptionVault address zero', async () => {
- const {
- owner,
- accessControl,
- mFONE,
- tokensReceiver,
- feeReceiver,
- mFoneToUsdDataFeed,
- mockedSanctionsList,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- const redemptionVaultWithMToken =
- await new RedemptionVaultWithMTokenTest__factory(owner).deploy();
-
- await expect(
- redemptionVaultWithMToken[
- 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,(uint256,uint256,uint256),address,address)'
- ](
- accessControl.address,
- {
- mToken: mFONE.address,
- mTokenDataFeed: mFoneToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- 1000,
- {
- fiatAdditionalFee: 100,
- fiatFlatFee: parseUnits('1'),
- minFiatRedeemAmount: 1000,
- },
- requestRedeemer.address,
- constants.AddressZero,
- ),
- ).revertedWith('zero address');
- });
- });
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMToken, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
- describe('setRedemptionVault()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { redemptionVaultWithMToken, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- redemptionVaultWithMToken
- .connect(regularAccounts[0])
- .setRedemptionVault(regularAccounts[1].address),
- ).to.be.revertedWith('WMAC: hasnt role');
- });
+ await mintToken(stableCoins.dai, redemptionVaultWithMToken, 500);
+ await mintToken(mTokenLoan, redemptionVaultWithMToken, 10000);
+ await mintToken(
+ stableCoins.dai,
+ redemptionVaultLoanSwapper,
+ 1_000_000,
+ );
- it('should fail: zero address', async () => {
- const { redemptionVaultWithMToken } = await loadFixture(defaultDeploy);
- await expect(
- redemptionVaultWithMToken.setRedemptionVault(constants.AddressZero),
- ).to.be.revertedWith('zero address');
- });
+ const mTokenLoanBefore = await mTokenLoan.balanceOf(
+ redemptionVaultWithMToken.address,
+ );
- it('should fail: same address', async () => {
- const { redemptionVaultWithMToken, redemptionVault } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- redemptionVaultWithMToken.setRedemptionVault(redemptionVault.address),
- ).to.be.revertedWith('RVMT: already set');
- });
+ await redemptionVaultWithMToken.checkAndRedeemMToken(
+ stableCoins.dai.address,
+ parseUnits('1000', 9),
+ parseUnits('1'),
+ );
- it('should succeed and emit SetRedemptionVault event', async () => {
- const { redemptionVaultWithMToken, owner, regularAccounts } =
- await loadFixture(defaultDeploy);
+ const mTokenLoanAfter = await mTokenLoan.balanceOf(
+ redemptionVaultWithMToken.address,
+ );
+ expect(mTokenLoanAfter).to.be.lt(mTokenLoanBefore);
- const newVault = regularAccounts[0].address;
+ const daiAfter = await stableCoins.dai.balanceOf(
+ redemptionVaultWithMToken.address,
+ );
+ expect(daiAfter).to.be.gte(parseUnits('1000', 9));
+ });
- await expect(redemptionVaultWithMToken.setRedemptionVault(newVault))
- .to.emit(redemptionVaultWithMToken, 'SetRedemptionVault')
- .withArgs(owner.address, newVault);
+ it('shouldnt revert when insufficient mTokenLoan balance', async () => {
+ const {
+ redemptionVaultWithMToken,
+ stableCoins,
+ owner,
+ dataFeed,
+ redemptionVaultLoanSwapper,
+ } = await loadFixture(defaultDeploy);
- expect(await redemptionVaultWithMToken.redemptionVault()).eq(newVault);
- });
- });
-
- describe('setMinAmount()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { owner, redemptionVaultWithMToken, regularAccounts } =
- await loadFixture(defaultDeploy);
- await setMinAmountTest(
- { vault: redemptionVaultWithMToken, owner },
- 10000,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithMToken } = await loadFixture(
- defaultDeploy,
- );
- await setMinAmountTest(
- { vault: redemptionVaultWithMToken, owner },
- 10000,
- );
- });
- });
-
- describe('setMinFiatRedeemAmount()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { owner, redemptionVaultWithMToken, regularAccounts } =
- await loadFixture(defaultDeploy);
- await setMinFiatRedeemAmountTest(
- { redemptionVault: redemptionVaultWithMToken, owner },
- 10000,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithMToken } = await loadFixture(
- defaultDeploy,
- );
- await setMinFiatRedeemAmountTest(
- { redemptionVault: redemptionVaultWithMToken, owner },
- 10000,
- );
- });
- });
-
- describe('setFiatAdditionalFee()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { owner, redemptionVaultWithMToken, regularAccounts } =
- await loadFixture(defaultDeploy);
- await setFiatAdditionalFeeTest(
- { redemptionVault: redemptionVaultWithMToken, owner },
- 100,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithMToken } = await loadFixture(
- defaultDeploy,
- );
- await setFiatAdditionalFeeTest(
- { redemptionVault: redemptionVaultWithMToken, owner },
- 100,
- );
- });
- });
-
- describe('setInstantDailyLimit()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { owner, redemptionVaultWithMToken, regularAccounts } =
- await loadFixture(defaultDeploy);
- await setInstantDailyLimitTest(
- { vault: redemptionVaultWithMToken, owner },
- parseUnits('1000'),
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithMToken } = await loadFixture(
- defaultDeploy,
- );
- await setInstantDailyLimitTest(
- { vault: redemptionVaultWithMToken, owner },
- parseUnits('1000'),
- );
- });
- it('should fail: when limit is zero', async () => {
- const { owner, redemptionVaultWithMToken } = await loadFixture(
- defaultDeploy,
- );
- await setInstantDailyLimitTest(
- { vault: redemptionVaultWithMToken, owner },
- constants.Zero,
- { revertMessage: 'MV: limit zero' },
- );
- });
- });
-
- describe('addPaymentToken()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- undefined,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithMToken, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- });
- });
-
- describe('removePaymentToken()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await removePaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithMToken, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await removePaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- );
- });
- });
-
- describe('addWaivedFeeAccount()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { owner, redemptionVaultWithMToken, regularAccounts } =
- await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithMToken, owner },
- regularAccounts[0].address,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithMToken, regularAccounts } =
- await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithMToken, owner },
- regularAccounts[0].address,
- );
- });
- });
-
- describe('removeWaivedFeeAccount()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { owner, redemptionVaultWithMToken, regularAccounts } =
- await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithMToken, owner },
- regularAccounts[0].address,
- );
- await removeWaivedFeeAccountTest(
- { vault: redemptionVaultWithMToken, owner },
- regularAccounts[0].address,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithMToken, regularAccounts } =
- await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithMToken, owner },
- regularAccounts[0].address,
- );
- await removeWaivedFeeAccountTest(
- { vault: redemptionVaultWithMToken, owner },
- regularAccounts[0].address,
- );
- });
- });
-
- describe('setFee()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { owner, redemptionVaultWithMToken, regularAccounts } =
- await loadFixture(defaultDeploy);
- await setInstantFeeTest(
- { vault: redemptionVaultWithMToken, owner },
- 100,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithMToken } = await loadFixture(
- defaultDeploy,
- );
- await setInstantFeeTest({ vault: redemptionVaultWithMToken, owner }, 100);
- });
- });
-
- describe('setVariabilityTolerance()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { owner, redemptionVaultWithMToken, regularAccounts } =
- await loadFixture(defaultDeploy);
- await setVariabilityToleranceTest(
- { vault: redemptionVaultWithMToken, owner },
- 100,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithMToken } = await loadFixture(
- defaultDeploy,
- );
- await setVariabilityToleranceTest(
- { vault: redemptionVaultWithMToken, owner },
- 100,
- );
- });
- });
-
- describe('withdrawToken()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { owner, redemptionVaultWithMToken, stableCoins, regularAccounts } =
- await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, redemptionVaultWithMToken, 1);
- await withdrawTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- 1,
- owner,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithMToken, stableCoins } =
- await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, redemptionVaultWithMToken, 100);
- await withdrawTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- 100,
- owner,
- );
- });
- });
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMToken, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
- describe('freeFromMinAmount()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const { redemptionVaultWithMToken, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- redemptionVaultWithMToken
- .connect(regularAccounts[0])
- .freeFromMinAmount(regularAccounts[1].address, true),
- ).to.be.revertedWith('WMAC: hasnt role');
- });
+ await expect(
+ redemptionVaultWithMToken.checkAndRedeemMToken(
+ stableCoins.dai.address,
+ parseUnits('1000', 9),
+ parseUnits('1'),
+ ),
+ ).to.not.be.reverted;
+ });
+ });
- it('should not fail', async () => {
- const { redemptionVaultWithMToken, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- redemptionVaultWithMToken.freeFromMinAmount(
- regularAccounts[0].address,
- true,
- ),
- ).to.not.reverted;
-
- expect(
- await redemptionVaultWithMToken.isFreeFromMinAmount(
- regularAccounts[0].address,
- ),
- ).to.eq(true);
- });
- });
-
- describe('changeTokenFee()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenFeeTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai.address,
- 100,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithMToken, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenFeeTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai.address,
- 100,
- );
- });
- });
-
- describe('changeTokenAllowance()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai.address,
- 100,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithMToken, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai.address,
- 100,
- );
- });
- });
-
- describe('checkAndRedeemMToken()', () => {
- it('should not redeem mTBILL when vault has sufficient tokenOut balance', async () => {
- const {
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- owner,
- dataFeed,
- redemptionVault,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
+ describe('redeemInstant()', () => {
+ describe('preferLoanLiquidity=true', () => {
+ it('redeem 100 mTBILL, when vault has enough DAI and all fees are 0', async () => {
+ const {
+ owner,
+ redemptionVaultWithMToken,
+ stableCoins,
+ mTokenLoan,
+ mTBILL,
+ mTokenLoanToUsdDataFeed,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ mockedAggregator,
+ } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, redemptionVaultWithMToken, 1000);
- await mintToken(mTBILL, redemptionVaultWithMToken, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMToken, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
- const mTbillBefore = await mTBILL.balanceOf(
- redemptionVaultWithMToken.address,
- );
+ await setRoundData({ mockedAggregator }, 1);
+ await setPreferLoanLiquidityTest(
+ { redemptionVault: redemptionVaultWithMToken, owner },
+ true,
+ );
- await redemptionVaultWithMToken.checkAndRedeemMToken(
- stableCoins.dai.address,
- parseUnits('500', 9),
- parseUnits('1'),
- );
+ await mintToken(mTBILL, owner, 100_000);
+ await mintToken(
+ stableCoins.dai,
+ redemptionVaultWithMToken,
+ 1_000_000,
+ );
+ await mintToken(mTokenLoan, redemptionVaultWithMToken, 100_000);
+ await approveBase18(
+ owner,
+ mTBILL,
+ redemptionVaultWithMToken,
+ 100_000,
+ );
- const mTbillAfter = await mTBILL.balanceOf(
- redemptionVaultWithMToken.address,
- );
- expect(mTbillAfter).to.equal(mTbillBefore);
- });
+ await setInstantFeeTest(
+ { vault: redemptionVaultWithMToken, owner },
+ 0,
+ );
- it('should redeem missing amount via mToken RV', async () => {
- const {
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- owner,
- dataFeed,
- redemptionVault,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
+ await redeemInstantWithMTokenTest(
+ {
+ redemptionVaultWithMToken,
+ owner,
+ mTokenLoan,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ mTokenLoanToUsdDataFeed,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ });
- await mintToken(stableCoins.dai, redemptionVaultWithMToken, 500);
- await mintToken(mTBILL, redemptionVaultWithMToken, 10000);
- await mintToken(stableCoins.dai, redemptionVault, 1_000_000);
+ it('when vault has no DAI but has mToken sleeve and LP liquidity, LP liquidity should be used first', async () => {
+ const {
+ owner,
+ redemptionVaultWithMToken,
+ stableCoins,
+ mTokenLoan,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ mockedAggregator,
+ mockedAggregatorMTokenLoan,
+ redemptionVaultLoanSwapper,
+ loanLp,
+ } = await loadFixture(defaultDeploy);
- const mTbillBefore = await mTBILL.balanceOf(
- redemptionVaultWithMToken.address,
- );
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMToken, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
- await redemptionVaultWithMToken.checkAndRedeemMToken(
- stableCoins.dai.address,
- parseUnits('1000', 9),
- parseUnits('1'),
- );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData(
+ { mockedAggregator: mockedAggregatorMTokenLoan },
+ 1,
+ );
+ await setInstantFeeTest(
+ { vault: redemptionVaultWithMToken, owner },
+ 0,
+ );
+ await setInstantFeeTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ 0,
+ );
+ await setLoanLpTest(
+ { redemptionVault: redemptionVaultWithMToken, owner },
+ loanLp.address,
+ );
+ await setLoanSwapperVaultTest(
+ { redemptionVault: redemptionVaultWithMToken, owner },
+ redemptionVaultLoanSwapper.address,
+ );
+ await setPreferLoanLiquidityTest(
+ { redemptionVault: redemptionVaultWithMToken, owner },
+ true,
+ );
- const mTbillAfter = await mTBILL.balanceOf(
- redemptionVaultWithMToken.address,
- );
- expect(mTbillAfter).to.be.lt(mTbillBefore);
+ await mintToken(mTBILL, owner, 100_000);
+ await mintToken(mTokenLoan, redemptionVaultWithMToken, 100_000);
+ await mintToken(mTokenLoan, loanLp, 100_000);
+ await mintToken(
+ stableCoins.dai,
+ redemptionVaultLoanSwapper,
+ 1_000_000,
+ );
+ await approveBase18(
+ owner,
+ mTBILL,
+ redemptionVaultWithMToken,
+ 100_000,
+ );
+ await approveBase18(
+ loanLp,
+ mTokenLoan,
+ redemptionVaultWithMToken,
+ 100_000,
+ );
- const daiAfter = await stableCoins.dai.balanceOf(
- redemptionVaultWithMToken.address,
- );
- expect(daiAfter).to.be.gte(parseUnits('1000', 9));
- });
+ const loanLpBalanceBefore = await mTokenLoan.balanceOf(
+ loanLp.address,
+ );
- it('should revert when insufficient mTBILL balance', async () => {
- const {
- redemptionVaultWithMToken,
- stableCoins,
- owner,
- dataFeed,
- redemptionVault,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await expect(
- redemptionVaultWithMToken.checkAndRedeemMToken(
- stableCoins.dai.address,
- parseUnits('1000', 9),
- parseUnits('1'),
- ),
- ).to.be.revertedWith('RVMT: balance < needed');
- });
- });
-
- describe('redeemInstant()', () => {
- it('should fail: when there is no token in vault', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- mFONE,
- mTokenToUsdDataFeed,
- mFoneToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'MV: token not exists',
- },
- );
- });
-
- it('should fail: when trying to redeem 0 amount', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- mFONE,
- mTokenToUsdDataFeed,
- mFoneToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 0,
- {
- revertMessage: 'RV: invalid amount',
- },
- );
- });
-
- it('should fail: call is paused', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- mFONE,
- mTokenToUsdDataFeed,
- mFoneToUsdDataFeed,
- dataFeed,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector(
- 'redeemInstant(address,uint256,uint256)',
- );
- await pauseVaultFn(redemptionVaultWithMToken, selector);
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: when user has no mFONE allowance', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- mFONE,
- mTokenToUsdDataFeed,
- mFoneToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await mintToken(mFONE, owner, 100_000);
-
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'ERC20: insufficient allowance',
- },
- );
- });
-
- it('should fail: when user has no mFONE balance', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- mFONE,
- mTokenToUsdDataFeed,
- mFoneToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 100_000);
-
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'ERC20: burn amount exceeds balance',
- },
- );
- });
-
- it('should fail: when data feed rate is 0', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- mFONE,
- mTokenToUsdDataFeed,
- mFoneToUsdDataFeed,
- dataFeed,
- mockedAggregatorMFone,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await mintToken(mFONE, owner, 100_000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 100_000);
- await setRoundData({ mockedAggregator: mockedAggregatorMFone }, 0);
-
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'DF: feed is deprecated',
- },
- );
- });
-
- it('should fail: when amount < minAmount', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- mFONE,
- mTokenToUsdDataFeed,
- mFoneToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await mintToken(mFONE, owner, 100_000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 100_000);
-
- await setMinAmountTest(
- { vault: redemptionVaultWithMToken, owner },
- 100_000,
- );
-
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 999,
- {
- revertMessage: 'RV: amount < min',
- },
- );
- });
-
- it('should fail: when token allowance exceeded', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- mFONE,
- mTokenToUsdDataFeed,
- mFoneToUsdDataFeed,
- dataFeed,
- mockedAggregator,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await mintToken(mFONE, owner, 100_000);
- await mintToken(stableCoins.dai, redemptionVaultWithMToken, 1_000_000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 100_000);
- await changeTokenAllowanceTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai.address,
- 100,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'MV: exceed allowance',
- },
- );
- });
-
- it('should fail: when daily limit exceeded', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- mFONE,
- mTokenToUsdDataFeed,
- mFoneToUsdDataFeed,
- dataFeed,
- mockedAggregator,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await mintToken(mFONE, owner, 100_000);
- await mintToken(stableCoins.dai, redemptionVaultWithMToken, 1_000_000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 100_000);
- await setInstantDailyLimitTest(
- { vault: redemptionVaultWithMToken, owner },
- parseUnits('1000'),
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'MV: exceed limit',
- },
- );
- });
-
- it('should fail: when fee is 100%', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- mFONE,
- mTokenToUsdDataFeed,
- mFoneToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await mintToken(mFONE, owner, 100_000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 100_000);
- await setInstantFeeTest(
- { vault: redemptionVaultWithMToken, owner },
- 10000,
- );
-
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'RV: amountMTokenIn < fee',
- },
- );
- });
-
- it('should fail: greenlist enabled and user not in greenlist', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- mFONE,
- mTokenToUsdDataFeed,
- mFoneToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redemptionVaultWithMToken.setGreenlistEnable(true);
-
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: user is blacklisted', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- mFONE,
- mTokenToUsdDataFeed,
- mFoneToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: user is sanctioned', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- mFONE,
- mTokenToUsdDataFeed,
- mFoneToUsdDataFeed,
- mockedSanctionsList,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- regularAccounts[0],
- );
-
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('should fail: user try to instant redeem fiat', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- mTBILL,
- mFONE,
- mTokenToUsdDataFeed,
- mFoneToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mFONE, owner, 100_000);
- await mintToken(mTBILL, redemptionVaultWithMToken, 100_000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 100_000);
-
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- await redemptionVaultWithMToken.MANUAL_FULLFILMENT_TOKEN(),
- 99_999,
- {
- revertMessage: 'MV: token not exists',
- },
- );
- });
-
- it('should fail: when inner vault fee is not waived', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- mFONE,
- mTokenToUsdDataFeed,
- mFoneToUsdDataFeed,
- dataFeed,
- redemptionVault,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- // Remove the waived fee — inner vault will charge fee on this contract
- await redemptionVault.removeWaivedFeeAccount(
- redemptionVaultWithMToken.address,
- );
-
- await mintToken(mFONE, owner, 100_000);
- await mintToken(mTBILL, redemptionVaultWithMToken, 100_000);
- await mintToken(stableCoins.dai, redemptionVault, 1_000_000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 100_000);
-
- // No DAI on vault — forces mTBILL redemption path where inner vault fee causes revert
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'RVMT: fees not waived on target',
- },
- );
- });
-
- it('should fail: vault has no mTBILL and no DAI', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- mFONE,
- mTokenToUsdDataFeed,
- mFoneToUsdDataFeed,
- dataFeed,
- redemptionVault,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await mintToken(mFONE, owner, 100_000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 100_000);
-
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'RVMT: balance < needed',
- },
- );
- });
-
- it('redeem 100 mFONE, when vault has enough DAI and all fees are 0', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- mFONE,
- mTokenToUsdDataFeed,
- mFoneToUsdDataFeed,
- dataFeed,
- mockedAggregator,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mFONE, owner, 100_000);
- await mintToken(stableCoins.dai, redemptionVaultWithMToken, 1_000_000);
- await mintToken(mTBILL, redemptionVaultWithMToken, 100_000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 100_000);
-
- await setInstantFeeTest({ vault: redemptionVaultWithMToken, owner }, 0);
-
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- });
-
- it('redeem 100 mFONE, when vault has enough DAI (with fees)', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- mFONE,
- mTokenToUsdDataFeed,
- mFoneToUsdDataFeed,
- dataFeed,
- mockedAggregator,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mFONE, owner, 100_000);
- await mintToken(stableCoins.dai, redemptionVaultWithMToken, 1_000_000);
- await mintToken(mTBILL, redemptionVaultWithMToken, 100_000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 100_000);
-
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- });
-
- it('redeem 100 mFONE, vault has no DAI => triggers mTBILL redemption', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- mFONE,
- mTokenToUsdDataFeed,
- mFoneToUsdDataFeed,
- dataFeed,
- redemptionVault,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await mintToken(mFONE, owner, 100_000);
- await mintToken(mTBILL, redemptionVaultWithMToken, 100_000);
- await mintToken(stableCoins.dai, redemptionVault, 1_000_000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 100_000);
-
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
- mTokenToUsdDataFeed,
- useMTokenSleeve: true,
- },
- stableCoins.dai,
- 100,
- );
- });
-
- it('redeem 100 mFONE, vault has partial DAI => triggers partial mTBILL redemption', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- mFONE,
- mTokenToUsdDataFeed,
- mFoneToUsdDataFeed,
- dataFeed,
- redemptionVault,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await mintToken(mFONE, owner, 100_000);
- await mintToken(mTBILL, redemptionVaultWithMToken, 100_000);
- await mintToken(stableCoins.dai, redemptionVaultWithMToken, 10);
- await mintToken(stableCoins.dai, redemptionVault, 1_000_000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 100_000);
-
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
- mTokenToUsdDataFeed,
- useMTokenSleeve: true,
- },
- stableCoins.dai,
- 100,
- );
- });
-
- it('redeem 100 mFONE with divergent rates (mFONE=$5, mTBILL=$2) => triggers mTBILL redemption', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- mFONE,
- mTokenToUsdDataFeed,
- mFoneToUsdDataFeed,
- dataFeed,
- redemptionVault,
- mockedAggregatorMFone,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator: mockedAggregatorMFone }, 5);
-
- await mintToken(mFONE, owner, 100_000);
- await mintToken(mTBILL, redemptionVaultWithMToken, 100_000);
- await mintToken(stableCoins.dai, redemptionVault, 1_000_000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 100_000);
-
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
- mTokenToUsdDataFeed,
- useMTokenSleeve: true,
- },
- stableCoins.dai,
- 100,
- );
- });
-
- it('redeem with waived fee', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- mFONE,
- mTokenToUsdDataFeed,
- mFoneToUsdDataFeed,
- dataFeed,
- mockedAggregator,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithMToken, owner },
- owner.address,
- );
-
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mFONE, owner, 100_000);
- await mintToken(stableCoins.dai, redemptionVaultWithMToken, 1_000_000);
- await mintToken(mTBILL, redemptionVaultWithMToken, 100_000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 100_000);
-
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
- mTokenToUsdDataFeed,
- waivedFee: true,
- },
- stableCoins.dai,
- 100,
- );
- });
- it('redeem 100 mFONE (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customRecipient,
- mFoneToUsdDataFeed,
- mFONE,
- mTBILL,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVaultWithMToken, 100000);
- await mintToken(mTBILL, redemptionVaultWithMToken, 100000);
- await mintToken(mFONE, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mFONE,
- redemptionVaultWithMToken,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- mFoneToUsdDataFeed,
- mFONE,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem 100 mFONE when other fn overload is paused (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customRecipient,
- mFoneToUsdDataFeed,
- mFONE,
- } = await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- redemptionVaultWithMToken,
- encodeFnSelector('redeemInstant(address,uint256,uint256)'),
- );
- await mintToken(stableCoins.dai, redemptionVaultWithMToken, 100000);
- await mintToken(mTBILL, redemptionVaultWithMToken, 100000);
- await mintToken(mFONE, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mFONE,
- redemptionVaultWithMToken,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- mFoneToUsdDataFeed,
- mFONE,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem 100 mFONE when other fn overload is paused', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- mFoneToUsdDataFeed,
- mFONE,
- } = await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- redemptionVaultWithMToken,
- encodeFnSelector('redeemInstant(address,uint256,uint256,address)'),
- );
- await mintToken(stableCoins.dai, redemptionVaultWithMToken, 100000);
- await mintToken(mTBILL, redemptionVaultWithMToken, 100000);
- await mintToken(mFONE, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mFONE,
- redemptionVaultWithMToken,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- mFoneToUsdDataFeed,
- mFONE,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('should fail: when function with custom recipient is paused', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- customRecipient,
- mFoneToUsdDataFeed,
- mFONE,
- } = await loadFixture(defaultDeploy);
- await mintToken(mFONE, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- redemptionVaultWithMToken,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector(
- 'redeemInstant(address,uint256,uint256,address)',
- );
- await pauseVaultFn(redemptionVaultWithMToken, selector);
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- mFoneToUsdDataFeed,
- mFONE,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: greenlist enabled and recipient not in greenlist (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- customRecipient,
- mFoneToUsdDataFeed,
- mFONE,
- } = await loadFixture(defaultDeploy);
-
- await redemptionVaultWithMToken.setGreenlistEnable(true);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- mFoneToUsdDataFeed,
- mFONE,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: recipient in blacklist (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- customRecipient,
- mFoneToUsdDataFeed,
- mFONE,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- customRecipient,
- );
-
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- mFoneToUsdDataFeed,
- mFONE,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: recipient in sanctions list (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- customRecipient,
- mFoneToUsdDataFeed,
- mFONE,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- customRecipient,
- );
-
- await redeemInstantWithMTokenTest(
- {
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- mFoneToUsdDataFeed,
- mFONE,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
- });
-
- describe('redeemRequest()', () => {
- it('should fail: when there is no token in vault', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mFONE,
- mFoneToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithMToken,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'MV: token not exists',
- },
- );
- });
-
- it('should fail: when trying to redeem 0 amount', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mFONE,
- mFoneToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithMToken,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- stableCoins.dai,
- 0,
- {
- revertMessage: 'RV: invalid amount',
- },
- );
- });
-
- it('should fail: call is paused', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mFONE,
- mFoneToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await pauseVaultFn(
- redemptionVaultWithMToken,
- encodeFnSelector('redeemRequest(address,uint256)'),
- );
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithMToken,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: when user has no mFONE balance', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mFONE,
- mFoneToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 100_000);
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithMToken,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'ERC20: transfer amount exceeds balance',
- },
- );
- });
-
- it('redeem request 100 mFONE', async () => {
- const {
- owner,
- redemptionVaultWithMToken,
- stableCoins,
- mFONE,
- mFoneToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await mintToken(mFONE, owner, 100_000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 100_000);
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithMToken,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- });
- });
-
- describe('redeemFiatRequest()', () => {
- it('should fail: call is paused', async () => {
- const { owner, redemptionVaultWithMToken, mFONE, mFoneToUsdDataFeed } =
- await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- redemptionVaultWithMToken,
- encodeFnSelector('redeemFiatRequest(uint256)'),
- );
-
- await redeemFiatRequestTest(
- {
- redemptionVault: redemptionVaultWithMToken,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- 100,
- {
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('redeem fiat request 100 mFONE', async () => {
- const { owner, redemptionVaultWithMToken, mFONE, mFoneToUsdDataFeed } =
- await loadFixture(defaultDeploy);
-
- await mintToken(mFONE, owner, 100_000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 100_000);
-
- await redeemFiatRequestTest(
- {
- redemptionVault: redemptionVaultWithMToken,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- 100,
- );
- });
- });
-
- describe('approveRequest()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- redemptionVaultWithMToken: redemptionVault,
- regularAccounts,
- mFoneToUsdDataFeed,
- mFONE,
- } = await loadFixture(defaultDeploy);
- await approveRedeemRequestTest(
- {
- redemptionVault,
- owner: regularAccounts[1],
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- 1,
- parseUnits('1'),
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- redemptionVaultWithMToken: redemptionVault,
- stableCoins,
- mFONE,
- mFoneToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await approveRedeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- 1,
- parseUnits('1'),
- {
- revertMessage: 'RV: request not exist',
- },
- );
- });
-
- it('should fail: request already processed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMFone,
- redemptionVaultWithMToken: redemptionVault,
- stableCoins,
- mFONE,
- mFoneToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
-
- await mintToken(mFONE, owner, 100);
- await approveBase18(owner, mFONE, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMFone }, 5);
-
- await redeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await approveRedeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- +requestId,
- parseUnits('1'),
- );
- await approveRedeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- +requestId,
- parseUnits('1'),
- { revertMessage: 'RV: request not pending' },
- );
- });
-
- it('approve request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMFone,
- redemptionVaultWithMToken: redemptionVault,
- stableCoins,
- mFONE,
- mFoneToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
-
- await mintToken(mFONE, owner, 100);
- await approveBase18(owner, mFONE, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMFone }, 5);
-
- await redeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await approveRedeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- +requestId,
- parseUnits('1'),
- );
- });
- });
-
- describe('approveRequest() with fiat', async () => {
- it('approve request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMFone,
- redemptionVaultWithMToken: redemptionVault,
- mFONE,
- mFoneToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mFONE, owner, 100);
- await approveBase18(owner, mFONE, redemptionVault, 100);
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMFone }, 5);
-
- await redeemFiatRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- 100,
- );
- const requestId = 0;
- await changeTokenAllowanceTest(
- { vault: redemptionVault, owner },
- constants.AddressZero,
- parseUnits('100'),
- );
-
- await approveRedeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- +requestId,
- parseUnits('1'),
- );
- });
- });
-
- describe('safeApproveRequest()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- redemptionVaultWithMToken: redemptionVault,
- regularAccounts,
- mFoneToUsdDataFeed,
- mFONE,
- } = await loadFixture(defaultDeploy);
- await safeApproveRedeemRequestTest(
- {
- redemptionVault,
- owner: regularAccounts[1],
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- 1,
- parseUnits('1'),
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- redemptionVaultWithMToken: redemptionVault,
- stableCoins,
- mFONE,
- mFoneToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await safeApproveRedeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- 1,
- parseUnits('1'),
- {
- revertMessage: 'RV: request not exist',
- },
- );
- });
-
- it('should fail: if new rate greater then variabilityTolerance', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMFone,
- redemptionVaultWithMToken: redemptionVault,
- stableCoins,
- mFONE,
- mFoneToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mFONE, owner, 100);
- await approveBase18(owner, mFONE, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMFone }, 5);
-
- await redeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeApproveRedeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- +requestId,
- parseUnits('6'),
- { revertMessage: 'MV: exceed price diviation' },
- );
- });
-
- it('should fail: request already processed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMFone,
- redemptionVaultWithMToken: redemptionVault,
- stableCoins,
- mFONE,
- mFoneToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mFONE, owner, 100);
- await approveBase18(owner, mFONE, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMFone }, 5);
-
- await redeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeApproveRedeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- +requestId,
- parseUnits('5.000001'),
- );
- await safeApproveRedeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- +requestId,
- parseUnits('5.00001'),
- { revertMessage: 'RV: request not pending' },
- );
- });
-
- it('safe approve request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMFone,
- redemptionVaultWithMToken: redemptionVault,
- stableCoins,
- mFONE,
- mFoneToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mFONE, owner, 100);
- await approveBase18(owner, mFONE, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMFone }, 5);
-
- await redeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeApproveRedeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- +requestId,
- parseUnits('5.000001'),
- );
- });
- });
-
- describe('rejectRequest()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- redemptionVaultWithMToken: redemptionVault,
- regularAccounts,
- mFoneToUsdDataFeed,
- mFONE,
- } = await loadFixture(defaultDeploy);
- await rejectRedeemRequestTest(
- {
- redemptionVault,
- owner: regularAccounts[1],
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- redemptionVaultWithMToken: redemptionVault,
- stableCoins,
- mFONE,
- mFoneToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await rejectRedeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- 1,
- {
- revertMessage: 'RV: request not exist',
- },
- );
- });
-
- it('should fail: request already processed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMFone,
- redemptionVaultWithMToken: redemptionVault,
- stableCoins,
- mFONE,
- mFoneToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mFONE, owner, 100);
- await approveBase18(owner, mFONE, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMFone }, 5);
-
- await redeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await rejectRedeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- +requestId,
- );
- await rejectRedeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- +requestId,
- { revertMessage: 'RV: request not pending' },
- );
- });
-
- it('reject request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMFone,
- redemptionVaultWithMToken: redemptionVault,
- stableCoins,
- mFONE,
- mFoneToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mFONE, owner, 100);
- await approveBase18(owner, mFONE, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMFone }, 5);
-
- await redeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await rejectRedeemRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL: mFONE,
- mTokenToUsdDataFeed: mFoneToUsdDataFeed,
- },
- +requestId,
- );
- });
- });
-
- describe('ceiling division math correctness', () => {
- const TOKEN_DECIMALS: Record = {
- usdc6: 6,
- usdc: 8,
- dai: 9,
- usdt: 18,
- };
-
- /**
- * Mirrors the Solidity math in RedemptionVaultWithMToken._redeemInstant:
- * amountTokenOutWithoutFee = truncate(
- * (amountMTokenWithoutFee * mTokenRate / tokenOutRate), tokenDecimals
- * )
- * Returns the expected user output in NATIVE decimals.
- */
- function computeExpectedTokenOut(
- amountMTokenIn: BigNumber,
- mTokenRate: BigNumber,
- tokenOutRate: BigNumber,
- tokenDecimals: number,
- feePercent: number,
- isWaived: boolean,
- ): BigNumber {
- const fee = isWaived
- ? BigNumber.from(0)
- : amountMTokenIn.mul(feePercent).div(10000);
- const amountWithoutFee = amountMTokenIn.sub(fee);
- const base18Out = amountWithoutFee.mul(mTokenRate).div(tokenOutRate);
- const scale = BigNumber.from(10).pow(18 - tokenDecimals);
- const truncated = base18Out.div(scale);
- return truncated;
- }
-
- /**
- * Lean helper: configures both outer + inner vault for a given tokenOut,
- * sets rates, mints tokens, and returns everything needed to call redeemInstant.
- * The outer vault has ZERO tokenOut to force the inner-vault redemption path.
- */
- async function setupCeilDivTest(opts: {
- fixture: Awaited>;
- tokenKey: 'usdc6' | 'usdc' | 'dai' | 'usdt';
- mTbillRate: number;
- isStable: boolean;
- tokenOutRate?: number;
- redeemAmount?: number;
- }) {
- const {
- redemptionVaultWithMToken,
- redemptionVault,
- owner,
- mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
- mTokenToUsdDataFeed,
- mockedAggregatorMToken,
- mockedAggregatorMFone,
- mockedAggregator,
- dataFeed,
- stableCoins,
- } = opts.fixture;
-
- const token = stableCoins[opts.tokenKey];
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- token,
- dataFeed.address,
- 0,
- opts.isStable,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- token,
- dataFeed.address,
- 0,
- opts.isStable,
- );
-
- await setRoundData(
- { mockedAggregator: mockedAggregatorMToken },
- opts.mTbillRate,
- );
- if (opts.tokenOutRate !== undefined) {
- await setRoundData({ mockedAggregator }, opts.tokenOutRate);
- }
-
- await setInstantFeeTest({ vault: redemptionVaultWithMToken, owner }, 0);
-
- const amount = opts.redeemAmount ?? 100;
-
- await mintToken(mFONE, owner, amount * 100);
- await mintToken(mTBILL, redemptionVaultWithMToken, amount * 100);
- await mintToken(token, redemptionVault, amount * 10000);
- await approveBase18(
- owner,
- mFONE,
- redemptionVaultWithMToken,
- amount * 100,
- );
-
- const mFoneRate = await mFoneToUsdDataFeed.getDataInBase18();
- const tokenOutRate = opts.isStable
- ? parseUnits('1')
- : await dataFeed.getDataInBase18();
-
- return {
- token,
- amount,
- redemptionVaultWithMToken,
- redemptionVault,
- owner,
- mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
- mTokenToUsdDataFeed,
- mockedAggregatorMFone,
- mockedAggregator,
- mFoneRate,
- tokenOutRate,
- };
- }
-
- describe('with base RedemptionVault as inner vault', () => {
- const tokenVariants: Array<{
- key: 'usdc6' | 'usdc' | 'dai' | 'usdt';
- label: string;
- }> = [
- { key: 'usdc6', label: '6-dec' },
- { key: 'usdc', label: '8-dec' },
- { key: 'dai', label: '9-dec' },
- { key: 'usdt', label: '18-dec' },
- ];
-
- for (const { key, label } of tokenVariants) {
- describe(`tokenOut ${label} (${key})`, () => {
- it('succeeds with exact division (mTBILL=5, stable)', async () => {
- const fixture = await loadFixture(defaultDeploy);
- const {
- token,
- amount,
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFoneRate,
- tokenOutRate,
- } = await setupCeilDivTest({
- fixture,
- tokenKey: key,
- mTbillRate: 5,
- isStable: true,
- });
-
- const mTbillBefore = await mTBILL.balanceOf(
+ const sleeveBalanceBefore = await mTokenLoan.balanceOf(
redemptionVaultWithMToken.address,
);
- const userTokenBefore = await token.balanceOf(owner.address);
-
- await redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
- parseUnits(amount.toString()),
- 0,
- );
-
- expect(
- await mTBILL.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mTbillBefore);
-
- const expected = computeExpectedTokenOut(
- parseUnits(amount.toString()),
- mFoneRate,
- tokenOutRate,
- TOKEN_DECIMALS[key],
- 0,
- true,
- );
- const userTokenAfter = await token.balanceOf(owner.address);
- expect(userTokenAfter.sub(userTokenBefore)).to.equal(expected);
- });
-
- it('succeeds with remainder-producing rate (mTBILL=1.05, stable)', async () => {
- const fixture = await loadFixture(defaultDeploy);
- const {
- token,
- amount,
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFoneRate,
- tokenOutRate,
- } = await setupCeilDivTest({
- fixture,
- tokenKey: key,
- mTbillRate: 1.05,
- isStable: true,
- });
- const mTbillBefore = await mTBILL.balanceOf(
- redemptionVaultWithMToken.address,
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithMToken,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ additionalLiquidity: async () => {
+ return (
+ await mTokenLoan.balanceOf(
+ redemptionVaultWithMToken.address,
+ )
+ ).div(10 ** (await stableCoins.dai.decimals()));
+ },
+ },
+ stableCoins.dai,
+ 100,
);
- const userTokenBefore = await token.balanceOf(owner.address);
- await redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
- parseUnits(amount.toString()),
- 0,
- );
-
- expect(
- await mTBILL.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mTbillBefore);
-
- const expected = computeExpectedTokenOut(
- parseUnits(amount.toString()),
- mFoneRate,
- tokenOutRate,
- TOKEN_DECIMALS[key],
- 0,
- true,
+ const loanLpBalanceAfter = await mTokenLoan.balanceOf(
+ loanLp.address,
);
- const userTokenAfter = await token.balanceOf(owner.address);
- expect(userTokenAfter.sub(userTokenBefore)).to.equal(expected);
- });
-
- it('succeeds with high mTBILL rate (mTBILL=100, stable)', async () => {
- const fixture = await loadFixture(defaultDeploy);
- const {
- token,
- amount,
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFoneRate,
- tokenOutRate,
- } = await setupCeilDivTest({
- fixture,
- tokenKey: key,
- mTbillRate: 100,
- isStable: true,
- });
-
- const mTbillBefore = await mTBILL.balanceOf(
+ const sleeveBalanceAfter = await mTokenLoan.balanceOf(
redemptionVaultWithMToken.address,
);
- const userTokenBefore = await token.balanceOf(owner.address);
-
- await redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
- parseUnits(amount.toString()),
- 0,
- );
-
- expect(
- await mTBILL.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mTbillBefore);
-
- const expected = computeExpectedTokenOut(
- parseUnits(amount.toString()),
- mFoneRate,
- tokenOutRate,
- TOKEN_DECIMALS[key],
- 0,
- true,
- );
- const userTokenAfter = await token.balanceOf(owner.address);
- expect(userTokenAfter.sub(userTokenBefore)).to.equal(expected);
+ expect(sleeveBalanceAfter).to.be.lte(sleeveBalanceBefore);
+ expect(loanLpBalanceAfter).to.be.lt(loanLpBalanceBefore);
});
- it('succeeds with low mTBILL rate (mTBILL=0.5, stable)', async () => {
- const fixture = await loadFixture(defaultDeploy);
+ it('redeem 100 mTBILL, vault has no DAI but has LP liquidity', async () => {
const {
- token,
- amount,
- redemptionVaultWithMToken,
owner,
- mTBILL,
- mFoneRate,
- tokenOutRate,
- } = await setupCeilDivTest({
- fixture,
- tokenKey: key,
- mTbillRate: 0.5,
- isStable: true,
- });
-
- const mTbillBefore = await mTBILL.balanceOf(
- redemptionVaultWithMToken.address,
- );
- const userTokenBefore = await token.balanceOf(owner.address);
-
- await redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
- parseUnits(amount.toString()),
- 0,
- );
-
- expect(
- await mTBILL.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mTbillBefore);
-
- const expected = computeExpectedTokenOut(
- parseUnits(amount.toString()),
- mFoneRate,
- tokenOutRate,
- TOKEN_DECIMALS[key],
- 0,
- true,
- );
- const userTokenAfter = await token.balanceOf(owner.address);
- expect(userTokenAfter.sub(userTokenBefore)).to.equal(expected);
- });
-
- it('succeeds with near-boundary rate (mTBILL=1.00000003, stable)', async () => {
- const fixture = await loadFixture(defaultDeploy);
- const {
- token,
redemptionVaultWithMToken,
- owner,
+ stableCoins,
+ mTokenLoan,
mTBILL,
- mFoneRate,
- tokenOutRate,
- } = await setupCeilDivTest({
- fixture,
- tokenKey: key,
- mTbillRate: 1.00000003,
- isStable: true,
- redeemAmount: 10000,
- });
-
- const mTbillBefore = await mTBILL.balanceOf(
- redemptionVaultWithMToken.address,
- );
- const userTokenBefore = await token.balanceOf(owner.address);
-
- await redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
- parseUnits('10000'),
- 0,
- );
-
- expect(
- await mTBILL.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mTbillBefore);
+ mTokenToUsdDataFeed,
+ dataFeed,
+ mockedAggregator,
+ redemptionVaultLoanSwapper,
+ loanLp,
+ } = await loadFixture(defaultDeploy);
- const expected = computeExpectedTokenOut(
- parseUnits('10000'),
- mFoneRate,
- tokenOutRate,
- TOKEN_DECIMALS[key],
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMToken, owner },
+ stableCoins.dai,
+ dataFeed.address,
0,
true,
);
- const userTokenAfter = await token.balanceOf(owner.address);
- expect(userTokenAfter.sub(userTokenBefore)).to.equal(expected);
- });
-
- it('succeeds with non-stable tokenOut (mTBILL=1.05, tokenOut=1.03)', async () => {
- const fixture = await loadFixture(defaultDeploy);
- const {
- token,
- amount,
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFoneRate,
- tokenOutRate,
- } = await setupCeilDivTest({
- fixture,
- tokenKey: key,
- mTbillRate: 1.05,
- isStable: false,
- tokenOutRate: 1.03,
- });
-
- const mTbillBefore = await mTBILL.balanceOf(
- redemptionVaultWithMToken.address,
- );
- const userTokenBefore = await token.balanceOf(owner.address);
-
- await redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
- parseUnits(amount.toString()),
- 0,
- );
-
- expect(
- await mTBILL.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mTbillBefore);
-
- const expected = computeExpectedTokenOut(
- parseUnits(amount.toString()),
- mFoneRate,
- tokenOutRate,
- TOKEN_DECIMALS[key],
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.dai,
+ dataFeed.address,
0,
true,
);
- const userTokenAfter = await token.balanceOf(owner.address);
- expect(userTokenAfter.sub(userTokenBefore)).to.equal(expected);
- });
- it('succeeds with small redeem (1 mFONE, mTBILL=1.05, stable)', async () => {
- const fixture = await loadFixture(defaultDeploy);
- const {
- token,
- redemptionVaultWithMToken,
+ await setRoundData({ mockedAggregator }, 1);
+ await setLoanLpTest(
+ { redemptionVault: redemptionVaultWithMToken, owner },
+ loanLp.address,
+ );
+ await setLoanSwapperVaultTest(
+ { redemptionVault: redemptionVaultWithMToken, owner },
+ redemptionVaultLoanSwapper.address,
+ );
+ await setPreferLoanLiquidityTest(
+ { redemptionVault: redemptionVaultWithMToken, owner },
+ true,
+ );
+
+ await mintToken(mTBILL, owner, 100_000);
+ await mintToken(mTokenLoan, loanLp, 100_000);
+ await mintToken(
+ stableCoins.dai,
+ redemptionVaultLoanSwapper,
+ 1_000_000,
+ );
+ await approveBase18(
owner,
mTBILL,
- mFoneRate,
- tokenOutRate,
- } = await setupCeilDivTest({
- fixture,
- tokenKey: key,
- mTbillRate: 1.05,
- isStable: true,
- redeemAmount: 1,
- });
-
- const mTbillBefore = await mTBILL.balanceOf(
- redemptionVaultWithMToken.address,
+ redemptionVaultWithMToken,
+ 100_000,
+ );
+ await approveBase18(
+ loanLp,
+ mTokenLoan,
+ redemptionVaultWithMToken,
+ 100_000,
);
- const userTokenBefore = await token.balanceOf(owner.address);
- await setMinAmountTest(
+ await setInstantFeeTest(
{ vault: redemptionVaultWithMToken, owner },
0,
);
- await redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
- parseUnits('1'),
- 0,
- );
+ const loanLpBalanceBefore = await mTokenLoan.balanceOf(
+ loanLp.address,
+ );
- expect(
- await mTBILL.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mTbillBefore);
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithMToken,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ stableCoins.dai,
+ 100,
+ );
- const expected = computeExpectedTokenOut(
- parseUnits('1'),
- mFoneRate,
- tokenOutRate,
- TOKEN_DECIMALS[key],
- 0,
- true,
+ const loanLpBalanceAfter = await mTokenLoan.balanceOf(
+ loanLp.address,
);
- const userTokenAfter = await token.balanceOf(owner.address);
- expect(userTokenAfter.sub(userTokenBefore)).to.equal(expected);
+ expect(loanLpBalanceAfter).to.be.lt(loanLpBalanceBefore);
});
- it('succeeds with large redeem near daily limit (mTBILL=1.05, stable)', async () => {
- const fixture = await loadFixture(defaultDeploy);
+ it('when vault has enough DAI and enough LP liquidity, LP should be used first', async () => {
const {
- token,
+ owner,
+ redemptionVaultWithMToken,
+ stableCoins,
+ mTokenLoan,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ mockedAggregator,
+ redemptionVaultLoanSwapper,
+ loanLp,
+ } = await loadFixture(defaultDeploy);
+
+ await mintToken(
+ stableCoins.dai,
+ redemptionVaultLoanSwapper,
+ 1_000_000,
+ );
+ await mintToken(
+ stableCoins.dai,
+ redemptionVaultWithMToken,
+ 1_000_000,
+ );
+ await mintToken(mTokenLoan, loanLp, 100_000);
+ await approveBase18(
+ loanLp,
+ mTokenLoan,
redemptionVaultWithMToken,
+ 100_000,
+ );
+
+ await mintToken(mTBILL, owner, 100_000);
+ await approveBase18(
owner,
mTBILL,
- mFoneRate,
- tokenOutRate,
- } = await setupCeilDivTest({
- fixture,
- tokenKey: key,
- mTbillRate: 1.05,
- isStable: true,
- redeemAmount: 50000,
- });
+ redemptionVaultWithMToken,
+ 100_000,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMToken, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setInstantFeeTest(
+ { vault: redemptionVaultWithMToken, owner },
+ 0,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setLoanLpTest(
+ { redemptionVault: redemptionVaultWithMToken, owner },
+ loanLp.address,
+ );
+ await setLoanSwapperVaultTest(
+ { redemptionVault: redemptionVaultWithMToken, owner },
+ redemptionVaultLoanSwapper.address,
+ );
+ await setPreferLoanLiquidityTest(
+ { redemptionVault: redemptionVaultWithMToken, owner },
+ true,
+ );
- const mTbillBefore = await mTBILL.balanceOf(
+ const loanLpBalanceBefore = await mTokenLoan.balanceOf(
+ loanLp.address,
+ );
+ const vaultDaiBalanceBefore = await stableCoins.dai.balanceOf(
redemptionVaultWithMToken.address,
);
- const userTokenBefore = await token.balanceOf(owner.address);
-
- await redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
- parseUnits('50000'),
- 0,
- );
- expect(
- await mTBILL.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mTbillBefore);
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithMToken,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ stableCoins.dai,
+ 100,
+ );
- const expected = computeExpectedTokenOut(
- parseUnits('50000'),
- mFoneRate,
- tokenOutRate,
- TOKEN_DECIMALS[key],
- 0,
- true,
+ const loanLpBalanceAfter = await mTokenLoan.balanceOf(
+ loanLp.address,
);
- const userTokenAfter = await token.balanceOf(owner.address);
- expect(userTokenAfter.sub(userTokenBefore)).to.equal(expected);
+ const vaultDaiBalanceAfter = await stableCoins.dai.balanceOf(
+ redemptionVaultWithMToken.address,
+ );
+ expect(loanLpBalanceAfter).to.be.lt(loanLpBalanceBefore);
+ expect(vaultDaiBalanceAfter).to.be.eq(vaultDaiBalanceBefore);
});
- });
- }
- });
- describe('with RedemptionVaultWithAave as inner vault', () => {
- for (const { key, label } of [
- { key: 'usdc' as const, label: '8-dec' },
- { key: 'usdc6' as const, label: '6-dec' },
- ]) {
- describe(`tokenOut ${label} (${key})`, () => {
- async function setupAaveInnerVault(
- fixture: Awaited>,
- tokenKey: 'usdc' | 'usdc6',
- ) {
+ it('when vault partially has USDC, partially has mToken sleeve and partially has LP liquidity, so all the liquidity should be used', async () => {
const {
- redemptionVaultWithMToken,
- redemptionVaultWithAave,
- aavePoolMock,
- aUSDC,
owner,
+ redemptionVaultWithMToken,
+ stableCoins,
+ mTokenLoan,
mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
mTokenToUsdDataFeed,
- mockedAggregatorMToken,
- mockedAggregator,
dataFeed,
- stableCoins,
- } = fixture;
-
- const token = stableCoins[tokenKey];
+ mockedAggregator,
+ redemptionVaultLoanSwapper,
+ loanLp,
+ redemptionVault,
+ mockedAggregatorMToken,
+ } = await loadFixture(defaultDeploy);
- await redemptionVaultWithMToken.setRedemptionVault(
- redemptionVaultWithAave.address,
+ await mintToken(stableCoins.dai, redemptionVaultLoanSwapper, 400);
+ await mintToken(stableCoins.dai, redemptionVaultWithMToken, 300);
+ await mintToken(stableCoins.dai, redemptionVault, 300);
+ await mintToken(mTokenLoan, loanLp, 400);
+ await mintToken(mTBILL, redemptionVaultWithMToken, 300);
+ await approveBase18(
+ loanLp,
+ mTokenLoan,
+ redemptionVaultWithMToken,
+ 400,
);
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithMToken, 1000);
await addPaymentTokenTest(
{ vault: redemptionVaultWithMToken, owner },
- token,
+ stableCoins.dai,
dataFeed.address,
0,
true,
);
await addPaymentTokenTest(
- { vault: redemptionVaultWithAave, owner },
- token,
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.dai,
dataFeed.address,
0,
true,
);
- await redemptionVaultWithAave.addWaivedFeeAccount(
- redemptionVaultWithMToken.address,
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
);
-
- if (tokenKey === 'usdc6') {
- const { ERC20Mock__factory } = await import(
- '../../typechain-types'
- );
- const aUsdc6 = await new ERC20Mock__factory(owner).deploy(6);
- await aavePoolMock.setReserveAToken(
- token.address,
- aUsdc6.address,
- );
- await token.mint(aavePoolMock.address, parseUnits('1000000', 6));
- await aUsdc6.mint(
- redemptionVaultWithAave.address,
- parseUnits('1000000', 6),
- );
- await redemptionVaultWithAave.setAavePool(
- token.address,
- aavePoolMock.address,
- );
- } else {
- await token.mint(aavePoolMock.address, parseUnits('1000000'));
- await aUSDC.mint(
- redemptionVaultWithAave.address,
- parseUnits('1000000'),
- );
- }
-
await setInstantFeeTest(
{ vault: redemptionVaultWithMToken, owner },
0,
);
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- return {
- token,
+ await setPreferLoanLiquidityTest(
+ { redemptionVault: redemptionVaultWithMToken, owner },
+ true,
+ );
+
+ await redemptionVaultWithMToken.setRedemptionVault(
+ redemptionVault.address,
+ );
+
+ await redemptionVault.setWaivedFeeAccount(
+ redemptionVaultWithMToken.address,
+ true,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithMToken,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ checkSupply: false,
+ additionalLiquidity: async () => {
+ return (
+ await mTBILL.balanceOf(redemptionVaultWithMToken.address)
+ ).div(10 ** (await stableCoins.dai.decimals()));
+ },
+ },
+ stableCoins.dai,
+ 1000,
+ );
+
+ expect(await mTokenLoan.balanceOf(loanLp.address)).to.eq(0);
+ expect(
+ await stableCoins.dai.balanceOf(
+ redemptionVaultWithMToken.address,
+ ),
+ ).to.be.eq(0);
+ expect(
+ await stableCoins.dai.balanceOf(redemptionVault.address),
+ ).to.be.eq(0);
+ expect(
+ await mTBILL.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.eq(0);
+ });
+ });
+
+ it('should fail: when inner vault fee is not waived', async () => {
+ const {
+ owner,
+ redemptionVaultWithMToken,
+ stableCoins,
+ mTokenLoan,
+ mTBILL,
+ mTokenLoanToUsdDataFeed,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ redemptionVaultLoanSwapper,
+ } = await loadFixture(defaultDeploy);
+
+ await setLoanLpTest(
+ { redemptionVault: redemptionVaultWithMToken, owner },
+ constants.AddressZero,
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMToken, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ // Remove the waived fee — inner vault will charge fee on this contract
+ await redemptionVaultLoanSwapper.setWaivedFeeAccount(
+ redemptionVaultWithMToken.address,
+ false,
+ );
+
+ await mintToken(mTBILL, owner, 100_000);
+ await mintToken(mTokenLoan, redemptionVaultWithMToken, 100_000);
+ await mintToken(
+ stableCoins.dai,
+ redemptionVaultLoanSwapper,
+ 1_000_000,
+ );
+ await approveBase18(
+ owner,
+ mTBILL,
+ redemptionVaultWithMToken,
+ 100_000,
+ );
+
+ // No DAI on vault — forces mTokenLoan redemption path where inner vault fee causes revert
+ await redeemInstantWithMTokenTest(
+ {
redemptionVaultWithMToken,
owner,
+ mTokenLoan,
mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
mTokenToUsdDataFeed,
- mockedAggregatorMToken,
- };
- }
+ mTokenLoanToUsdDataFeed,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ revertMessage: 'ERC20: transfer amount exceeds balance',
+ },
+ );
+ });
- it('succeeds with remainder-producing rate (mTBILL=1.05, stable)', async () => {
- const fixture = await loadFixture(defaultDeploy);
- const {
- token,
+ it('should fail: vault has no mTokenLoan and no DAI', async () => {
+ const {
+ owner,
+ redemptionVaultWithMToken,
+ stableCoins,
+ mTokenLoan,
+ mTBILL,
+ mTokenLoanToUsdDataFeed,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ redemptionVaultLoanSwapper,
+ } = await loadFixture(defaultDeploy);
+
+ await setLoanLpTest(
+ { redemptionVault: redemptionVaultWithMToken, owner },
+ constants.AddressZero,
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMToken, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await mintToken(mTBILL, owner, 100_000);
+ await approveBase18(
+ owner,
+ mTBILL,
+ redemptionVaultWithMToken,
+ 100_000,
+ );
+
+ await redeemInstantWithMTokenTest(
+ {
redemptionVaultWithMToken,
owner,
+ mTokenLoan,
mTBILL,
- mFONE,
- mockedAggregatorMToken,
- } = await setupAaveInnerVault(fixture, key);
+ mTokenToUsdDataFeed,
+ mTokenLoanToUsdDataFeed,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ revertMessage: 'ERC20: transfer amount exceeds balance',
+ },
+ );
+ });
- await setRoundData(
- { mockedAggregator: mockedAggregatorMToken },
- 1.05,
- );
- await mintToken(mFONE, owner, 10000);
- await mintToken(mTBILL, redemptionVaultWithMToken, 10000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 10000);
+ it('redeem 100 mTBILL, when vault has enough DAI and all fees are 0', async () => {
+ const {
+ owner,
+ redemptionVaultWithMToken,
+ stableCoins,
+ mTokenLoan,
+ mTBILL,
+ mTokenLoanToUsdDataFeed,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ mockedAggregator,
+ } = await loadFixture(defaultDeploy);
+
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMToken, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
- const mTbillBefore = await mTBILL.balanceOf(
- redemptionVaultWithMToken.address,
- );
+ await setRoundData({ mockedAggregator }, 1);
- await redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
- parseUnits('100'),
- 0,
- );
+ await mintToken(mTBILL, owner, 100_000);
+ await mintToken(
+ stableCoins.dai,
+ redemptionVaultWithMToken,
+ 1_000_000,
+ );
+ await mintToken(mTokenLoan, redemptionVaultWithMToken, 100_000);
+ await approveBase18(
+ owner,
+ mTBILL,
+ redemptionVaultWithMToken,
+ 100_000,
+ );
- expect(
- await mTBILL.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mTbillBefore);
- });
+ await setInstantFeeTest(
+ { vault: redemptionVaultWithMToken, owner },
+ 0,
+ );
- it('succeeds with high mTBILL rate (mTBILL=100, stable)', async () => {
- const fixture = await loadFixture(defaultDeploy);
- const {
- token,
+ await redeemInstantWithMTokenTest(
+ {
redemptionVaultWithMToken,
owner,
+ mTokenLoan,
mTBILL,
- mFONE,
- mockedAggregatorMToken,
- } = await setupAaveInnerVault(fixture, key);
+ mTokenToUsdDataFeed,
+ mTokenLoanToUsdDataFeed,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ });
- await setRoundData(
- { mockedAggregator: mockedAggregatorMToken },
- 100,
- );
- await mintToken(mFONE, owner, 10000);
- await mintToken(mTBILL, redemptionVaultWithMToken, 10000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 10000);
+ it('redeem 100 mTBILL, when vault has enough DAI (with fees)', async () => {
+ const {
+ owner,
+ redemptionVaultWithMToken,
+ stableCoins,
+ mTokenLoan,
+ mTBILL,
+ mTokenLoanToUsdDataFeed,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ mockedAggregator,
+ } = await loadFixture(defaultDeploy);
- const mTbillBefore = await mTBILL.balanceOf(
- redemptionVaultWithMToken.address,
- );
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMToken, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
- await redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
- parseUnits('100'),
- 0,
- );
+ await setRoundData({ mockedAggregator }, 1);
- expect(
- await mTBILL.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mTbillBefore);
- });
+ await mintToken(mTBILL, owner, 100_000);
+ await mintToken(
+ stableCoins.dai,
+ redemptionVaultWithMToken,
+ 1_000_000,
+ );
+ await mintToken(mTokenLoan, redemptionVaultWithMToken, 100_000);
+ await approveBase18(
+ owner,
+ mTBILL,
+ redemptionVaultWithMToken,
+ 100_000,
+ );
- it('succeeds with near-boundary rate (mTBILL=1.00000003, stable)', async () => {
- const fixture = await loadFixture(defaultDeploy);
- const {
- token,
+ await redeemInstantWithMTokenTest(
+ {
redemptionVaultWithMToken,
owner,
+ mTokenLoan,
mTBILL,
- mFONE,
- mockedAggregatorMToken,
- } = await setupAaveInnerVault(fixture, key);
-
- await setRoundData(
- { mockedAggregator: mockedAggregatorMToken },
- 1.00000003,
- );
- await mintToken(mFONE, owner, 100000);
- await mintToken(mTBILL, redemptionVaultWithMToken, 100000);
- await approveBase18(
- owner,
- mFONE,
- redemptionVaultWithMToken,
- 100000,
- );
+ mTokenToUsdDataFeed,
+ mTokenLoanToUsdDataFeed,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ });
- const mTbillBefore = await mTBILL.balanceOf(
- redemptionVaultWithMToken.address,
- );
+ it('redeem 100 mTBILL, vault has no DAI => triggers mTokenLoan redemption', async () => {
+ const {
+ owner,
+ redemptionVaultWithMToken,
+ stableCoins,
+ mTokenLoan,
+ mTBILL,
+ mTokenLoanToUsdDataFeed,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ redemptionVaultLoanSwapper,
+ } = await loadFixture(defaultDeploy);
- await redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
- parseUnits('10000'),
- 0,
- );
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMToken, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
- expect(
- await mTBILL.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mTbillBefore);
- });
- });
- }
- });
+ await mintToken(mTBILL, owner, 100_000);
+ await mintToken(mTokenLoan, redemptionVaultWithMToken, 100_000);
+ await mintToken(
+ stableCoins.dai,
+ redemptionVaultLoanSwapper,
+ 1_000_000,
+ );
+ await approveBase18(
+ owner,
+ mTBILL,
+ redemptionVaultWithMToken,
+ 100_000,
+ );
- describe('with RedemptionVaultWithMorpho as inner vault', () => {
- for (const { key, label } of [
- { key: 'usdc' as const, label: '8-dec' },
- { key: 'usdc6' as const, label: '6-dec' },
- ]) {
- describe(`tokenOut ${label} (${key})`, () => {
- async function setupMorphoInnerVault(
- fixture: Awaited>,
- tokenKey: 'usdc' | 'usdc6',
- ) {
- const {
+ await redeemInstantWithMTokenTest(
+ {
redemptionVaultWithMToken,
- redemptionVaultWithMorpho,
- morphoVaultMock,
owner,
+ mTokenLoan,
mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
mTokenToUsdDataFeed,
- mockedAggregatorMToken,
- mockedAggregator,
- dataFeed,
- stableCoins,
- } = fixture;
-
- const token = stableCoins[tokenKey];
-
- await redemptionVaultWithMToken.setRedemptionVault(
- redemptionVaultWithMorpho.address,
- );
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- token,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- token,
- dataFeed.address,
- 0,
- true,
- );
+ mTokenLoanToUsdDataFeed,
+ useMTokenSleeve: true,
+ additionalLiquidity: async () => {
+ return (
+ await mTokenLoan.balanceOf(redemptionVaultWithMToken.address)
+ ).div(10 ** (await stableCoins.dai.decimals()));
+ },
+ },
+ stableCoins.dai,
+ 100,
+ );
+ });
- await redemptionVaultWithMorpho.addWaivedFeeAccount(
- redemptionVaultWithMToken.address,
- );
+ it('redeem 100 mTBILL, vault has partial DAI => triggers partial mTokenLoan redemption', async () => {
+ const {
+ owner,
+ redemptionVaultWithMToken,
+ stableCoins,
+ mTokenLoan,
+ mTBILL,
+ mTokenLoanToUsdDataFeed,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ redemptionVaultLoanSwapper,
+ } = await loadFixture(defaultDeploy);
- if (tokenKey === 'usdc6') {
- const { MorphoVaultMock__factory } = await import(
- '../../typechain-types'
- );
- const morphoUsdc6 = await new MorphoVaultMock__factory(
- owner,
- ).deploy(token.address);
- await token.mint(morphoUsdc6.address, parseUnits('1000000', 6));
- await morphoUsdc6.mint(
- redemptionVaultWithMorpho.address,
- parseUnits('1000000', 6),
- );
- await redemptionVaultWithMorpho.setMorphoVault(
- token.address,
- morphoUsdc6.address,
- );
- } else {
- await token.mint(morphoVaultMock.address, parseUnits('1000000'));
- await morphoVaultMock.mint(
- redemptionVaultWithMorpho.address,
- parseUnits('1000000'),
- );
- }
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMToken, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
- await setInstantFeeTest(
- { vault: redemptionVaultWithMToken, owner },
- 0,
- );
+ await mintToken(mTBILL, owner, 100_000);
+ await mintToken(mTokenLoan, redemptionVaultWithMToken, 100_000);
+ await mintToken(stableCoins.dai, redemptionVaultWithMToken, 10);
+ await mintToken(
+ stableCoins.dai,
+ redemptionVaultLoanSwapper,
+ 1_000_000,
+ );
+ await approveBase18(
+ owner,
+ mTBILL,
+ redemptionVaultWithMToken,
+ 100_000,
+ );
- return {
- token,
+ await redeemInstantWithMTokenTest(
+ {
redemptionVaultWithMToken,
owner,
+ mTokenLoan,
mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
mTokenToUsdDataFeed,
- mockedAggregatorMToken,
- };
- }
+ mTokenLoanToUsdDataFeed,
+ useMTokenSleeve: true,
+ additionalLiquidity: async () => {
+ return (
+ await mTokenLoan.balanceOf(redemptionVaultWithMToken.address)
+ ).div(10 ** (await stableCoins.dai.decimals()));
+ },
+ },
+ stableCoins.dai,
+ 100,
+ );
+ });
- it('succeeds with remainder-producing rate (mTBILL=1.05, stable)', async () => {
- const fixture = await loadFixture(defaultDeploy);
- const {
- token,
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFONE,
- mockedAggregatorMToken,
- } = await setupMorphoInnerVault(fixture, key);
+ it('redeem 100 mTBILL with divergent rates (mTBILL=$5, mTokenLoan=$2) => triggers mTokenLoan redemption', async () => {
+ const {
+ owner,
+ redemptionVaultWithMToken,
+ stableCoins,
+ mTokenLoan,
+ mTBILL,
+ mTokenLoanToUsdDataFeed,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ redemptionVaultLoanSwapper,
+ mockedAggregatorMTokenLoan,
+ } = await loadFixture(defaultDeploy);
- await setRoundData(
- { mockedAggregator: mockedAggregatorMToken },
- 1.05,
- );
- await mintToken(mFONE, owner, 10000);
- await mintToken(mTBILL, redemptionVaultWithMToken, 10000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 10000);
+ await setWaivedFeeAccountTest(
+ { vault: redemptionVaultWithMToken, owner },
+ redemptionVaultLoanSwapper.address,
+ true,
+ );
- const mTbillBefore = await mTBILL.balanceOf(
- redemptionVaultWithMToken.address,
- );
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMToken, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
- await redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
- parseUnits('100'),
- 0,
- );
+ await setRoundData(
+ { mockedAggregator: mockedAggregatorMTokenLoan },
+ 2,
+ );
- expect(
- await mTBILL.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mTbillBefore);
- });
+ await mintToken(mTBILL, owner, 100_000);
+ await mintToken(mTokenLoan, redemptionVaultWithMToken, 100_000);
+ await mintToken(
+ stableCoins.dai,
+ redemptionVaultLoanSwapper,
+ 1_000_000,
+ );
+ await approveBase18(
+ owner,
+ mTBILL,
+ redemptionVaultWithMToken,
+ 100_000,
+ );
- it('succeeds with high mTBILL rate (mTBILL=100, stable)', async () => {
- const fixture = await loadFixture(defaultDeploy);
- const {
- token,
+ await redeemInstantWithMTokenTest(
+ {
redemptionVaultWithMToken,
owner,
+ mTokenLoan,
mTBILL,
- mFONE,
- mockedAggregatorMToken,
- } = await setupMorphoInnerVault(fixture, key);
+ mTokenToUsdDataFeed,
+ mTokenLoanToUsdDataFeed,
+ useMTokenSleeve: true,
+ additionalLiquidity: async () => {
+ return (
+ await mTokenLoan.balanceOf(redemptionVaultWithMToken.address)
+ )
+ .div(10 ** (await stableCoins.dai.decimals()))
+ .mul(2);
+ },
+ },
+ stableCoins.dai,
+ 100,
+ );
+ });
- await setRoundData(
- { mockedAggregator: mockedAggregatorMToken },
- 100,
- );
- await mintToken(mFONE, owner, 10000);
- await mintToken(mTBILL, redemptionVaultWithMToken, 10000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 10000);
+ it('redeem with waived fee', async () => {
+ const {
+ owner,
+ redemptionVaultWithMToken,
+ stableCoins,
+ mTokenLoan,
+ mTBILL,
+ mTokenLoanToUsdDataFeed,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ mockedAggregator,
+ } = await loadFixture(defaultDeploy);
- const mTbillBefore = await mTBILL.balanceOf(
- redemptionVaultWithMToken.address,
- );
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMToken, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setWaivedFeeAccountTest(
+ { vault: redemptionVaultWithMToken, owner },
+ owner.address,
+ true,
+ );
- await redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
- parseUnits('100'),
- 0,
- );
+ await setRoundData({ mockedAggregator }, 1);
- expect(
- await mTBILL.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mTbillBefore);
- });
+ await mintToken(mTBILL, owner, 100_000);
+ await mintToken(
+ stableCoins.dai,
+ redemptionVaultWithMToken,
+ 1_000_000,
+ );
+ await mintToken(mTokenLoan, redemptionVaultWithMToken, 100_000);
+ await approveBase18(
+ owner,
+ mTBILL,
+ redemptionVaultWithMToken,
+ 100_000,
+ );
- it('succeeds with near-boundary rate (mTBILL=1.00000003, stable)', async () => {
- const fixture = await loadFixture(defaultDeploy);
- const {
- token,
+ await redeemInstantWithMTokenTest(
+ {
redemptionVaultWithMToken,
owner,
+ mTokenLoan,
mTBILL,
- mFONE,
- mockedAggregatorMToken,
- } = await setupMorphoInnerVault(fixture, key);
-
- await setRoundData(
- { mockedAggregator: mockedAggregatorMToken },
- 1.00000003,
- );
- await mintToken(mFONE, owner, 100000);
- await mintToken(mTBILL, redemptionVaultWithMToken, 100000);
- await approveBase18(
- owner,
- mFONE,
- redemptionVaultWithMToken,
- 100000,
- );
-
- const mTbillBefore = await mTBILL.balanceOf(
- redemptionVaultWithMToken.address,
- );
+ mTokenToUsdDataFeed,
+ mTokenLoanToUsdDataFeed,
+ waivedFee: true,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ });
+ });
- await redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
- parseUnits('10000'),
- 0,
- );
+ describe('ceiling division math correctness', () => {
+ const TOKEN_DECIMALS: Record = {
+ usdc6: 6,
+ usdc: 8,
+ dai: 9,
+ usdt: 18,
+ };
- expect(
- await mTBILL.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mTbillBefore);
- });
- });
- }
- });
+ /**
+ * Mirrors the Solidity math in RedemptionVaultWithMToken._redeemInstant:
+ * amountTokenOutWithoutFee = truncate(
+ * (amountMTokenWithoutFee * mTokenRate / tokenOutRate), tokenDecimals
+ * )
+ * Returns the expected user output in NATIVE decimals.
+ */
+ function computeExpectedTokenOut(
+ amountMTokenIn: BigNumber,
+ mTokenRate: BigNumber,
+ tokenOutRate: BigNumber,
+ tokenDecimals: number,
+ feePercent: number,
+ isWaived: boolean,
+ ): BigNumber {
+ const fee = isWaived
+ ? BigNumber.from(0)
+ : amountMTokenIn.mul(feePercent).div(10000);
+ const amountWithoutFee = amountMTokenIn.sub(fee);
+ const base18Out = amountWithoutFee.mul(mTokenRate).div(tokenOutRate);
+ const scale = BigNumber.from(10).pow(18 - tokenDecimals);
+ const truncated = base18Out.div(scale);
+ return truncated;
+ }
- describe('with RedemptionVaultWithUSTB as inner vault', () => {
- describe('tokenOut 8-dec (usdc — USTB only supports its configured USDC)', () => {
- async function setupUstbInnerVault(
- fixture: Awaited>,
- ) {
+ /**
+ * Lean helper: configures both outer + inner vault for a given tokenOut,
+ * sets rates, mints tokens, and returns everything needed to call redeemInstant.
+ * The outer vault has ZERO tokenOut to force the inner-vault redemption path.
+ */
+ async function setupCeilDivTest(opts: {
+ fixture: Awaited>;
+ tokenKey: 'usdc6' | 'usdc' | 'dai' | 'usdt';
+ mTbillRate: number;
+ isStable: boolean;
+ tokenOutRate?: number;
+ redeemAmount?: number;
+ }) {
const {
redemptionVaultWithMToken,
- redemptionVaultWithUSTB,
- ustbToken,
- ustbRedemption,
+ redemptionVaultLoanSwapper,
owner,
+ mTokenLoan,
mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
mTokenToUsdDataFeed,
+ mTokenLoanToUsdDataFeed,
mockedAggregatorMToken,
+ mockedAggregatorMTokenLoan,
mockedAggregator,
dataFeed,
stableCoins,
- } = fixture;
-
- const token = stableCoins.usdc;
+ } = opts.fixture;
- await redemptionVaultWithMToken.setRedemptionVault(
- redemptionVaultWithUSTB.address,
- );
+ const token = stableCoins[opts.tokenKey];
await addPaymentTokenTest(
{ vault: redemptionVaultWithMToken, owner },
token,
dataFeed.address,
0,
- true,
+ opts.isStable,
);
await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
+ { vault: redemptionVaultLoanSwapper, owner },
token,
dataFeed.address,
0,
- true,
- );
-
- await redemptionVaultWithUSTB.addWaivedFeeAccount(
- redemptionVaultWithMToken.address,
+ opts.isStable,
);
- await ustbToken.mint(
- redemptionVaultWithUSTB.address,
- parseUnits('1000000', 6),
+ await setRoundData(
+ { mockedAggregator: mockedAggregatorMToken },
+ opts.mTbillRate,
);
- await token.mint(ustbRedemption.address, parseUnits('1000000'));
+ if (opts.tokenOutRate !== undefined) {
+ await setRoundData({ mockedAggregator }, opts.tokenOutRate);
+ }
await setInstantFeeTest(
{ vault: redemptionVaultWithMToken, owner },
0,
);
+ const amount = opts.redeemAmount ?? 100;
+
+ await mintToken(mTBILL, owner, amount * 100);
+ await mintToken(mTokenLoan, redemptionVaultWithMToken, amount * 100);
+ await mintToken(token, redemptionVaultLoanSwapper, amount * 10000);
+ await approveBase18(
+ owner,
+ mTBILL,
+ redemptionVaultWithMToken,
+ amount * 100,
+ );
+
+ const mFoneRate = await mTokenToUsdDataFeed.getDataInBase18();
+ const tokenOutRate = opts.isStable
+ ? parseUnits('1')
+ : await dataFeed.getDataInBase18();
+
return {
token,
+ amount,
redemptionVaultWithMToken,
+ redemptionVaultLoanSwapper,
owner,
+ mTokenLoan,
mTBILL,
- mFONE,
- mFoneToUsdDataFeed,
mTokenToUsdDataFeed,
- mockedAggregatorMToken,
+ mTokenLoanToUsdDataFeed,
+ mockedAggregatorMTokenLoan,
+ mockedAggregator,
+ mFoneRate,
+ tokenOutRate,
};
}
- it('succeeds with remainder-producing rate (mTBILL=1.05, stable)', async () => {
- const fixture = await loadFixture(defaultDeploy);
- const {
- token,
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFONE,
- mockedAggregatorMToken,
- } = await setupUstbInnerVault(fixture);
+ describe('with base RedemptionVault as inner vault', () => {
+ const tokenVariants: Array<{
+ key: 'usdc6' | 'usdc' | 'dai' | 'usdt';
+ label: string;
+ }> = [
+ { key: 'usdc6', label: '6-dec' },
+ { key: 'usdc', label: '8-dec' },
+ { key: 'dai', label: '9-dec' },
+ { key: 'usdt', label: '18-dec' },
+ ];
+
+ for (const { key, label } of tokenVariants) {
+ describe(`tokenOut ${label} (${key})`, () => {
+ it('succeeds with exact division (mTokenLoan=5, stable)', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const {
+ token,
+ amount,
+ redemptionVaultWithMToken,
+ owner,
+ mTokenLoan,
+ mFoneRate,
+ tokenOutRate,
+ } = await setupCeilDivTest({
+ fixture,
+ tokenKey: key,
+ mTbillRate: 5,
+ isStable: true,
+ });
+
+ const mTbillBefore = await mTokenLoan.balanceOf(
+ redemptionVaultWithMToken.address,
+ );
+ const userTokenBefore = await token.balanceOf(owner.address);
+
+ await redemptionVaultWithMToken
+ .connect(owner)
+ ['redeemInstant(address,uint256,uint256)'](
+ token.address,
+ parseUnits(amount.toString()),
+ 0,
+ );
+
+ expect(
+ await mTokenLoan.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.lt(mTbillBefore);
+
+ const expected = computeExpectedTokenOut(
+ parseUnits(amount.toString()),
+ mFoneRate,
+ tokenOutRate,
+ TOKEN_DECIMALS[key],
+ 0,
+ true,
+ );
+ const userTokenAfter = await token.balanceOf(owner.address);
+ expect(userTokenAfter.sub(userTokenBefore)).to.equal(expected);
+ });
+
+ it('succeeds with remainder-producing rate (mTokenLoan=1.05, stable)', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const {
+ token,
+ amount,
+ redemptionVaultWithMToken,
+ owner,
+ mTokenLoan,
+ mFoneRate,
+ tokenOutRate,
+ } = await setupCeilDivTest({
+ fixture,
+ tokenKey: key,
+ mTbillRate: 1.05,
+ isStable: true,
+ });
+
+ const mTbillBefore = await mTokenLoan.balanceOf(
+ redemptionVaultWithMToken.address,
+ );
+ const userTokenBefore = await token.balanceOf(owner.address);
+
+ await redemptionVaultWithMToken
+ .connect(owner)
+ ['redeemInstant(address,uint256,uint256)'](
+ token.address,
+ parseUnits(amount.toString()),
+ 0,
+ );
+
+ expect(
+ await mTokenLoan.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.lt(mTbillBefore);
+
+ const expected = computeExpectedTokenOut(
+ parseUnits(amount.toString()),
+ mFoneRate,
+ tokenOutRate,
+ TOKEN_DECIMALS[key],
+ 0,
+ true,
+ );
+ const userTokenAfter = await token.balanceOf(owner.address);
+ expect(userTokenAfter.sub(userTokenBefore)).to.equal(expected);
+ });
+
+ it('succeeds with high mTokenLoan rate (mTokenLoan=100, stable)', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const {
+ token,
+ amount,
+ redemptionVaultWithMToken,
+ owner,
+ mTokenLoan,
+ mFoneRate,
+ tokenOutRate,
+ } = await setupCeilDivTest({
+ fixture,
+ tokenKey: key,
+ mTbillRate: 100,
+ isStable: true,
+ });
+
+ const mTbillBefore = await mTokenLoan.balanceOf(
+ redemptionVaultWithMToken.address,
+ );
+ const userTokenBefore = await token.balanceOf(owner.address);
+
+ await redemptionVaultWithMToken
+ .connect(owner)
+ ['redeemInstant(address,uint256,uint256)'](
+ token.address,
+ parseUnits(amount.toString()),
+ 0,
+ );
+
+ expect(
+ await mTokenLoan.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.lt(mTbillBefore);
+
+ const expected = computeExpectedTokenOut(
+ parseUnits(amount.toString()),
+ mFoneRate,
+ tokenOutRate,
+ TOKEN_DECIMALS[key],
+ 0,
+ true,
+ );
+ const userTokenAfter = await token.balanceOf(owner.address);
+ expect(userTokenAfter.sub(userTokenBefore)).to.equal(expected);
+ });
+
+ it('succeeds with low mTokenLoan rate (mTokenLoan=0.5, stable)', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const {
+ token,
+ amount,
+ redemptionVaultWithMToken,
+ owner,
+ mTokenLoan,
+ mFoneRate,
+ tokenOutRate,
+ } = await setupCeilDivTest({
+ fixture,
+ tokenKey: key,
+ mTbillRate: 0.5,
+ isStable: true,
+ });
+
+ const mTbillBefore = await mTokenLoan.balanceOf(
+ redemptionVaultWithMToken.address,
+ );
+ const userTokenBefore = await token.balanceOf(owner.address);
+
+ await redemptionVaultWithMToken
+ .connect(owner)
+ ['redeemInstant(address,uint256,uint256)'](
+ token.address,
+ parseUnits(amount.toString()),
+ 0,
+ );
+
+ expect(
+ await mTokenLoan.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.lt(mTbillBefore);
+
+ const expected = computeExpectedTokenOut(
+ parseUnits(amount.toString()),
+ mFoneRate,
+ tokenOutRate,
+ TOKEN_DECIMALS[key],
+ 0,
+ true,
+ );
+ const userTokenAfter = await token.balanceOf(owner.address);
+ expect(userTokenAfter.sub(userTokenBefore)).to.equal(expected);
+ });
+
+ it('succeeds with near-boundary rate (mTokenLoan=1.00000003, stable)', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const {
+ token,
+ redemptionVaultWithMToken,
+ owner,
+ mTokenLoan,
+ mFoneRate,
+ tokenOutRate,
+ } = await setupCeilDivTest({
+ fixture,
+ tokenKey: key,
+ mTbillRate: 1.00000003,
+ isStable: true,
+ redeemAmount: 10000,
+ });
+
+ const mTbillBefore = await mTokenLoan.balanceOf(
+ redemptionVaultWithMToken.address,
+ );
+ const userTokenBefore = await token.balanceOf(owner.address);
+
+ await redemptionVaultWithMToken
+ .connect(owner)
+ ['redeemInstant(address,uint256,uint256)'](
+ token.address,
+ parseUnits('10000'),
+ 0,
+ );
+
+ expect(
+ await mTokenLoan.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.lt(mTbillBefore);
+
+ const expected = computeExpectedTokenOut(
+ parseUnits('10000'),
+ mFoneRate,
+ tokenOutRate,
+ TOKEN_DECIMALS[key],
+ 0,
+ true,
+ );
+ const userTokenAfter = await token.balanceOf(owner.address);
+ expect(userTokenAfter.sub(userTokenBefore)).to.equal(expected);
+ });
+
+ it('succeeds with non-stable tokenOut (mTokenLoan=1.05, tokenOut=1.03)', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const {
+ token,
+ amount,
+ redemptionVaultWithMToken,
+ owner,
+ mTokenLoan,
+ mFoneRate,
+ tokenOutRate,
+ } = await setupCeilDivTest({
+ fixture,
+ tokenKey: key,
+ mTbillRate: 1.05,
+ isStable: false,
+ tokenOutRate: 1.03,
+ });
+
+ const mTbillBefore = await mTokenLoan.balanceOf(
+ redemptionVaultWithMToken.address,
+ );
+ const userTokenBefore = await token.balanceOf(owner.address);
+
+ await redemptionVaultWithMToken
+ .connect(owner)
+ ['redeemInstant(address,uint256,uint256)'](
+ token.address,
+ parseUnits(amount.toString()),
+ 0,
+ );
+
+ expect(
+ await mTokenLoan.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.lt(mTbillBefore);
+
+ const expected = computeExpectedTokenOut(
+ parseUnits(amount.toString()),
+ mFoneRate,
+ tokenOutRate,
+ TOKEN_DECIMALS[key],
+ 0,
+ true,
+ );
+ const userTokenAfter = await token.balanceOf(owner.address);
+ expect(userTokenAfter.sub(userTokenBefore)).to.equal(expected);
+ });
+
+ it('succeeds with small redeem (1 mTBILL, mTokenLoan=1.05, stable)', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const {
+ token,
+ redemptionVaultWithMToken,
+ owner,
+ mTokenLoan,
+ mFoneRate,
+ tokenOutRate,
+ } = await setupCeilDivTest({
+ fixture,
+ tokenKey: key,
+ mTbillRate: 1.05,
+ isStable: true,
+ redeemAmount: 1,
+ });
+
+ const mTbillBefore = await mTokenLoan.balanceOf(
+ redemptionVaultWithMToken.address,
+ );
+ const userTokenBefore = await token.balanceOf(owner.address);
+
+ await setMinAmountTest(
+ { vault: redemptionVaultWithMToken, owner },
+ 0,
+ );
+
+ await redemptionVaultWithMToken
+ .connect(owner)
+ ['redeemInstant(address,uint256,uint256)'](
+ token.address,
+ parseUnits('1'),
+ 0,
+ );
+
+ expect(
+ await mTokenLoan.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.lt(mTbillBefore);
+
+ const expected = computeExpectedTokenOut(
+ parseUnits('1'),
+ mFoneRate,
+ tokenOutRate,
+ TOKEN_DECIMALS[key],
+ 0,
+ true,
+ );
+ const userTokenAfter = await token.balanceOf(owner.address);
+ expect(userTokenAfter.sub(userTokenBefore)).to.equal(expected);
+ });
+
+ it('succeeds with large redeem near daily limit (mTokenLoan=1.05, stable)', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const {
+ token,
+ redemptionVaultWithMToken,
+ owner,
+ mTokenLoan,
+ mFoneRate,
+ tokenOutRate,
+ } = await setupCeilDivTest({
+ fixture,
+ tokenKey: key,
+ mTbillRate: 1.05,
+ isStable: true,
+ redeemAmount: 50000,
+ });
+
+ const mTbillBefore = await mTokenLoan.balanceOf(
+ redemptionVaultWithMToken.address,
+ );
+ const userTokenBefore = await token.balanceOf(owner.address);
+
+ await redemptionVaultWithMToken
+ .connect(owner)
+ ['redeemInstant(address,uint256,uint256)'](
+ token.address,
+ parseUnits('50000'),
+ 0,
+ );
+
+ expect(
+ await mTokenLoan.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.lt(mTbillBefore);
+
+ const expected = computeExpectedTokenOut(
+ parseUnits('50000'),
+ mFoneRate,
+ tokenOutRate,
+ TOKEN_DECIMALS[key],
+ 0,
+ true,
+ );
+ const userTokenAfter = await token.balanceOf(owner.address);
+ expect(userTokenAfter.sub(userTokenBefore)).to.equal(expected);
+ });
+ });
+ }
+ });
- await setRoundData(
- { mockedAggregator: mockedAggregatorMToken },
- 1.05,
- );
- await mintToken(mFONE, owner, 10000);
- await mintToken(mTBILL, redemptionVaultWithMToken, 10000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 10000);
+ describe('with RedemptionVaultWithAave as inner vault', () => {
+ for (const { key, label } of [
+ { key: 'usdc' as const, label: '8-dec' },
+ { key: 'usdc6' as const, label: '6-dec' },
+ ]) {
+ describe(`tokenOut ${label} (${key})`, () => {
+ async function setupAaveInnerVault(
+ fixture: Awaited>,
+ tokenKey: 'usdc' | 'usdc6',
+ ) {
+ const {
+ redemptionVaultWithMToken,
+ redemptionVaultWithAave,
+ aavePoolMock,
+ aUSDC,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ mTokenLoanToUsdDataFeed,
+ mockedAggregatorMToken,
+ dataFeed,
+ stableCoins,
+ } = fixture;
+
+ const token = stableCoins[tokenKey];
+
+ await redemptionVaultWithMToken.setRedemptionVault(
+ redemptionVaultWithAave.address,
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMToken, owner },
+ token,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithAave, owner },
+ token,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redemptionVaultWithAave.setWaivedFeeAccount(
+ redemptionVaultWithMToken.address,
+ true,
+ );
+
+ if (tokenKey === 'usdc6') {
+ const { ERC20Mock__factory } = await import(
+ '../../typechain-types'
+ );
+ const aUsdc6 = await new ERC20Mock__factory(owner).deploy(6);
+ await aavePoolMock.setReserveAToken(
+ token.address,
+ aUsdc6.address,
+ );
+ await token.mint(
+ aavePoolMock.address,
+ parseUnits('1000000', 6),
+ );
+ await aUsdc6.mint(
+ redemptionVaultWithAave.address,
+ parseUnits('1000000', 6),
+ );
+ await redemptionVaultWithAave.setAavePool(
+ token.address,
+ aavePoolMock.address,
+ );
+ } else {
+ await token.mint(aavePoolMock.address, parseUnits('1000000'));
+ await aUSDC.mint(
+ redemptionVaultWithAave.address,
+ parseUnits('1000000'),
+ );
+ await redemptionVaultWithAave.setAavePool(
+ token.address,
+ aavePoolMock.address,
+ );
+ }
+
+ await setInstantFeeTest(
+ { vault: redemptionVaultWithMToken, owner },
+ 0,
+ );
+
+ return {
+ token,
+ redemptionVaultWithMToken,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ mTokenLoanToUsdDataFeed,
+ mockedAggregatorMToken,
+ };
+ }
+
+ it('succeeds with remainder-producing rate (mTokenLoan=1.05, stable)', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const {
+ token,
+ redemptionVaultWithMToken,
+ owner,
+ mTBILL,
+ mockedAggregatorMToken,
+ } = await setupAaveInnerVault(fixture, key);
+
+ await setRoundData(
+ { mockedAggregator: mockedAggregatorMToken },
+ 1.05,
+ );
+ await mintToken(mTBILL, owner, 10000);
+ await mintToken(mTBILL, redemptionVaultWithMToken, 10000);
+ await approveBase18(
+ owner,
+ mTBILL,
+ redemptionVaultWithMToken,
+ 10000,
+ );
+
+ const mTbillBefore = await mTBILL.balanceOf(
+ redemptionVaultWithMToken.address,
+ );
+
+ await redemptionVaultWithMToken
+ .connect(owner)
+ ['redeemInstant(address,uint256,uint256)'](
+ token.address,
+ parseUnits('100'),
+ 0,
+ );
+
+ expect(
+ await mTBILL.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.lt(mTbillBefore);
+ });
+
+ it('succeeds with high mTokenLoan rate (mTokenLoan=100, stable)', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const {
+ token,
+ redemptionVaultWithMToken,
+ owner,
+ mTBILL,
+ mockedAggregatorMToken,
+ } = await setupAaveInnerVault(fixture, key);
+
+ await setRoundData(
+ { mockedAggregator: mockedAggregatorMToken },
+ 100,
+ );
+ await mintToken(mTBILL, owner, 10000);
+ await mintToken(mTBILL, redemptionVaultWithMToken, 10000);
+ await approveBase18(
+ owner,
+ mTBILL,
+ redemptionVaultWithMToken,
+ 10000,
+ );
+
+ const mTbillBefore = await mTBILL.balanceOf(
+ redemptionVaultWithMToken.address,
+ );
+
+ await redemptionVaultWithMToken
+ .connect(owner)
+ ['redeemInstant(address,uint256,uint256)'](
+ token.address,
+ parseUnits('100'),
+ 0,
+ );
+
+ expect(
+ await mTBILL.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.lt(mTbillBefore);
+ });
+
+ it('succeeds with near-boundary rate (mTokenLoan=1.00000003, stable)', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const {
+ token,
+ redemptionVaultWithMToken,
+ owner,
+ mTBILL,
+ mockedAggregatorMToken,
+ } = await setupAaveInnerVault(fixture, key);
+
+ await setRoundData(
+ { mockedAggregator: mockedAggregatorMToken },
+ 1.00000003,
+ );
+ await mintToken(mTBILL, owner, 100000);
+ await mintToken(mTBILL, redemptionVaultWithMToken, 100000);
+ await approveBase18(
+ owner,
+ mTBILL,
+ redemptionVaultWithMToken,
+ 100000,
+ );
+
+ const mTbillBefore = await mTBILL.balanceOf(
+ redemptionVaultWithMToken.address,
+ );
+
+ await redemptionVaultWithMToken
+ .connect(owner)
+ ['redeemInstant(address,uint256,uint256)'](
+ token.address,
+ parseUnits('10000'),
+ 0,
+ );
+
+ expect(
+ await mTBILL.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.lt(mTbillBefore);
+ });
+ });
+ }
+ });
- const mTbillBefore = await mTBILL.balanceOf(
- redemptionVaultWithMToken.address,
- );
+ describe('with RedemptionVaultWithMorpho as inner vault', () => {
+ for (const { key, label } of [
+ { key: 'usdc' as const, label: '8-dec' },
+ { key: 'usdc6' as const, label: '6-dec' },
+ ]) {
+ describe(`tokenOut ${label} (${key})`, () => {
+ async function setupMorphoInnerVault(
+ fixture: Awaited>,
+ tokenKey: 'usdc' | 'usdc6',
+ ) {
+ const {
+ redemptionVaultWithMToken,
+ redemptionVaultWithMorpho,
+ morphoVaultMock,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ mTokenLoanToUsdDataFeed,
+ mockedAggregatorMToken,
+ dataFeed,
+ stableCoins,
+ } = fixture;
+
+ const token = stableCoins[tokenKey];
+
+ await redemptionVaultWithMToken.setRedemptionVault(
+ redemptionVaultWithMorpho.address,
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMToken, owner },
+ token,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMorpho, owner },
+ token,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redemptionVaultWithMorpho.setWaivedFeeAccount(
+ redemptionVaultWithMToken.address,
+ true,
+ );
+
+ if (tokenKey === 'usdc6') {
+ const { MorphoVaultMock__factory } = await import(
+ '../../typechain-types'
+ );
+ const morphoUsdc6 = await new MorphoVaultMock__factory(
+ owner,
+ ).deploy(token.address);
+ await token.mint(
+ morphoUsdc6.address,
+ parseUnits('1000000', 6),
+ );
+ await morphoUsdc6.mint(
+ redemptionVaultWithMorpho.address,
+ parseUnits('1000000', 6),
+ );
+ await redemptionVaultWithMorpho.setMorphoVault(
+ token.address,
+ morphoUsdc6.address,
+ );
+ } else {
+ await token.mint(
+ morphoVaultMock.address,
+ parseUnits('1000000'),
+ );
+ await morphoVaultMock.mint(
+ redemptionVaultWithMorpho.address,
+ parseUnits('1000000'),
+ );
+ await redemptionVaultWithMorpho.setMorphoVault(
+ token.address,
+ morphoVaultMock.address,
+ );
+ }
+
+ await setInstantFeeTest(
+ { vault: redemptionVaultWithMToken, owner },
+ 0,
+ );
+
+ return {
+ token,
+ redemptionVaultWithMToken,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ mTokenLoanToUsdDataFeed,
+ mockedAggregatorMToken,
+ };
+ }
+
+ it('succeeds with remainder-producing rate (mTokenLoan=1.05, stable)', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const {
+ token,
+ redemptionVaultWithMToken,
+ owner,
+ mTBILL,
+ mockedAggregatorMToken,
+ } = await setupMorphoInnerVault(fixture, key);
+
+ await setRoundData(
+ { mockedAggregator: mockedAggregatorMToken },
+ 1.05,
+ );
+ await mintToken(mTBILL, owner, 10000);
+ await mintToken(mTBILL, redemptionVaultWithMToken, 10000);
+ await approveBase18(
+ owner,
+ mTBILL,
+ redemptionVaultWithMToken,
+ 10000,
+ );
+
+ const mTbillBefore = await mTBILL.balanceOf(
+ redemptionVaultWithMToken.address,
+ );
+
+ await redemptionVaultWithMToken
+ .connect(owner)
+ ['redeemInstant(address,uint256,uint256)'](
+ token.address,
+ parseUnits('100'),
+ 0,
+ );
+
+ expect(
+ await mTBILL.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.lt(mTbillBefore);
+ });
+
+ it('succeeds with high mTokenLoan rate (mTokenLoan=100, stable)', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const {
+ token,
+ redemptionVaultWithMToken,
+ owner,
+ mTBILL,
+ mockedAggregatorMToken,
+ } = await setupMorphoInnerVault(fixture, key);
+
+ await setRoundData(
+ { mockedAggregator: mockedAggregatorMToken },
+ 100,
+ );
+ await mintToken(mTBILL, owner, 10000);
+ await mintToken(mTBILL, redemptionVaultWithMToken, 10000);
+ await approveBase18(
+ owner,
+ mTBILL,
+ redemptionVaultWithMToken,
+ 10000,
+ );
+
+ const mTbillBefore = await mTBILL.balanceOf(
+ redemptionVaultWithMToken.address,
+ );
+
+ await redemptionVaultWithMToken
+ .connect(owner)
+ ['redeemInstant(address,uint256,uint256)'](
+ token.address,
+ parseUnits('100'),
+ 0,
+ );
+
+ expect(
+ await mTBILL.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.lt(mTbillBefore);
+ });
+
+ it('succeeds with near-boundary rate (mTokenLoan=1.00000003, stable)', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const {
+ token,
+ redemptionVaultWithMToken,
+ owner,
+ mTBILL,
+ mockedAggregatorMToken,
+ } = await setupMorphoInnerVault(fixture, key);
+
+ await setRoundData(
+ { mockedAggregator: mockedAggregatorMToken },
+ 1.00000003,
+ );
+ await mintToken(mTBILL, owner, 100000);
+ await mintToken(mTBILL, redemptionVaultWithMToken, 100000);
+ await approveBase18(
+ owner,
+ mTBILL,
+ redemptionVaultWithMToken,
+ 100000,
+ );
+
+ const mTbillBefore = await mTBILL.balanceOf(
+ redemptionVaultWithMToken.address,
+ );
+
+ await redemptionVaultWithMToken
+ .connect(owner)
+ ['redeemInstant(address,uint256,uint256)'](
+ token.address,
+ parseUnits('10000'),
+ 0,
+ );
+
+ expect(
+ await mTBILL.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.lt(mTbillBefore);
+ });
+ });
+ }
+ });
- await redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
- parseUnits('100'),
- 0,
- );
+ describe('with RedemptionVaultWithUSTB as inner vault', () => {
+ describe('tokenOut 8-dec (usdc — USTB only supports its configured USDC)', () => {
+ async function setupUstbInnerVault(
+ fixture: Awaited>,
+ ) {
+ const {
+ redemptionVaultWithMToken,
+ redemptionVaultWithUSTB,
+ ustbToken,
+ ustbRedemption,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ mTokenLoanToUsdDataFeed,
+ mockedAggregatorMToken,
+ dataFeed,
+ stableCoins,
+ } = fixture;
+
+ const token = stableCoins.usdc;
+
+ await redemptionVaultWithMToken.setRedemptionVault(
+ redemptionVaultWithUSTB.address,
+ );
- expect(
- await mTBILL.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mTbillBefore);
- });
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMToken, owner },
+ token,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithUSTB, owner },
+ token,
+ dataFeed.address,
+ 0,
+ true,
+ );
- it('succeeds with high mTBILL rate (mTBILL=100, stable)', async () => {
- const fixture = await loadFixture(defaultDeploy);
- const {
- token,
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFONE,
- mockedAggregatorMToken,
- } = await setupUstbInnerVault(fixture);
+ await redemptionVaultWithUSTB.setWaivedFeeAccount(
+ redemptionVaultWithMToken.address,
+ true,
+ );
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 100);
- await mintToken(mFONE, owner, 10000);
- await mintToken(mTBILL, redemptionVaultWithMToken, 10000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 10000);
+ await ustbToken.mint(
+ redemptionVaultWithUSTB.address,
+ parseUnits('1000000', 6),
+ );
+ await token.mint(ustbRedemption.address, parseUnits('1000000'));
- const mTbillBefore = await mTBILL.balanceOf(
- redemptionVaultWithMToken.address,
- );
+ await setInstantFeeTest(
+ { vault: redemptionVaultWithMToken, owner },
+ 0,
+ );
- await redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
- parseUnits('100'),
- 0,
- );
+ return {
+ token,
+ redemptionVaultWithMToken,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ mTokenLoanToUsdDataFeed,
+ mockedAggregatorMToken,
+ };
+ }
- expect(
- await mTBILL.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mTbillBefore);
- });
+ it('succeeds with remainder-producing rate (mTokenLoan=1.05, stable)', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const {
+ token,
+ redemptionVaultWithMToken,
+ owner,
+ mTBILL,
+ mockedAggregatorMToken,
+ } = await setupUstbInnerVault(fixture);
- it('succeeds with near-boundary rate (mTBILL=1.00000003, stable)', async () => {
- const fixture = await loadFixture(defaultDeploy);
- const {
- token,
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFONE,
- mockedAggregatorMToken,
- } = await setupUstbInnerVault(fixture);
+ await setRoundData(
+ { mockedAggregator: mockedAggregatorMToken },
+ 1.05,
+ );
+ await mintToken(mTBILL, owner, 10000);
+ await mintToken(mTBILL, redemptionVaultWithMToken, 10000);
+ await approveBase18(
+ owner,
+ mTBILL,
+ redemptionVaultWithMToken,
+ 10000,
+ );
- await setRoundData(
- { mockedAggregator: mockedAggregatorMToken },
- 1.00000003,
- );
- await mintToken(mFONE, owner, 100000);
- await mintToken(mTBILL, redemptionVaultWithMToken, 100000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 100000);
+ const mTbillBefore = await mTBILL.balanceOf(
+ redemptionVaultWithMToken.address,
+ );
- const mTbillBefore = await mTBILL.balanceOf(
- redemptionVaultWithMToken.address,
- );
+ await redemptionVaultWithMToken
+ .connect(owner)
+ ['redeemInstant(address,uint256,uint256)'](
+ token.address,
+ parseUnits('100'),
+ 0,
+ );
+
+ expect(
+ await mTBILL.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.lt(mTbillBefore);
+ });
- await redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
- parseUnits('10000'),
- 0,
- );
+ it('succeeds with high mTokenLoan rate (mTokenLoan=100, stable)', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const {
+ token,
+ redemptionVaultWithMToken,
+ owner,
+ mTBILL,
+ mockedAggregatorMToken,
+ } = await setupUstbInnerVault(fixture);
- expect(
- await mTBILL.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mTbillBefore);
+ await setRoundData(
+ { mockedAggregator: mockedAggregatorMToken },
+ 100,
+ );
+ await mintToken(mTBILL, owner, 10000);
+ await mintToken(mTBILL, redemptionVaultWithMToken, 10000);
+ await approveBase18(
+ owner,
+ mTBILL,
+ redemptionVaultWithMToken,
+ 10000,
+ );
+
+ const mTbillBefore = await mTBILL.balanceOf(
+ redemptionVaultWithMToken.address,
+ );
+
+ await redemptionVaultWithMToken
+ .connect(owner)
+ ['redeemInstant(address,uint256,uint256)'](
+ token.address,
+ parseUnits('100'),
+ 0,
+ );
+
+ expect(
+ await mTBILL.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.lt(mTbillBefore);
+ });
+
+ it('succeeds with near-boundary rate (mTokenLoan=1.00000003, stable)', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const {
+ token,
+ redemptionVaultWithMToken,
+ owner,
+ mTBILL,
+ mockedAggregatorMToken,
+ } = await setupUstbInnerVault(fixture);
+
+ await setRoundData(
+ { mockedAggregator: mockedAggregatorMToken },
+ 1.00000003,
+ );
+ await mintToken(mTBILL, owner, 100000);
+ await mintToken(mTBILL, redemptionVaultWithMToken, 100000);
+ await approveBase18(
+ owner,
+ mTBILL,
+ redemptionVaultWithMToken,
+ 100000,
+ );
+
+ const mTbillBefore = await mTBILL.balanceOf(
+ redemptionVaultWithMToken.address,
+ );
+
+ await redemptionVaultWithMToken
+ .connect(owner)
+ ['redeemInstant(address,uint256,uint256)'](
+ token.address,
+ parseUnits('10000'),
+ 0,
+ );
+
+ expect(
+ await mTBILL.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.lt(mTbillBefore);
+ });
+ });
});
- });
- });
- describe('with RedemptionVaultWithSwapper as inner vault - direct path', () => {
- async function setupSwapperDirectPath(
- fixture: Awaited>,
- tokenKey: 'usdc6' | 'usdc' | 'dai' | 'usdt',
- ) {
- const {
- redemptionVaultWithMToken,
- redemptionVaultWithSwapper,
- owner,
- mBASIS,
- mFONE,
- mFoneToUsdDataFeed,
- mBasisToUsdDataFeed,
- mockedAggregatorMBasis,
- mockedAggregator,
- dataFeed,
- stableCoins,
- } = fixture;
-
- const token = stableCoins[tokenKey];
-
- await redemptionVaultWithMToken.setRedemptionVault(
- redemptionVaultWithSwapper.address,
- );
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- token,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithSwapper, owner },
- token,
- dataFeed.address,
- 0,
- true,
- );
-
- await redemptionVaultWithSwapper.addWaivedFeeAccount(
- redemptionVaultWithMToken.address,
- );
-
- await setInstantFeeTest({ vault: redemptionVaultWithMToken, owner }, 0);
-
- await mintToken(token, redemptionVaultWithSwapper, 1_000_000);
-
- return {
- token,
- redemptionVaultWithMToken,
- redemptionVaultWithSwapper,
- owner,
- mBASIS,
- mFONE,
- mFoneToUsdDataFeed,
- mBasisToUsdDataFeed,
- mockedAggregatorMBasis,
- mockedAggregator,
- };
- }
-
- for (const { key, label } of [
- { key: 'usdc6' as const, label: '6-dec' },
- { key: 'usdc' as const, label: '8-dec' },
- { key: 'dai' as const, label: '9-dec' },
- { key: 'usdt' as const, label: '18-dec' },
- ]) {
- describe(`tokenOut ${label} (${key})`, () => {
- it('succeeds with exact division (mBasis=5, stable)', async () => {
+ describe('with outer vault fee enabled', () => {
+ it('succeeds with 1% fee, usdc6 (6-dec), mTokenLoan=1.05, stable', async () => {
const fixture = await loadFixture(defaultDeploy);
const {
token,
+ amount,
redemptionVaultWithMToken,
owner,
- mBASIS,
- mFONE,
- mockedAggregatorMBasis,
- } = await setupSwapperDirectPath(fixture, key);
+ mTokenLoan,
+ } = await setupCeilDivTest({
+ fixture,
+ tokenKey: 'usdc6',
+ mTbillRate: 1.05,
+ isStable: true,
+ });
- await setRoundData({ mockedAggregator: mockedAggregatorMBasis }, 5);
- await mintToken(mFONE, owner, 10000);
- await mintToken(mBASIS, redemptionVaultWithMToken, 10000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 10000);
+ await setInstantFeeTest(
+ { vault: redemptionVaultWithMToken, owner },
+ 100,
+ );
- const mBasisBefore = await mBASIS.balanceOf(
+ const mTbillBefore = await mTokenLoan.balanceOf(
redemptionVaultWithMToken.address,
);
+ const userTokenBefore = await token.balanceOf(owner.address);
await redemptionVaultWithMToken
.connect(owner)
['redeemInstant(address,uint256,uint256)'](
token.address,
- parseUnits('100'),
+ parseUnits(amount.toString()),
0,
);
expect(
- await mBASIS.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mBasisBefore);
+ await mTokenLoan.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.lt(mTbillBefore);
+ expect(await token.balanceOf(owner.address)).to.be.gt(
+ userTokenBefore,
+ );
});
- it('succeeds with remainder-producing rate (mBasis=1.05, stable)', async () => {
+ it('succeeds with 1% fee, usdc (8-dec), mTokenLoan=1.05, stable', async () => {
const fixture = await loadFixture(defaultDeploy);
const {
token,
+ amount,
redemptionVaultWithMToken,
owner,
- mBASIS,
- mFONE,
- mockedAggregatorMBasis,
- } = await setupSwapperDirectPath(fixture, key);
+ mTokenLoan,
+ } = await setupCeilDivTest({
+ fixture,
+ tokenKey: 'usdc',
+ mTbillRate: 1.05,
+ isStable: true,
+ });
- await setRoundData(
- { mockedAggregator: mockedAggregatorMBasis },
- 1.05,
+ await setInstantFeeTest(
+ { vault: redemptionVaultWithMToken, owner },
+ 100,
);
- await mintToken(mFONE, owner, 10000);
- await mintToken(mBASIS, redemptionVaultWithMToken, 10000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 10000);
- const mBasisBefore = await mBASIS.balanceOf(
+ const mTbillBefore = await mTokenLoan.balanceOf(
redemptionVaultWithMToken.address,
);
const userTokenBefore = await token.balanceOf(owner.address);
@@ -4128,79 +2466,80 @@ describe('RedemptionVaultWithMToken', function () {
.connect(owner)
['redeemInstant(address,uint256,uint256)'](
token.address,
- parseUnits('100'),
+ parseUnits(amount.toString()),
0,
);
expect(
- await mBASIS.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mBasisBefore);
+ await mTokenLoan.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.lt(mTbillBefore);
expect(await token.balanceOf(owner.address)).to.be.gt(
userTokenBefore,
);
});
- it('succeeds with high rate (mBasis=100, stable)', async () => {
+ it('succeeds with 1% fee, usdt (18-dec), mTokenLoan=1.05, stable', async () => {
const fixture = await loadFixture(defaultDeploy);
const {
token,
+ amount,
redemptionVaultWithMToken,
owner,
- mBASIS,
- mFONE,
- mockedAggregatorMBasis,
- } = await setupSwapperDirectPath(fixture, key);
+ mTokenLoan,
+ } = await setupCeilDivTest({
+ fixture,
+ tokenKey: 'usdt',
+ mTbillRate: 1.05,
+ isStable: true,
+ });
- await setRoundData(
- { mockedAggregator: mockedAggregatorMBasis },
+ await setInstantFeeTest(
+ { vault: redemptionVaultWithMToken, owner },
100,
);
- await mintToken(mFONE, owner, 10000);
- await mintToken(mBASIS, redemptionVaultWithMToken, 10000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 10000);
- const mBasisBefore = await mBASIS.balanceOf(
+ const mTbillBefore = await mTokenLoan.balanceOf(
redemptionVaultWithMToken.address,
);
+ const userTokenBefore = await token.balanceOf(owner.address);
await redemptionVaultWithMToken
.connect(owner)
['redeemInstant(address,uint256,uint256)'](
token.address,
- parseUnits('100'),
+ parseUnits(amount.toString()),
0,
);
expect(
- await mBASIS.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mBasisBefore);
+ await mTokenLoan.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.lt(mTbillBefore);
+ expect(await token.balanceOf(owner.address)).to.be.gt(
+ userTokenBefore,
+ );
});
- it('succeeds with near-boundary rate (mBasis=1.00000003, stable)', async () => {
+ it('succeeds with 1% fee, usdc6 (6-dec), mTokenLoan=100, stable', async () => {
const fixture = await loadFixture(defaultDeploy);
const {
token,
+ amount,
redemptionVaultWithMToken,
owner,
- mBASIS,
- mFONE,
- mockedAggregatorMBasis,
- } = await setupSwapperDirectPath(fixture, key);
+ mTokenLoan,
+ } = await setupCeilDivTest({
+ fixture,
+ tokenKey: 'usdc6',
+ mTbillRate: 100,
+ isStable: true,
+ });
- await setRoundData(
- { mockedAggregator: mockedAggregatorMBasis },
- 1.00000003,
- );
- await mintToken(mFONE, owner, 100000);
- await mintToken(mBASIS, redemptionVaultWithMToken, 100000);
- await approveBase18(
- owner,
- mFONE,
- redemptionVaultWithMToken,
- 100000,
+ await setInstantFeeTest(
+ { vault: redemptionVaultWithMToken, owner },
+ 100,
);
- const mBasisBefore = await mBASIS.balanceOf(
+ const mTbillBefore = await mTokenLoan.balanceOf(
redemptionVaultWithMToken.address,
);
@@ -4208,60 +2547,74 @@ describe('RedemptionVaultWithMToken', function () {
.connect(owner)
['redeemInstant(address,uint256,uint256)'](
token.address,
- parseUnits('10000'),
+ parseUnits(amount.toString()),
0,
);
expect(
- await mBASIS.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mBasisBefore);
+ await mTokenLoan.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.lt(mTbillBefore);
});
- it('succeeds with non-stable tokenOut (mBasis=1.05, tokenOut=1.03)', async () => {
+ it('succeeds with 1% fee, usdc (8-dec), non-stable mTokenLoan=1.05 tokenOut=1.03', async () => {
const fixture = await loadFixture(defaultDeploy);
const {
token,
+ amount,
redemptionVaultWithMToken,
owner,
- mBASIS,
- mFONE,
- mockedAggregatorMBasis,
- mockedAggregator,
- } = await setupSwapperDirectPath(fixture, key);
+ mTokenLoan,
+ } = await setupCeilDivTest({
+ fixture,
+ tokenKey: 'usdc',
+ mTbillRate: 1.05,
+ isStable: false,
+ tokenOutRate: 1.03,
+ });
- await removePaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- token,
- );
- await addPaymentTokenTest(
+ await setInstantFeeTest(
{ vault: redemptionVaultWithMToken, owner },
- token,
- fixture.dataFeed.address,
- 0,
- false,
+ 100,
);
- await removePaymentTokenTest(
- { vault: fixture.redemptionVaultWithSwapper, owner },
- token,
+
+ const mTbillBefore = await mTokenLoan.balanceOf(
+ redemptionVaultWithMToken.address,
);
- await addPaymentTokenTest(
- { vault: fixture.redemptionVaultWithSwapper, owner },
- token,
- fixture.dataFeed.address,
- 0,
- false,
+ const userTokenBefore = await token.balanceOf(owner.address);
+
+ await redemptionVaultWithMToken
+ .connect(owner)
+ ['redeemInstant(address,uint256,uint256)'](
+ token.address,
+ parseUnits(amount.toString()),
+ 0,
+ );
+
+ expect(
+ await mTokenLoan.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.lt(mTbillBefore);
+ expect(await token.balanceOf(owner.address)).to.be.gt(
+ userTokenBefore,
);
+ });
- await setRoundData(
- { mockedAggregator: mockedAggregatorMBasis },
- 1.05,
+ it('succeeds with 1% fee, usdc6 (6-dec), near-boundary mTokenLoan=1.00000003', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const { token, redemptionVaultWithMToken, owner, mTokenLoan } =
+ await setupCeilDivTest({
+ fixture,
+ tokenKey: 'usdc6',
+ mTbillRate: 1.00000003,
+ isStable: true,
+ redeemAmount: 10000,
+ });
+
+ await setInstantFeeTest(
+ { vault: redemptionVaultWithMToken, owner },
+ 100,
);
- await setRoundData({ mockedAggregator }, 1.03);
- await mintToken(mFONE, owner, 10000);
- await mintToken(mBASIS, redemptionVaultWithMToken, 10000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 10000);
- const mBasisBefore = await mBASIS.balanceOf(
+ const mTbillBefore = await mTokenLoan.balanceOf(
redemptionVaultWithMToken.address,
);
@@ -4269,794 +2622,319 @@ describe('RedemptionVaultWithMToken', function () {
.connect(owner)
['redeemInstant(address,uint256,uint256)'](
token.address,
- parseUnits('100'),
+ parseUnits('10000'),
0,
);
expect(
- await mBASIS.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mBasisBefore);
+ await mTokenLoan.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.lt(mTbillBefore);
});
});
- }
- });
- describe('with RedemptionVaultWithSwapper as inner vault - swap path', () => {
- async function setupSwapperSwapPath(
- fixture: Awaited>,
- tokenKey: 'usdc6' | 'usdc' | 'dai' | 'usdt',
- ) {
- const {
- redemptionVaultWithMToken,
- redemptionVaultWithSwapper,
- redemptionVault,
- owner,
- mTBILL,
- mBASIS,
- mFONE,
- mFoneToUsdDataFeed,
- mBasisToUsdDataFeed,
- mockedAggregatorMBasis,
- mockedAggregatorMToken,
- mockedAggregator,
- dataFeed,
- stableCoins,
- liquidityProvider,
- } = fixture;
-
- const token = stableCoins[tokenKey];
-
- await redemptionVaultWithMToken.setRedemptionVault(
- redemptionVaultWithSwapper.address,
- );
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMToken, owner },
- token,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithSwapper, owner },
- token,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- token,
- dataFeed.address,
- 0,
- true,
- );
-
- await redemptionVaultWithSwapper.addWaivedFeeAccount(
- redemptionVaultWithMToken.address,
- );
-
- await setInstantFeeTest({ vault: redemptionVaultWithMToken, owner }, 0);
-
- await mintToken(mTBILL, liquidityProvider, 1_000_000);
- await approveBase18(
- liquidityProvider,
- mTBILL,
- redemptionVaultWithSwapper,
- 1_000_000,
- );
- await mintToken(token, redemptionVault, 1_000_000);
-
- return {
- token,
- redemptionVaultWithMToken,
- redemptionVaultWithSwapper,
- redemptionVault,
- owner,
- mTBILL,
- mBASIS,
- mFONE,
- mFoneToUsdDataFeed,
- mBasisToUsdDataFeed,
- mockedAggregatorMBasis,
- mockedAggregatorMToken,
- mockedAggregator,
- liquidityProvider,
- };
- }
-
- for (const { key, label } of [
- { key: 'usdc6' as const, label: '6-dec' },
- { key: 'usdc' as const, label: '8-dec' },
- { key: 'dai' as const, label: '9-dec' },
- { key: 'usdt' as const, label: '18-dec' },
- ]) {
- describe(`tokenOut ${label} (${key})`, () => {
- it('succeeds with equal mBasis/mTBILL rates (mBasis=5, mTBILL=5, stable)', async () => {
+ describe('minReceiveAmount assertions', () => {
+ it('succeeds with exact minReceiveAmount (6-dec, mTokenLoan=1.05)', async () => {
const fixture = await loadFixture(defaultDeploy);
const {
token,
+ amount,
redemptionVaultWithMToken,
owner,
- mBASIS,
- mFONE,
- mockedAggregatorMBasis,
- mockedAggregatorMToken,
- } = await setupSwapperSwapPath(fixture, key);
-
- await setRoundData({ mockedAggregator: mockedAggregatorMBasis }, 5);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await mintToken(mFONE, owner, 10000);
- await mintToken(mBASIS, redemptionVaultWithMToken, 10000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 10000);
+ mFoneRate,
+ tokenOutRate,
+ } = await setupCeilDivTest({
+ fixture,
+ tokenKey: 'usdc6',
+ mTbillRate: 1.05,
+ isStable: true,
+ });
- const mBasisBefore = await mBASIS.balanceOf(
- redemptionVaultWithMToken.address,
+ const expectedNative = computeExpectedTokenOut(
+ parseUnits(amount.toString()),
+ mFoneRate,
+ tokenOutRate,
+ TOKEN_DECIMALS['usdc6'],
+ 0,
+ true,
);
+ const expectedBase18 = expectedNative.mul(
+ BigNumber.from(10).pow(18 - TOKEN_DECIMALS['usdc6']),
+ );
+
+ const userTokenBefore = await token.balanceOf(owner.address);
await redemptionVaultWithMToken
.connect(owner)
['redeemInstant(address,uint256,uint256)'](
token.address,
- parseUnits('100'),
- 0,
+ parseUnits(amount.toString()),
+ expectedBase18,
);
- expect(
- await mBASIS.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mBasisBefore);
+ const userTokenAfter = await token.balanceOf(owner.address);
+ expect(userTokenAfter.sub(userTokenBefore)).to.equal(
+ expectedNative,
+ );
});
- it('succeeds with clean-division rates (mBasis=6, mTBILL=3, stable)', async () => {
+ it('reverts when minReceiveAmount exceeds actual (6-dec, mTokenLoan=1.05)', async () => {
const fixture = await loadFixture(defaultDeploy);
const {
token,
+ amount,
redemptionVaultWithMToken,
owner,
- mBASIS,
- mFONE,
- mockedAggregatorMBasis,
- mockedAggregatorMToken,
- } = await setupSwapperSwapPath(fixture, key);
+ mFoneRate,
+ tokenOutRate,
+ } = await setupCeilDivTest({
+ fixture,
+ tokenKey: 'usdc6',
+ mTbillRate: 1.05,
+ isStable: true,
+ });
+
+ const expectedNative = computeExpectedTokenOut(
+ parseUnits(amount.toString()),
+ mFoneRate,
+ tokenOutRate,
+ TOKEN_DECIMALS['usdc6'],
+ 0,
+ true,
+ );
+ const tooHigh = expectedNative
+ .mul(BigNumber.from(10).pow(18 - TOKEN_DECIMALS['usdc6']))
+ .add(1);
+
+ await expect(
+ redemptionVaultWithMToken
+ .connect(owner)
+ ['redeemInstant(address,uint256,uint256)'](
+ token.address,
+ parseUnits(amount.toString()),
+ tooHigh,
+ ),
+ ).to.be.revertedWithCustomError(
+ redemptionVaultWithMToken,
+ 'SlippageExceeded',
+ );
+ });
+ });
+
+ describe('partial outer vault balance', () => {
+ it('succeeds with 30% tokenOut present in outer vault (6-dec, mTokenLoan=1.05)', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+ const {
+ token,
+ amount,
+ redemptionVaultWithMToken,
+ owner,
+ mTokenLoan,
+ mFoneRate,
+ tokenOutRate,
+ } = await setupCeilDivTest({
+ fixture,
+ tokenKey: 'usdc6',
+ mTbillRate: 1.05,
+ isStable: true,
+ });
- await setRoundData({ mockedAggregator: mockedAggregatorMBasis }, 6);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 3);
- await mintToken(mFONE, owner, 10000);
- await mintToken(mBASIS, redemptionVaultWithMToken, 10000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 10000);
+ const expectedNative = computeExpectedTokenOut(
+ parseUnits(amount.toString()),
+ mFoneRate,
+ tokenOutRate,
+ TOKEN_DECIMALS['usdc6'],
+ 0,
+ true,
+ );
+ const thirtyPercent = expectedNative.mul(30).div(100);
+ await token.mint(redemptionVaultWithMToken.address, thirtyPercent);
- const mBasisBefore = await mBASIS.balanceOf(
+ const mTbillBefore = await mTokenLoan.balanceOf(
redemptionVaultWithMToken.address,
);
+ const userTokenBefore = await token.balanceOf(owner.address);
await redemptionVaultWithMToken
.connect(owner)
['redeemInstant(address,uint256,uint256)'](
token.address,
- parseUnits('100'),
+ parseUnits(amount.toString()),
0,
);
expect(
- await mBASIS.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mBasisBefore);
+ await mTokenLoan.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.lt(mTbillBefore);
+
+ const userTokenAfter = await token.balanceOf(owner.address);
+ expect(userTokenAfter.sub(userTokenBefore)).to.equal(
+ expectedNative,
+ );
});
- it('succeeds with remainder-producing swap (mBasis=1.05, mTBILL=1.03, stable)', async () => {
+ it('succeeds with 99% tokenOut present in outer vault (6-dec, mTokenLoan=1.05)', async () => {
const fixture = await loadFixture(defaultDeploy);
const {
token,
+ amount,
redemptionVaultWithMToken,
owner,
- mBASIS,
- mFONE,
- mockedAggregatorMBasis,
- mockedAggregatorMToken,
- } = await setupSwapperSwapPath(fixture, key);
+ mTokenLoan,
+ mFoneRate,
+ tokenOutRate,
+ } = await setupCeilDivTest({
+ fixture,
+ tokenKey: 'usdc6',
+ mTbillRate: 1.05,
+ isStable: true,
+ });
- await setRoundData(
- { mockedAggregator: mockedAggregatorMBasis },
- 1.05,
+ const expectedNative = computeExpectedTokenOut(
+ parseUnits(amount.toString()),
+ mFoneRate,
+ tokenOutRate,
+ TOKEN_DECIMALS['usdc6'],
+ 0,
+ true,
);
- await setRoundData(
- { mockedAggregator: mockedAggregatorMToken },
- 1.03,
+ const ninetyNinePercent = expectedNative.mul(99).div(100);
+ await token.mint(
+ redemptionVaultWithMToken.address,
+ ninetyNinePercent,
);
- await mintToken(mFONE, owner, 10000);
- await mintToken(mBASIS, redemptionVaultWithMToken, 10000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 10000);
- const mBasisBefore = await mBASIS.balanceOf(
+ const mTbillBefore = await mTokenLoan.balanceOf(
redemptionVaultWithMToken.address,
);
+ const userTokenBefore = await token.balanceOf(owner.address);
await redemptionVaultWithMToken
.connect(owner)
['redeemInstant(address,uint256,uint256)'](
token.address,
- parseUnits('100'),
+ parseUnits(amount.toString()),
0,
);
expect(
- await mBASIS.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mBasisBefore);
+ await mTokenLoan.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.lt(mTbillBefore);
+
+ const userTokenAfter = await token.balanceOf(owner.address);
+ expect(userTokenAfter.sub(userTokenBefore)).to.equal(
+ expectedNative,
+ );
});
+ });
- it('succeeds with high divergent rates (mBasis=100, mTBILL=7, stable)', async () => {
+ describe('mTBILL rate variation', () => {
+ it('succeeds with high mTBILL rate (mTBILL=3.5, mTokenLoan=1.05, 6-dec)', async () => {
const fixture = await loadFixture(defaultDeploy);
const {
token,
+ amount,
redemptionVaultWithMToken,
owner,
- mBASIS,
- mFONE,
- mockedAggregatorMBasis,
- mockedAggregatorMToken,
- } = await setupSwapperSwapPath(fixture, key);
+ mTokenLoan,
+ mTokenToUsdDataFeed,
+ mockedAggregatorMTokenLoan,
+ tokenOutRate,
+ } = await setupCeilDivTest({
+ fixture,
+ tokenKey: 'usdc6',
+ mTbillRate: 1.05,
+ isStable: true,
+ });
await setRoundData(
- { mockedAggregator: mockedAggregatorMBasis },
- 100,
+ { mockedAggregator: mockedAggregatorMTokenLoan },
+ 3.5,
);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 7);
- await mintToken(mFONE, owner, 10000);
- await mintToken(mBASIS, redemptionVaultWithMToken, 10000);
- await approveBase18(owner, mFONE, redemptionVaultWithMToken, 10000);
+ const mFoneRate = await mTokenToUsdDataFeed.getDataInBase18();
- const mBasisBefore = await mBASIS.balanceOf(
+ const mTbillBefore = await mTokenLoan.balanceOf(
redemptionVaultWithMToken.address,
);
+ const userTokenBefore = await token.balanceOf(owner.address);
await redemptionVaultWithMToken
.connect(owner)
['redeemInstant(address,uint256,uint256)'](
token.address,
- parseUnits('100'),
+ parseUnits(amount.toString()),
0,
);
expect(
- await mBASIS.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mBasisBefore);
+ await mTokenLoan.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.lt(mTbillBefore);
+
+ const expected = computeExpectedTokenOut(
+ parseUnits(amount.toString()),
+ mFoneRate,
+ tokenOutRate,
+ TOKEN_DECIMALS['usdc6'],
+ 0,
+ true,
+ );
+ const userTokenAfter = await token.balanceOf(owner.address);
+ expect(userTokenAfter.sub(userTokenBefore)).to.equal(expected);
});
- it('succeeds with near-boundary rates (mBasis=1.00000003, mTBILL=1.00000007, stable)', async () => {
+ it('succeeds with low mTBILL rate (mTBILL=0.5, mTokenLoan=1.05, 6-dec)', async () => {
const fixture = await loadFixture(defaultDeploy);
const {
token,
+ amount,
redemptionVaultWithMToken,
owner,
- mBASIS,
- mFONE,
- mockedAggregatorMBasis,
- mockedAggregatorMToken,
- } = await setupSwapperSwapPath(fixture, key);
+ mTokenLoan,
+ mTokenToUsdDataFeed,
+ mockedAggregatorMTokenLoan,
+ tokenOutRate,
+ } = await setupCeilDivTest({
+ fixture,
+ tokenKey: 'usdc6',
+ mTbillRate: 1.05,
+ isStable: true,
+ });
await setRoundData(
- { mockedAggregator: mockedAggregatorMBasis },
- 1.00000003,
- );
- await setRoundData(
- { mockedAggregator: mockedAggregatorMToken },
- 1.00000007,
- );
- await mintToken(mFONE, owner, 100000);
- await mintToken(mBASIS, redemptionVaultWithMToken, 100000);
- await approveBase18(
- owner,
- mFONE,
- redemptionVaultWithMToken,
- 100000,
+ { mockedAggregator: mockedAggregatorMTokenLoan },
+ 0.5,
);
+ const mFoneRate = await mTokenToUsdDataFeed.getDataInBase18();
- const mBasisBefore = await mBASIS.balanceOf(
+ const mTbillBefore = await mTokenLoan.balanceOf(
redemptionVaultWithMToken.address,
);
+ const userTokenBefore = await token.balanceOf(owner.address);
await redemptionVaultWithMToken
.connect(owner)
['redeemInstant(address,uint256,uint256)'](
token.address,
- parseUnits('10000'),
+ parseUnits(amount.toString()),
0,
);
expect(
- await mBASIS.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mBasisBefore);
- });
- });
- }
- });
-
- describe('with outer vault fee enabled', () => {
- it('succeeds with 1% fee, usdc6 (6-dec), mTBILL=1.05, stable', async () => {
- const fixture = await loadFixture(defaultDeploy);
- const { token, amount, redemptionVaultWithMToken, owner, mTBILL } =
- await setupCeilDivTest({
- fixture,
- tokenKey: 'usdc6',
- mTbillRate: 1.05,
- isStable: true,
- });
-
- await setInstantFeeTest(
- { vault: redemptionVaultWithMToken, owner },
- 100,
- );
-
- const mTbillBefore = await mTBILL.balanceOf(
- redemptionVaultWithMToken.address,
- );
- const userTokenBefore = await token.balanceOf(owner.address);
-
- await redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
- parseUnits(amount.toString()),
- 0,
- );
-
- expect(
- await mTBILL.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mTbillBefore);
- expect(await token.balanceOf(owner.address)).to.be.gt(userTokenBefore);
- });
-
- it('succeeds with 1% fee, usdc (8-dec), mTBILL=1.05, stable', async () => {
- const fixture = await loadFixture(defaultDeploy);
- const { token, amount, redemptionVaultWithMToken, owner, mTBILL } =
- await setupCeilDivTest({
- fixture,
- tokenKey: 'usdc',
- mTbillRate: 1.05,
- isStable: true,
- });
-
- await setInstantFeeTest(
- { vault: redemptionVaultWithMToken, owner },
- 100,
- );
-
- const mTbillBefore = await mTBILL.balanceOf(
- redemptionVaultWithMToken.address,
- );
- const userTokenBefore = await token.balanceOf(owner.address);
-
- await redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
- parseUnits(amount.toString()),
- 0,
- );
-
- expect(
- await mTBILL.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mTbillBefore);
- expect(await token.balanceOf(owner.address)).to.be.gt(userTokenBefore);
- });
-
- it('succeeds with 1% fee, usdt (18-dec), mTBILL=1.05, stable', async () => {
- const fixture = await loadFixture(defaultDeploy);
- const { token, amount, redemptionVaultWithMToken, owner, mTBILL } =
- await setupCeilDivTest({
- fixture,
- tokenKey: 'usdt',
- mTbillRate: 1.05,
- isStable: true,
- });
-
- await setInstantFeeTest(
- { vault: redemptionVaultWithMToken, owner },
- 100,
- );
-
- const mTbillBefore = await mTBILL.balanceOf(
- redemptionVaultWithMToken.address,
- );
- const userTokenBefore = await token.balanceOf(owner.address);
-
- await redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
- parseUnits(amount.toString()),
- 0,
- );
-
- expect(
- await mTBILL.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mTbillBefore);
- expect(await token.balanceOf(owner.address)).to.be.gt(userTokenBefore);
- });
-
- it('succeeds with 1% fee, usdc6 (6-dec), mTBILL=100, stable', async () => {
- const fixture = await loadFixture(defaultDeploy);
- const { token, amount, redemptionVaultWithMToken, owner, mTBILL } =
- await setupCeilDivTest({
- fixture,
- tokenKey: 'usdc6',
- mTbillRate: 100,
- isStable: true,
- });
-
- await setInstantFeeTest(
- { vault: redemptionVaultWithMToken, owner },
- 100,
- );
-
- const mTbillBefore = await mTBILL.balanceOf(
- redemptionVaultWithMToken.address,
- );
-
- await redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
- parseUnits(amount.toString()),
- 0,
- );
-
- expect(
- await mTBILL.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mTbillBefore);
- });
-
- it('succeeds with 1% fee, usdc (8-dec), non-stable mTBILL=1.05 tokenOut=1.03', async () => {
- const fixture = await loadFixture(defaultDeploy);
- const { token, amount, redemptionVaultWithMToken, owner, mTBILL } =
- await setupCeilDivTest({
- fixture,
- tokenKey: 'usdc',
- mTbillRate: 1.05,
- isStable: false,
- tokenOutRate: 1.03,
- });
-
- await setInstantFeeTest(
- { vault: redemptionVaultWithMToken, owner },
- 100,
- );
-
- const mTbillBefore = await mTBILL.balanceOf(
- redemptionVaultWithMToken.address,
- );
- const userTokenBefore = await token.balanceOf(owner.address);
-
- await redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
- parseUnits(amount.toString()),
- 0,
- );
-
- expect(
- await mTBILL.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mTbillBefore);
- expect(await token.balanceOf(owner.address)).to.be.gt(userTokenBefore);
- });
-
- it('succeeds with 1% fee, usdc6 (6-dec), near-boundary mTBILL=1.00000003', async () => {
- const fixture = await loadFixture(defaultDeploy);
- const { token, redemptionVaultWithMToken, owner, mTBILL } =
- await setupCeilDivTest({
- fixture,
- tokenKey: 'usdc6',
- mTbillRate: 1.00000003,
- isStable: true,
- redeemAmount: 10000,
- });
-
- await setInstantFeeTest(
- { vault: redemptionVaultWithMToken, owner },
- 100,
- );
-
- const mTbillBefore = await mTBILL.balanceOf(
- redemptionVaultWithMToken.address,
- );
-
- await redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
- parseUnits('10000'),
- 0,
- );
-
- expect(
- await mTBILL.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mTbillBefore);
- });
- });
-
- describe('minReceiveAmount assertions', () => {
- it('succeeds with exact minReceiveAmount (6-dec, mTBILL=1.05)', async () => {
- const fixture = await loadFixture(defaultDeploy);
- const {
- token,
- amount,
- redemptionVaultWithMToken,
- owner,
- mFoneRate,
- tokenOutRate,
- } = await setupCeilDivTest({
- fixture,
- tokenKey: 'usdc6',
- mTbillRate: 1.05,
- isStable: true,
- });
-
- const expectedNative = computeExpectedTokenOut(
- parseUnits(amount.toString()),
- mFoneRate,
- tokenOutRate,
- TOKEN_DECIMALS['usdc6'],
- 0,
- true,
- );
- const expectedBase18 = expectedNative.mul(
- BigNumber.from(10).pow(18 - TOKEN_DECIMALS['usdc6']),
- );
-
- const userTokenBefore = await token.balanceOf(owner.address);
-
- await redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
- parseUnits(amount.toString()),
- expectedBase18,
- );
-
- const userTokenAfter = await token.balanceOf(owner.address);
- expect(userTokenAfter.sub(userTokenBefore)).to.equal(expectedNative);
- });
-
- it('reverts when minReceiveAmount exceeds actual (6-dec, mTBILL=1.05)', async () => {
- const fixture = await loadFixture(defaultDeploy);
- const {
- token,
- amount,
- redemptionVaultWithMToken,
- owner,
- mFoneRate,
- tokenOutRate,
- } = await setupCeilDivTest({
- fixture,
- tokenKey: 'usdc6',
- mTbillRate: 1.05,
- isStable: true,
- });
+ await mTokenLoan.balanceOf(redemptionVaultWithMToken.address),
+ ).to.be.lt(mTbillBefore);
- const expectedNative = computeExpectedTokenOut(
- parseUnits(amount.toString()),
- mFoneRate,
- tokenOutRate,
- TOKEN_DECIMALS['usdc6'],
- 0,
- true,
- );
- const tooHigh = expectedNative
- .mul(BigNumber.from(10).pow(18 - TOKEN_DECIMALS['usdc6']))
- .add(1);
-
- await expect(
- redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
+ const expected = computeExpectedTokenOut(
parseUnits(amount.toString()),
- tooHigh,
- ),
- ).to.be.revertedWith('RVMT: minReceiveAmount > actual');
- });
- });
-
- describe('partial outer vault balance', () => {
- it('succeeds with 30% tokenOut present in outer vault (6-dec, mTBILL=1.05)', async () => {
- const fixture = await loadFixture(defaultDeploy);
- const {
- token,
- amount,
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFoneRate,
- tokenOutRate,
- } = await setupCeilDivTest({
- fixture,
- tokenKey: 'usdc6',
- mTbillRate: 1.05,
- isStable: true,
- });
-
- const expectedNative = computeExpectedTokenOut(
- parseUnits(amount.toString()),
- mFoneRate,
- tokenOutRate,
- TOKEN_DECIMALS['usdc6'],
- 0,
- true,
- );
- const thirtyPercent = expectedNative.mul(30).div(100);
- await token.mint(redemptionVaultWithMToken.address, thirtyPercent);
-
- const mTbillBefore = await mTBILL.balanceOf(
- redemptionVaultWithMToken.address,
- );
- const userTokenBefore = await token.balanceOf(owner.address);
-
- await redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
- parseUnits(amount.toString()),
- 0,
- );
-
- expect(
- await mTBILL.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mTbillBefore);
-
- const userTokenAfter = await token.balanceOf(owner.address);
- expect(userTokenAfter.sub(userTokenBefore)).to.equal(expectedNative);
- });
-
- it('succeeds with 99% tokenOut present in outer vault (6-dec, mTBILL=1.05)', async () => {
- const fixture = await loadFixture(defaultDeploy);
- const {
- token,
- amount,
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFoneRate,
- tokenOutRate,
- } = await setupCeilDivTest({
- fixture,
- tokenKey: 'usdc6',
- mTbillRate: 1.05,
- isStable: true,
- });
-
- const expectedNative = computeExpectedTokenOut(
- parseUnits(amount.toString()),
- mFoneRate,
- tokenOutRate,
- TOKEN_DECIMALS['usdc6'],
- 0,
- true,
- );
- const ninetyNinePercent = expectedNative.mul(99).div(100);
- await token.mint(redemptionVaultWithMToken.address, ninetyNinePercent);
-
- const mTbillBefore = await mTBILL.balanceOf(
- redemptionVaultWithMToken.address,
- );
- const userTokenBefore = await token.balanceOf(owner.address);
-
- await redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
- parseUnits(amount.toString()),
- 0,
- );
-
- expect(
- await mTBILL.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mTbillBefore);
-
- const userTokenAfter = await token.balanceOf(owner.address);
- expect(userTokenAfter.sub(userTokenBefore)).to.equal(expectedNative);
- });
- });
-
- describe('mFONE rate variation', () => {
- it('succeeds with high mFONE rate (mFONE=3.5, mTBILL=1.05, 6-dec)', async () => {
- const fixture = await loadFixture(defaultDeploy);
- const {
- token,
- amount,
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFoneToUsdDataFeed,
- mockedAggregatorMFone,
- tokenOutRate,
- } = await setupCeilDivTest({
- fixture,
- tokenKey: 'usdc6',
- mTbillRate: 1.05,
- isStable: true,
- });
-
- await setRoundData({ mockedAggregator: mockedAggregatorMFone }, 3.5);
- const mFoneRate = await mFoneToUsdDataFeed.getDataInBase18();
-
- const mTbillBefore = await mTBILL.balanceOf(
- redemptionVaultWithMToken.address,
- );
- const userTokenBefore = await token.balanceOf(owner.address);
-
- await redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
- parseUnits(amount.toString()),
- 0,
- );
-
- expect(
- await mTBILL.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mTbillBefore);
-
- const expected = computeExpectedTokenOut(
- parseUnits(amount.toString()),
- mFoneRate,
- tokenOutRate,
- TOKEN_DECIMALS['usdc6'],
- 0,
- true,
- );
- const userTokenAfter = await token.balanceOf(owner.address);
- expect(userTokenAfter.sub(userTokenBefore)).to.equal(expected);
- });
-
- it('succeeds with low mFONE rate (mFONE=0.5, mTBILL=1.05, 6-dec)', async () => {
- const fixture = await loadFixture(defaultDeploy);
- const {
- token,
- amount,
- redemptionVaultWithMToken,
- owner,
- mTBILL,
- mFoneToUsdDataFeed,
- mockedAggregatorMFone,
- tokenOutRate,
- } = await setupCeilDivTest({
- fixture,
- tokenKey: 'usdc6',
- mTbillRate: 1.05,
- isStable: true,
+ mFoneRate,
+ tokenOutRate,
+ TOKEN_DECIMALS['usdc6'],
+ 0,
+ true,
+ );
+ const userTokenAfter = await token.balanceOf(owner.address);
+ expect(userTokenAfter.sub(userTokenBefore)).to.equal(expected);
+ });
});
-
- await setRoundData({ mockedAggregator: mockedAggregatorMFone }, 0.5);
- const mFoneRate = await mFoneToUsdDataFeed.getDataInBase18();
-
- const mTbillBefore = await mTBILL.balanceOf(
- redemptionVaultWithMToken.address,
- );
- const userTokenBefore = await token.balanceOf(owner.address);
-
- await redemptionVaultWithMToken
- .connect(owner)
- ['redeemInstant(address,uint256,uint256)'](
- token.address,
- parseUnits(amount.toString()),
- 0,
- );
-
- expect(
- await mTBILL.balanceOf(redemptionVaultWithMToken.address),
- ).to.be.lt(mTbillBefore);
-
- const expected = computeExpectedTokenOut(
- parseUnits(amount.toString()),
- mFoneRate,
- tokenOutRate,
- TOKEN_DECIMALS['usdc6'],
- 0,
- true,
- );
- const userTokenAfter = await token.balanceOf(owner.address);
- expect(userTokenAfter.sub(userTokenBefore)).to.equal(expected);
});
});
- });
-});
+ },
+);
diff --git a/test/unit/RedemptionVaultWithMorpho.test.ts b/test/unit/RedemptionVaultWithMorpho.test.ts
index 1517a731..628c5ef4 100644
--- a/test/unit/RedemptionVaultWithMorpho.test.ts
+++ b/test/unit/RedemptionVaultWithMorpho.test.ts
@@ -1,2774 +1,1206 @@
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
+import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { expect } from 'chai';
import { constants } from 'ethers';
import { parseUnits } from 'ethers/lib/utils';
import { ethers } from 'hardhat';
+import {
+ baseInitParamsRv,
+ redemptionVaultSuits,
+} from './suits/redemption-vault.suits';
+
import { encodeFnSelector } from '../../helpers/utils';
import {
- ManageableVaultTester__factory,
+ RedemptionVaultWithMorpho__factory,
RedemptionVaultWithMorphoTest__factory,
} from '../../typechain-types';
-import { acErrors, blackList, greenList } from '../common/ac.helpers';
+import { acErrors } from '../common/ac.helpers';
import {
approveBase18,
mintToken,
pauseVaultFn,
+ validateImplementation,
} from '../common/common.helpers';
import { setRoundData } from '../common/data-feed.helpers';
import { defaultDeploy } from '../common/fixtures';
import {
addPaymentTokenTest,
setInstantFeeTest,
- setMinAmountTest,
- setInstantDailyLimitTest,
- addWaivedFeeAccountTest,
- removeWaivedFeeAccountTest,
- setVariabilityToleranceTest,
- removePaymentTokenTest,
- withdrawTest,
- changeTokenFeeTest,
- changeTokenAllowanceTest,
+ setWaivedFeeAccountTest,
} from '../common/manageable-vault.helpers';
import {
setMorphoVaultTest,
removeMorphoVaultTest,
} from '../common/redemption-vault-morpho.helpers';
import {
- approveRedeemRequestTest,
- redeemFiatRequestTest,
redeemInstantTest,
- redeemRequestTest,
- rejectRedeemRequestTest,
- safeApproveRedeemRequestTest,
- setFiatAdditionalFeeTest,
- setMinFiatRedeemAmountTest,
+ setPreferLoanLiquidityTest,
+ setLoanLpTest,
} from '../common/redemption-vault.helpers';
-import { sanctionUser } from '../common/with-sanctions-list.helpers';
-
-describe('RedemptionVaultWithMorpho', function () {
- it('deployment', async () => {
- const {
- redemptionVaultWithMorpho,
- morphoVaultMock,
- stableCoins,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- roles,
- } = await loadFixture(defaultDeploy);
-
- expect(await redemptionVaultWithMorpho.mToken()).eq(mTBILL.address);
-
- expect(await redemptionVaultWithMorpho.ONE_HUNDRED_PERCENT()).eq('10000');
-
- expect(await redemptionVaultWithMorpho.paused()).eq(false);
-
- expect(await redemptionVaultWithMorpho.tokensReceiver()).eq(
- tokensReceiver.address,
- );
- expect(await redemptionVaultWithMorpho.feeReceiver()).eq(
- feeReceiver.address,
- );
-
- expect(await redemptionVaultWithMorpho.minAmount()).eq(1000);
- expect(await redemptionVaultWithMorpho.minFiatRedeemAmount()).eq(1000);
-
- expect(await redemptionVaultWithMorpho.instantFee()).eq('100');
-
- expect(await redemptionVaultWithMorpho.instantDailyLimit()).eq(
- parseUnits('100000'),
- );
-
- expect(await redemptionVaultWithMorpho.mTokenDataFeed()).eq(
- mTokenToUsdDataFeed.address,
- );
- expect(await redemptionVaultWithMorpho.variationTolerance()).eq(1);
-
- expect(await redemptionVaultWithMorpho.vaultRole()).eq(
- roles.tokenRoles.mTBILL.redemptionVaultAdmin,
- );
-
- expect(await redemptionVaultWithMorpho.MANUAL_FULLFILMENT_TOKEN()).eq(
- ethers.constants.AddressZero,
- );
-
+import { initializeRvWithMorpho } from '../common/vault-initializer.helpers';
+
+redemptionVaultSuits(
+ 'RedemptionVaultWithMorpho',
+ defaultDeploy,
+ {
+ createNew: async (owner: SignerWithAddress) =>
+ new RedemptionVaultWithMorphoTest__factory(owner).deploy(),
+ key: 'redemptionVaultWithMorpho',
+ },
+ async (fixture) => {
+ const { redemptionVaultWithMorpho, stableCoins, morphoVaultMock } = fixture;
expect(
await redemptionVaultWithMorpho.morphoVaults(stableCoins.usdc.address),
).eq(morphoVaultMock.address);
- });
-
- it('failing deployment', async () => {
- const {
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- accessControl,
- mockedSanctionsList,
- owner,
- } = await loadFixture(defaultDeploy);
-
- const redemptionVaultWithMorpho =
- await new RedemptionVaultWithMorphoTest__factory(owner).deploy();
-
- await expect(
- redemptionVaultWithMorpho.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- {
- fiatAdditionalFee: 10000,
- fiatFlatFee: parseUnits('1'),
- minFiatRedeemAmount: parseUnits('100'),
- },
- constants.AddressZero,
- ),
- ).to.be.reverted;
- });
-
- describe('initialization', () => {
- it('should fail: call initialize() when already initialized', async () => {
- const { redemptionVaultWithMorpho } = await loadFixture(defaultDeploy);
-
- await expect(
- redemptionVaultWithMorpho.initialize(
- constants.AddressZero,
- {
- mToken: constants.AddressZero,
- mTokenDataFeed: constants.AddressZero,
- },
- {
- feeReceiver: constants.AddressZero,
- tokensReceiver: constants.AddressZero,
- },
- {
- instantFee: 0,
- instantDailyLimit: 0,
- },
- constants.AddressZero,
- 0,
- 0,
- {
- fiatAdditionalFee: 0,
- fiatFlatFee: 0,
- minFiatRedeemAmount: 0,
- },
- constants.AddressZero,
- ),
- ).revertedWith('Initializable: contract is already initialized');
- });
-
- it('should fail: call with initializing == false', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initializeWithoutInitializer(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- 1000,
- ),
- ).revertedWith('Initializable: contract is not initializing');
- });
- });
-
- describe('setMorphoVault()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- redemptionVaultWithMorpho,
- owner,
- regularAccounts,
- stableCoins,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await setMorphoVaultTest(
- { redemptionVault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc.address,
- morphoVaultMock.address,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: zero token address', async () => {
- const { redemptionVaultWithMorpho, owner, morphoVaultMock } =
- await loadFixture(defaultDeploy);
-
- await setMorphoVaultTest(
- { redemptionVault: redemptionVaultWithMorpho, owner },
- constants.AddressZero,
- morphoVaultMock.address,
- {
- revertMessage: 'zero address',
- },
- );
- });
-
- it('should fail: zero vault address', async () => {
- const { redemptionVaultWithMorpho, owner, stableCoins } =
- await loadFixture(defaultDeploy);
-
- await setMorphoVaultTest(
- { redemptionVault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc.address,
- constants.AddressZero,
- {
- revertMessage: 'zero address',
- },
- );
- });
-
- it('should fail: asset mismatch', async () => {
- const { redemptionVaultWithMorpho, owner, stableCoins, morphoVaultMock } =
- await loadFixture(defaultDeploy);
-
- await setMorphoVaultTest(
- { redemptionVault: redemptionVaultWithMorpho, owner },
- stableCoins.dai.address,
- morphoVaultMock.address,
- {
- revertMessage: 'RVM: asset mismatch',
- },
- );
- });
-
- it('call from address with vault admin role', async () => {
- const { redemptionVaultWithMorpho, owner, stableCoins, morphoVaultMock } =
- await loadFixture(defaultDeploy);
-
- await setMorphoVaultTest(
- { redemptionVault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc.address,
- morphoVaultMock.address,
- );
- });
- });
-
- describe('removeMorphoVault()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { redemptionVaultWithMorpho, owner, regularAccounts, stableCoins } =
- await loadFixture(defaultDeploy);
-
- await removeMorphoVaultTest(
- { redemptionVault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc.address,
- {
- from: regularAccounts[0],
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: vault not set', async () => {
- const { redemptionVaultWithMorpho, owner, stableCoins } =
- await loadFixture(defaultDeploy);
-
- await removeMorphoVaultTest(
- { redemptionVault: redemptionVaultWithMorpho, owner },
- stableCoins.dai.address,
- {
- revertMessage: 'RVM: vault not set',
- },
- );
- });
-
- it('call from address with vault admin role', async () => {
- const { redemptionVaultWithMorpho, owner, stableCoins } =
- await loadFixture(defaultDeploy);
-
- await removeMorphoVaultTest(
- { redemptionVault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc.address,
- );
- });
- });
-
- describe('setMinAmount()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { redemptionVaultWithMorpho, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await setMinAmountTest(
- { vault: redemptionVaultWithMorpho, owner: regularAccounts[0] },
- 1,
- { revertMessage: 'WMAC: hasnt role', from: regularAccounts[0] },
- );
- });
-
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithMorpho } = await loadFixture(
- defaultDeploy,
- );
- await setMinAmountTest({ vault: redemptionVaultWithMorpho, owner }, 1);
- });
- });
-
- describe('setMinFiatRedeemAmount()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { redemptionVaultWithMorpho, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await setMinFiatRedeemAmountTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner: regularAccounts[0],
- },
- 1,
- { revertMessage: 'WMAC: hasnt role', from: regularAccounts[0] },
- );
- });
-
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithMorpho } = await loadFixture(
- defaultDeploy,
- );
- await setMinFiatRedeemAmountTest(
- { redemptionVault: redemptionVaultWithMorpho, owner },
- 1,
- );
- });
- });
-
- describe('setFiatAdditionalFee()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { redemptionVaultWithMorpho, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await setFiatAdditionalFeeTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner: regularAccounts[0],
- },
- 1,
- { revertMessage: 'WMAC: hasnt role', from: regularAccounts[0] },
- );
- });
-
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithMorpho } = await loadFixture(
- defaultDeploy,
- );
- await setFiatAdditionalFeeTest(
- { redemptionVault: redemptionVaultWithMorpho, owner },
- 1,
- );
- });
- });
-
- describe('setInstantDailyLimit()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { redemptionVaultWithMorpho, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await setInstantDailyLimitTest(
- { vault: redemptionVaultWithMorpho, owner: regularAccounts[0] },
- 1,
- { revertMessage: 'WMAC: hasnt role', from: regularAccounts[0] },
- );
- });
-
- it('should fail: try to set 0 limit', async () => {
- const { owner, redemptionVaultWithMorpho } = await loadFixture(
- defaultDeploy,
- );
- await setInstantDailyLimitTest(
- { vault: redemptionVaultWithMorpho, owner },
- constants.Zero,
- { revertMessage: 'MV: limit zero' },
- );
- });
-
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithMorpho } = await loadFixture(
- defaultDeploy,
- );
- await setInstantDailyLimitTest(
- { vault: redemptionVaultWithMorpho, owner },
- 1,
- );
- });
- });
-
- describe('addPaymentToken()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- redemptionVaultWithMorpho,
- regularAccounts,
- dataFeed,
- stableCoins,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner: regularAccounts[0] },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- constants.MaxUint256,
- { revertMessage: 'WMAC: hasnt role', from: regularAccounts[0] },
- );
- });
-
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithMorpho, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- });
- });
-
- describe('removePaymentToken()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- regularAccounts,
- stableCoins,
- dataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await removePaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner: regularAccounts[0] },
- stableCoins.usdc,
- { revertMessage: 'WMAC: hasnt role', from: regularAccounts[0] },
- );
- });
-
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithMorpho, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await removePaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- );
- });
- });
-
- describe('addWaivedFeeAccount()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { redemptionVaultWithMorpho, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithMorpho, owner: regularAccounts[0] },
- regularAccounts[1].address,
- { revertMessage: 'WMAC: hasnt role', from: regularAccounts[0] },
- );
- });
-
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithMorpho, regularAccounts } =
- await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithMorpho, owner },
- regularAccounts[0].address,
- );
- });
- });
-
- describe('removeWaivedFeeAccount()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { owner, redemptionVaultWithMorpho, regularAccounts } =
- await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithMorpho, owner },
- regularAccounts[0].address,
- );
- await removeWaivedFeeAccountTest(
- { vault: redemptionVaultWithMorpho, owner: regularAccounts[0] },
- regularAccounts[0].address,
- { revertMessage: 'WMAC: hasnt role', from: regularAccounts[0] },
- );
- });
-
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithMorpho, regularAccounts } =
- await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithMorpho, owner },
- regularAccounts[0].address,
- );
- await removeWaivedFeeAccountTest(
- { vault: redemptionVaultWithMorpho, owner },
- regularAccounts[0].address,
- );
- });
- });
-
- describe('setFee()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { redemptionVaultWithMorpho, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await setInstantFeeTest(
- { vault: redemptionVaultWithMorpho, owner: regularAccounts[0] },
- 1,
- { revertMessage: 'WMAC: hasnt role', from: regularAccounts[0] },
- );
- });
-
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithMorpho } = await loadFixture(
- defaultDeploy,
- );
- await setInstantFeeTest({ vault: redemptionVaultWithMorpho, owner }, 1);
- });
- });
-
- describe('setVariabilityTolerance()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { redemptionVaultWithMorpho, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await setVariabilityToleranceTest(
- { vault: redemptionVaultWithMorpho, owner: regularAccounts[0] },
- 1,
- { revertMessage: 'WMAC: hasnt role', from: regularAccounts[0] },
- );
- });
-
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithMorpho } = await loadFixture(
- defaultDeploy,
- );
- await setVariabilityToleranceTest(
- { vault: redemptionVaultWithMorpho, owner },
- 1,
- );
- });
- });
-
- describe('withdrawToken()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const { redemptionVaultWithMorpho, stableCoins, regularAccounts } =
- await loadFixture(defaultDeploy);
- await withdrawTest(
- { vault: redemptionVaultWithMorpho, owner: regularAccounts[0] },
- stableCoins.usdc,
- 1,
- regularAccounts[0],
- { revertMessage: 'WMAC: hasnt role', from: regularAccounts[0] },
- );
- });
-
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithMorpho, stableCoins } =
- await loadFixture(defaultDeploy);
- await mintToken(stableCoins.usdc, redemptionVaultWithMorpho, 1);
- await withdrawTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- 1,
- owner,
- );
- });
- });
-
- describe('freeFromMinAmount()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const { redemptionVaultWithMorpho, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- redemptionVaultWithMorpho
- .connect(regularAccounts[0])
- .freeFromMinAmount(regularAccounts[0].address, true),
- ).revertedWith('WMAC: hasnt role');
- });
-
- it('should not fail', async () => {
- const { owner, redemptionVaultWithMorpho } = await loadFixture(
- defaultDeploy,
- );
-
- expect(
- await redemptionVaultWithMorpho.isFreeFromMinAmount(owner.address),
- ).eq(false);
- await redemptionVaultWithMorpho.freeFromMinAmount(owner.address, true);
- expect(
- await redemptionVaultWithMorpho.isFreeFromMinAmount(owner.address),
- ).eq(true);
- });
- });
-
- describe('changeTokenAllowance()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- regularAccounts,
- stableCoins,
- dataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: redemptionVaultWithMorpho, owner: regularAccounts[0] },
- stableCoins.usdc.address,
- 100,
- { revertMessage: 'WMAC: hasnt role', from: regularAccounts[0] },
- );
- });
-
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithMorpho, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc.address,
- 100,
- );
- });
- });
-
- describe('changeTokenFee()', () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- regularAccounts,
- stableCoins,
- dataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenFeeTest(
- { vault: redemptionVaultWithMorpho, owner: regularAccounts[0] },
- stableCoins.usdc.address,
- 100,
- { revertMessage: 'WMAC: hasnt role', from: regularAccounts[0] },
- );
- });
-
- it('call from address with vault admin role', async () => {
- const { owner, redemptionVaultWithMorpho, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenFeeTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc.address,
- 100,
- );
- });
- });
-
- describe('checkAndRedeemMorpho()', () => {
- it('should not withdraw from Morpho when contract has enough balance', async () => {
- const { redemptionVaultWithMorpho, stableCoins, morphoVaultMock } =
- await loadFixture(defaultDeploy);
-
- const usdcAmount = parseUnits('1000', 8);
- await stableCoins.usdc.mint(
- redemptionVaultWithMorpho.address,
- usdcAmount,
- );
-
- const balanceBefore = await stableCoins.usdc.balanceOf(
- redemptionVaultWithMorpho.address,
- );
- const sharesBefore = await morphoVaultMock.balanceOf(
- redemptionVaultWithMorpho.address,
- );
-
- await redemptionVaultWithMorpho.checkAndRedeemMorpho(
- stableCoins.usdc.address,
- parseUnits('500', 8),
- );
-
- const balanceAfter = await stableCoins.usdc.balanceOf(
- redemptionVaultWithMorpho.address,
- );
- const sharesAfter = await morphoVaultMock.balanceOf(
- redemptionVaultWithMorpho.address,
- );
- expect(balanceAfter).to.equal(balanceBefore);
- expect(sharesAfter).to.equal(sharesBefore);
- });
-
- it('should withdraw missing amount from Morpho', async () => {
- const { redemptionVaultWithMorpho, stableCoins, morphoVaultMock } =
- await loadFixture(defaultDeploy);
-
- // Vault has 500 USDC, needs 1000
- const initialUsdc = parseUnits('500', 8);
- await stableCoins.usdc.mint(
- redemptionVaultWithMorpho.address,
- initialUsdc,
- );
-
- // Vault has 600 Morpho shares (1:1 exchange rate by default)
- const sharesAmount = parseUnits('600', 8);
- await morphoVaultMock.mint(
- redemptionVaultWithMorpho.address,
- sharesAmount,
- );
-
- await redemptionVaultWithMorpho.checkAndRedeemMorpho(
- stableCoins.usdc.address,
- parseUnits('1000', 8),
- );
-
- // Vault should now have 1000 USDC (500 original + 500 withdrawn from Morpho)
- const usdcAfter = await stableCoins.usdc.balanceOf(
- redemptionVaultWithMorpho.address,
- );
- expect(usdcAfter).to.equal(parseUnits('1000', 8));
-
- // Share balance should decrease by 500 (1:1 rate)
- const sharesAfter = await morphoVaultMock.balanceOf(
- redemptionVaultWithMorpho.address,
- );
- expect(sharesAfter).to.equal(parseUnits('100', 8));
- });
-
- it('should revert when token not vault asset', async () => {
- const { redemptionVaultWithMorpho, stableCoins } = await loadFixture(
- defaultDeploy,
- );
-
- // DAI is not the Morpho vault's underlying asset (USDC is)
- await expect(
- redemptionVaultWithMorpho.checkAndRedeemMorpho(
- stableCoins.dai.address,
- parseUnits('1000', 9),
- ),
- ).to.be.revertedWith('RVM: no vault for token');
- });
-
- it('should revert when contract has insufficient shares', async () => {
- const { redemptionVaultWithMorpho, stableCoins, morphoVaultMock } =
- await loadFixture(defaultDeploy);
-
- // Vault has 200 USDC, needs 1000
- await stableCoins.usdc.mint(
- redemptionVaultWithMorpho.address,
- parseUnits('200', 8),
- );
-
- // Vault has only 300 shares (not enough for 800 missing at 1:1 rate)
- await morphoVaultMock.mint(
- redemptionVaultWithMorpho.address,
- parseUnits('300', 8),
- );
-
- await expect(
- redemptionVaultWithMorpho.checkAndRedeemMorpho(
- stableCoins.usdc.address,
- parseUnits('1000', 8),
- ),
- ).to.be.revertedWith('RVM: insufficient shares');
- });
-
- it('should revert when Morpho vault has insufficient underlying liquidity', async () => {
- const { redemptionVaultWithMorpho, stableCoins, morphoVaultMock } =
- await loadFixture(defaultDeploy);
-
- // Vault needs to withdraw from Morpho
- await stableCoins.usdc.mint(
- redemptionVaultWithMorpho.address,
- parseUnits('200', 8),
- );
-
- // Vault has enough shares
- await morphoVaultMock.mint(
- redemptionVaultWithMorpho.address,
- parseUnits('1000', 8),
- );
-
- // Drain the mock's USDC
- const mockBalance = await stableCoins.usdc.balanceOf(
- morphoVaultMock.address,
- );
- await morphoVaultMock.withdrawAdmin(
- stableCoins.usdc.address,
- (
- await ethers.getSigners()
- )[10].address,
- mockBalance,
- );
-
- await expect(
- redemptionVaultWithMorpho.checkAndRedeemMorpho(
- stableCoins.usdc.address,
- parseUnits('1000', 8),
- ),
- ).to.be.revertedWith('MorphoVaultMock: InsufficientLiquidity');
- });
-
- it('should withdraw correctly with non-1:1 exchange rate (shares worth more)', async () => {
- const { redemptionVaultWithMorpho, stableCoins, morphoVaultMock } =
- await loadFixture(defaultDeploy);
-
- // Set exchange rate: 1 share = 1.05 underlying (5% interest accrued)
- await morphoVaultMock.setExchangeRate(parseUnits('1.05', 18));
-
- // Vault has 200 USDC, needs 1000 → missing 800
- await stableCoins.usdc.mint(
- redemptionVaultWithMorpho.address,
- parseUnits('200', 8),
- );
-
- // At 1.05 rate, 800 assets needs ceil(800 / 1.05) ≈ 762 shares
- // Mint 800 shares (more than enough)
- await morphoVaultMock.mint(
- redemptionVaultWithMorpho.address,
- parseUnits('800', 8),
- );
-
- const sharesBefore = await morphoVaultMock.balanceOf(
- redemptionVaultWithMorpho.address,
- );
-
- await redemptionVaultWithMorpho.checkAndRedeemMorpho(
- stableCoins.usdc.address,
- parseUnits('1000', 8),
- );
-
- const usdcAfter = await stableCoins.usdc.balanceOf(
- redemptionVaultWithMorpho.address,
- );
- expect(usdcAfter).to.equal(parseUnits('1000', 8));
-
- const sharesAfter = await morphoVaultMock.balanceOf(
- redemptionVaultWithMorpho.address,
- );
- // Shares burned should be less than 800 because each share is worth 1.05
- expect(sharesAfter).to.be.gt(0);
- const sharesBurned = sharesBefore.sub(sharesAfter);
- expect(sharesBurned).to.be.lt(parseUnits('800', 8));
- });
-
- it('should revert with insufficient shares at non-1:1 exchange rate', async () => {
- const { redemptionVaultWithMorpho, stableCoins, morphoVaultMock } =
- await loadFixture(defaultDeploy);
-
- // Set exchange rate: 1 share = 0.95 underlying (loss scenario)
- await morphoVaultMock.setExchangeRate(parseUnits('0.95', 18));
-
- // Vault has 200 USDC, needs 1000 → missing 800
- await stableCoins.usdc.mint(
- redemptionVaultWithMorpho.address,
- parseUnits('200', 8),
- );
-
- // At 0.95 rate, 800 assets needs ceil(800 / 0.95) ≈ 843 shares
- // Mint only 800 shares (not enough at this rate)
- await morphoVaultMock.mint(
- redemptionVaultWithMorpho.address,
- parseUnits('800', 8),
- );
-
- await expect(
- redemptionVaultWithMorpho.checkAndRedeemMorpho(
- stableCoins.usdc.address,
- parseUnits('1000', 8),
- ),
- ).to.be.revertedWith('RVM: insufficient shares');
- });
- });
-
- describe('redeemInstant()', () => {
- it('should fail: when there is no token in vault', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 1,
- {
- revertMessage: 'MV: token not exists',
- },
- );
- });
-
- it('should fail: when trying to redeem 0 amount', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 0,
- {
- revertMessage: 'RV: invalid amount',
- },
- );
- });
-
- it('should fail: when function paused', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- redemptionVaultWithMorpho,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector(
- 'redeemInstant(address,uint256,uint256)',
- );
- await pauseVaultFn(redemptionVaultWithMorpho, selector);
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: call with insufficient allowance', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- revertMessage: 'ERC20: insufficient allowance',
- },
- );
- });
-
- it('should fail: call with insufficient balance', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, mTBILL, redemptionVaultWithMorpho, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- revertMessage: 'ERC20: burn amount exceeds balance',
- },
- );
- });
-
- it('should fail: dataFeed rate 0', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- dataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(
- owner,
- stableCoins.usdc,
- redemptionVaultWithMorpho,
- 10,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await mintToken(mTBILL, owner, 100_000);
- await setRoundData({ mockedAggregator }, 0);
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 1,
- {
- revertMessage: 'DF: feed is deprecated',
- },
- );
- });
-
- it('should fail: call for amount < minAmount', async () => {
- const {
- redemptionVaultWithMorpho,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mTBILL, owner, 100_000);
- await approveBase18(owner, mTBILL, redemptionVaultWithMorpho, 100_000);
-
- await setMinAmountTest(
- { vault: redemptionVaultWithMorpho, owner },
- 100_000,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 99_999,
- {
- revertMessage: 'RV: amount < min',
- },
- );
- });
-
- it('should fail: if exceeds token allowance', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- dataFeed,
- mockedAggregator,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(mTBILL, owner, 100_000);
- await changeTokenAllowanceTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc.address,
- 100,
- );
- await approveBase18(owner, mTBILL, redemptionVaultWithMorpho, 100_000);
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 99_999,
- {
- revertMessage: 'MV: exceed allowance',
- },
- );
- });
-
- it('should fail: if daily limit exceeded', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- dataFeed,
- mockedAggregator,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(mTBILL, owner, 100_000);
- await setInstantDailyLimitTest(
- { vault: redemptionVaultWithMorpho, owner },
- 1000,
- );
-
- await approveBase18(owner, mTBILL, redemptionVaultWithMorpho, 100_000);
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 99_999,
- {
- revertMessage: 'MV: exceed limit',
- },
- );
- });
-
- it('should fail: if some fee = 100%', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVaultWithMorpho, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 10000,
- true,
- );
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- revertMessage: 'RV: amountMTokenIn < fee',
- },
- );
- });
-
- it('should fail: greenlist enabled and user not in greenlist', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redemptionVaultWithMorpho.setGreenlistEnable(true);
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: user in blacklist', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: user in sanctions list', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- mockedSanctionsList,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- regularAccounts[0],
- );
-
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mTBILL,
- redemptionVaultWithMorpho,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- // ── Happy path tests ─────────────────────────────────────────────────
-
- it('redeem 100 mTBILL when vault has enough USDC (no Morpho needed)', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, redemptionVaultWithMorpho, 100000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVaultWithMorpho, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- const sharesBefore = await morphoVaultMock.balanceOf(
- redemptionVaultWithMorpho.address,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
-
- // Share balance should not change
- const sharesAfter = await morphoVaultMock.balanceOf(
- redemptionVaultWithMorpho.address,
- );
- expect(sharesAfter).to.equal(sharesBefore);
- });
-
- it('redeem 1000 mTBILL when vault has no USDC but has Morpho shares', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- // Mint shares to vault (enough for redemption at 1:1 rate)
- await morphoVaultMock.mint(
- redemptionVaultWithMorpho.address,
- parseUnits('9900', 8),
- );
- await mintToken(mTBILL, owner, 1000);
- await approveBase18(owner, mTBILL, redemptionVaultWithMorpho, 1000);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setInstantFeeTest({ vault: redemptionVaultWithMorpho, owner }, 0);
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
-
- const sharesBefore = await morphoVaultMock.balanceOf(
- redemptionVaultWithMorpho.address,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 1000,
- );
-
- const sharesAfter = await morphoVaultMock.balanceOf(
- redemptionVaultWithMorpho.address,
- );
-
- // Shares should decrease
- expect(sharesAfter).to.be.lt(sharesBefore);
- });
-
- it('redeem 1000 mTBILL when vault has 100 USDC and sufficient shares (partial Morpho)', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- // Vault has 100 USDC + shares
- await mintToken(stableCoins.usdc, redemptionVaultWithMorpho, 100);
- await morphoVaultMock.mint(
- redemptionVaultWithMorpho.address,
- parseUnits('9900', 8),
- );
- await mintToken(mTBILL, owner, 1000);
- await approveBase18(owner, mTBILL, redemptionVaultWithMorpho, 1000);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 1000,
- );
- });
-
- it('redeem 1000 mTBILL with different prices (stable 1.03$, mToken 5$) and partial Morpho', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, redemptionVaultWithMorpho, 100);
- await morphoVaultMock.mint(
- redemptionVaultWithMorpho.address,
- parseUnits('15000', 8),
- );
- await mintToken(mTBILL, owner, 1000);
- await approveBase18(owner, mTBILL, redemptionVaultWithMorpho, 1000);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 100,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await redemptionVaultWithMorpho.freeFromMinAmount(owner.address, true);
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 1000,
- );
- });
-
- it('redeem 1000 mTBILL with waived fee and Morpho withdrawal', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, redemptionVaultWithMorpho, 100);
- await morphoVaultMock.mint(
- redemptionVaultWithMorpho.address,
- parseUnits('15000', 8),
- );
- await mintToken(mTBILL, owner, 1000);
- await approveBase18(owner, mTBILL, redemptionVaultWithMorpho, 1000);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 100,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithMorpho, owner },
- owner.address,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- waivedFee: true,
- },
- stableCoins.usdc,
- 1000,
- );
- });
-
- it('should fail: insufficient shares during redeemInstant', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- // Vault has no USDC and only 10 shares (not enough for 1000 mTBILL redemption)
- await morphoVaultMock.mint(
- redemptionVaultWithMorpho.address,
- parseUnits('10', 8),
- );
- await mintToken(mTBILL, owner, 1000);
- await approveBase18(owner, mTBILL, redemptionVaultWithMorpho, 1000);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setInstantFeeTest({ vault: redemptionVaultWithMorpho, owner }, 0);
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
-
- await expect(
- redemptionVaultWithMorpho['redeemInstant(address,uint256,uint256)'](
- stableCoins.usdc.address,
- parseUnits('1000'),
- 0,
- ),
- ).to.be.revertedWith('RVM: insufficient shares');
- });
-
- it('should fail: Morpho vault has insufficient liquidity during redeemInstant', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- morphoVaultMock,
- } = await loadFixture(defaultDeploy);
-
- // Vault has shares but mock has no liquidity
- await morphoVaultMock.mint(
- redemptionVaultWithMorpho.address,
- parseUnits('10000', 8),
- );
- await mintToken(mTBILL, owner, 100000);
- await approveBase18(owner, mTBILL, redemptionVaultWithMorpho, 100000);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setInstantFeeTest({ vault: redemptionVaultWithMorpho, owner }, 0);
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
-
- // Drain the mock
- const mockBalance = await stableCoins.usdc.balanceOf(
- morphoVaultMock.address,
- );
- await morphoVaultMock.withdrawAdmin(
- stableCoins.usdc.address,
- (
- await ethers.getSigners()
- )[10].address,
- mockBalance,
- );
-
- await expect(
- redemptionVaultWithMorpho['redeemInstant(address,uint256,uint256)'](
- stableCoins.usdc.address,
- parseUnits('1000'),
- 0,
- ),
- ).to.be.revertedWith('MorphoVaultMock: InsufficientLiquidity');
- });
-
- // ── Custom recipient tests ───────────────────────────────────────────
-
- it('redeem 100 mTBILL (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, redemptionVaultWithMorpho, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mTBILL,
- redemptionVaultWithMorpho,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem 100 mTBILL when other fn overload is paused (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- redemptionVaultWithMorpho,
- encodeFnSelector('redeemInstant(address,uint256,uint256)'),
- );
- await mintToken(stableCoins.usdc, redemptionVaultWithMorpho, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mTBILL,
- redemptionVaultWithMorpho,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem 100 mTBILL when other fn overload is paused', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- redemptionVaultWithMorpho,
- encodeFnSelector('redeemInstant(address,uint256,uint256,address)'),
- );
- await mintToken(stableCoins.usdc, redemptionVaultWithMorpho, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mTBILL,
- redemptionVaultWithMorpho,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('should fail: when function with custom recipient is paused', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- customRecipient,
- } = await loadFixture(defaultDeploy);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- redemptionVaultWithMorpho,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector(
- 'redeemInstant(address,uint256,uint256,address)',
- );
- await pauseVaultFn(redemptionVaultWithMorpho, selector);
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: greenlist enabled and recipient not in greenlist (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await redemptionVaultWithMorpho.setGreenlistEnable(true);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: recipient in blacklist (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- customRecipient,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: recipient in sanctions list (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- customRecipient,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
- });
-
- describe('redeemRequest()', () => {
- it('should fail: when there is no token in vault', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'MV: token not exists',
- },
- );
- });
-
- it('should fail: when trying to redeem 0 amount', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 0,
- {
- revertMessage: 'RV: invalid amount',
- },
- );
- });
-
- it('should fail: when function paused', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- redemptionVaultWithMorpho,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector('redeemRequest(address,uint256)');
- await pauseVaultFn(redemptionVaultWithMorpho, selector);
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: call with insufficient balance', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, mTBILL, redemptionVaultWithMorpho, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'ERC20: transfer amount exceeds balance',
- },
- );
- });
-
- it('redeem request: happy path', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVaultWithMorpho, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithMorpho, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
- });
- });
-
- describe('redeemFiatRequest()', () => {
- it('should fail: when function paused', async () => {
- const {
- owner,
- redemptionVaultWithMorpho,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
- await mintToken(mTBILL, regularAccounts[0], 100000);
- await approveBase18(
- regularAccounts[0],
- mTBILL,
- redemptionVaultWithMorpho,
- 100000,
- );
- const selector = encodeFnSelector('redeemFiatRequest(uint256)');
- await pauseVaultFn(redemptionVaultWithMorpho, selector);
- await redeemFiatRequestTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 100000,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('redeem fiat request: happy path', async () => {
- const { owner, redemptionVaultWithMorpho, mTBILL, mTokenToUsdDataFeed } =
- await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100000);
- await approveBase18(owner, mTBILL, redemptionVaultWithMorpho, 100000);
- await redeemFiatRequestTest(
- {
- redemptionVault: redemptionVaultWithMorpho,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 100000,
- );
- });
- });
-
- describe('approveRequest()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- redemptionVaultWithMorpho: redemptionVault,
- regularAccounts,
- mTokenToUsdDataFeed,
- mTBILL,
- } = await loadFixture(defaultDeploy);
- await approveRedeemRequestTest(
- {
- redemptionVault,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 1,
- parseUnits('1'),
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- redemptionVaultWithMorpho: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await approveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 1,
- parseUnits('1'),
- {
- revertMessage: 'RV: request not exist',
- },
- );
- });
-
- it('should fail: request already processed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithMorpho: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await approveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('1'),
- );
- await approveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('1'),
- { revertMessage: 'RV: request not pending' },
- );
- });
-
- it('approve request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithMorpho: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await approveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('1'),
- );
- });
- });
-
- describe('approveRequest() with fiat', async () => {
- it('approve request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithMorpho: redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 100,
- );
- const requestId = 0;
- await changeTokenAllowanceTest(
- { vault: redemptionVault, owner },
- constants.AddressZero,
- parseUnits('100'),
- );
-
- await approveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('1'),
- );
- });
- });
-
- describe('safeApproveRequest()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- redemptionVaultWithMorpho: redemptionVault,
- regularAccounts,
- mTokenToUsdDataFeed,
- mTBILL,
- } = await loadFixture(defaultDeploy);
- await safeApproveRedeemRequestTest(
- {
- redemptionVault,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 1,
- parseUnits('1'),
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- redemptionVaultWithMorpho: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 1,
- parseUnits('1'),
- {
- revertMessage: 'RV: request not exist',
- },
- );
- });
-
- it('should fail: if new rate greater then variabilityTolerance', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithMorpho: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('6'),
- { revertMessage: 'MV: exceed price diviation' },
- );
- });
-
- it('should fail: request already processed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithMorpho: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('5.000001'),
- );
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('5.00001'),
- { revertMessage: 'RV: request not pending' },
- );
- });
-
- it('safe approve request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithMorpho: redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('5.000001'),
- );
- });
- });
-
- describe('rejectRequest()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- redemptionVaultWithMorpho: redemptionVault,
- regularAccounts,
- mTokenToUsdDataFeed,
- mTBILL,
- } = await loadFixture(defaultDeploy);
- await rejectRedeemRequestTest(
- {
- redemptionVault,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- redemptionVaultWithMorpho: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await rejectRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 1,
- {
- revertMessage: 'RV: request not exist',
- },
- );
- });
-
- it('should fail: request already processed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithMorpho: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await rejectRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- );
- await rejectRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- { revertMessage: 'RV: request not pending' },
- );
- });
-
- it('reject request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithMorpho: redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await rejectRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- );
- });
- });
-});
+ await validateImplementation(RedemptionVaultWithMorpho__factory);
+ },
+ {
+ deployUninitialized: (fixture) =>
+ new RedemptionVaultWithMorphoTest__factory(fixture.owner).deploy(),
+ initialize: async (fixture, params, opt) => {
+ await initializeRvWithMorpho(
+ { ...baseInitParamsRv(fixture), ...params },
+ opt?.contract,
+ opt,
+ );
+ },
+ },
+ async (defaultDeploy) => {
+ describe('RedemptionVaultWithMorpho', () => {
+ describe('setMorphoVault()', () => {
+ it('should fail: call from address without vault admin role', async () => {
+ const {
+ redemptionVaultWithMorpho,
+ owner,
+ regularAccounts,
+ stableCoins,
+ morphoVaultMock,
+ } = await loadFixture(defaultDeploy);
+
+ await setMorphoVaultTest(
+ { redemptionVault: redemptionVaultWithMorpho, owner },
+ stableCoins.usdc.address,
+ morphoVaultMock.address,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ },
+ );
+ });
+
+ it('should fail: zero token address', async () => {
+ const { redemptionVaultWithMorpho, owner, morphoVaultMock } =
+ await loadFixture(defaultDeploy);
+
+ await setMorphoVaultTest(
+ { redemptionVault: redemptionVaultWithMorpho, owner },
+ constants.AddressZero,
+ morphoVaultMock.address,
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidAddress',
+ },
+ },
+ );
+ });
+
+ it('should fail: zero vault address', async () => {
+ const { redemptionVaultWithMorpho, owner, stableCoins } =
+ await loadFixture(defaultDeploy);
+
+ await setMorphoVaultTest(
+ { redemptionVault: redemptionVaultWithMorpho, owner },
+ stableCoins.usdc.address,
+ constants.AddressZero,
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidAddress',
+ },
+ },
+ );
+ });
+
+ it('should fail: asset mismatch', async () => {
+ const {
+ redemptionVaultWithMorpho,
+ owner,
+ stableCoins,
+ morphoVaultMock,
+ } = await loadFixture(defaultDeploy);
+
+ await setMorphoVaultTest(
+ { redemptionVault: redemptionVaultWithMorpho, owner },
+ stableCoins.dai.address,
+ morphoVaultMock.address,
+ {
+ revertCustomError: {
+ customErrorName: 'AssetMismatch',
+ },
+ },
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const {
+ redemptionVaultWithMorpho,
+ owner,
+ stableCoins,
+ morphoVaultMock,
+ pauseManager,
+ } = await loadFixture(defaultDeploy);
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVaultWithMorpho,
+ encodeFnSelector('setMorphoVault(address,address)'),
+ );
+
+ await setMorphoVaultTest(
+ { redemptionVault: redemptionVaultWithMorpho, owner },
+ stableCoins.usdc.address,
+ morphoVaultMock.address,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('call from address with vault admin role', async () => {
+ const {
+ redemptionVaultWithMorpho,
+ owner,
+ stableCoins,
+ morphoVaultMock,
+ } = await loadFixture(defaultDeploy);
+
+ await setMorphoVaultTest(
+ { redemptionVault: redemptionVaultWithMorpho, owner },
+ stableCoins.usdc.address,
+ morphoVaultMock.address,
+ );
+ });
+ });
+
+ describe('removeMorphoVault()', () => {
+ it('should fail: call from address without vault admin role', async () => {
+ const {
+ redemptionVaultWithMorpho,
+ owner,
+ regularAccounts,
+ stableCoins,
+ } = await loadFixture(defaultDeploy);
+
+ await removeMorphoVaultTest(
+ { redemptionVault: redemptionVaultWithMorpho, owner },
+ stableCoins.usdc.address,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
+ },
+ );
+ });
+
+ it('should fail: vault not set', async () => {
+ const { redemptionVaultWithMorpho, owner, stableCoins } =
+ await loadFixture(defaultDeploy);
+
+ await removeMorphoVaultTest(
+ { redemptionVault: redemptionVaultWithMorpho, owner },
+ stableCoins.dai.address,
+ {
+ revertCustomError: {
+ customErrorName: 'VaultNotSet',
+ },
+ },
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const {
+ redemptionVaultWithMorpho,
+ owner,
+ stableCoins,
+ pauseManager,
+ } = await loadFixture(defaultDeploy);
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVaultWithMorpho,
+ encodeFnSelector('removeMorphoVault(address)'),
+ );
+
+ await removeMorphoVaultTest(
+ { redemptionVault: redemptionVaultWithMorpho, owner },
+ stableCoins.usdc.address,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('call from address with vault admin role', async () => {
+ const { redemptionVaultWithMorpho, owner, stableCoins } =
+ await loadFixture(defaultDeploy);
+
+ await removeMorphoVaultTest(
+ { redemptionVault: redemptionVaultWithMorpho, owner },
+ stableCoins.usdc.address,
+ );
+ });
+ });
+
+ describe('checkAndRedeemMorpho()', () => {
+ it('should not redeem when contract has enough balance', async () => {
+ const { redemptionVaultWithMorpho, stableCoins, morphoVaultMock } =
+ await loadFixture(defaultDeploy);
+
+ const usdcAmount = parseUnits('1000', 8);
+ await stableCoins.usdc.mint(
+ redemptionVaultWithMorpho.address,
+ usdcAmount,
+ );
+
+ await morphoVaultMock.mint(
+ redemptionVaultWithMorpho.address,
+ parseUnits('1000', 8),
+ );
+
+ await expect(
+ redemptionVaultWithMorpho.checkAndRedeemMorpho(
+ stableCoins.usdc.address,
+ parseUnits('500', 8),
+ ),
+ ).not.reverted;
+
+ expect(
+ await stableCoins.usdc.balanceOf(redemptionVaultWithMorpho.address),
+ ).to.be.eq(parseUnits('1000', 8));
+ expect(
+ await morphoVaultMock.balanceOf(redemptionVaultWithMorpho.address),
+ ).to.be.eq(parseUnits('1000', 8));
+ });
+
+ it('should withdraw missing amount from Morpho', async () => {
+ const { redemptionVaultWithMorpho, stableCoins, morphoVaultMock } =
+ await loadFixture(defaultDeploy);
+
+ // Vault has 500 USDC, needs 1000
+ const initialUsdc = parseUnits('500', 8);
+ await stableCoins.usdc.mint(
+ redemptionVaultWithMorpho.address,
+ initialUsdc,
+ );
+
+ // Vault has 600 Morpho shares (1:1 exchange rate by default)
+ const sharesAmount = parseUnits('600', 8);
+ await morphoVaultMock.mint(
+ redemptionVaultWithMorpho.address,
+ sharesAmount,
+ );
+
+ await redemptionVaultWithMorpho.checkAndRedeemMorpho(
+ stableCoins.usdc.address,
+ parseUnits('1000', 8),
+ );
+
+ // Vault should now have 1000 USDC (500 original + 500 withdrawn from Morpho)
+ const usdcAfter = await stableCoins.usdc.balanceOf(
+ redemptionVaultWithMorpho.address,
+ );
+ expect(usdcAfter).to.equal(parseUnits('1000', 8));
+
+ // Share balance should decrease by 500 (1:1 rate)
+ const sharesAfter = await morphoVaultMock.balanceOf(
+ redemptionVaultWithMorpho.address,
+ );
+ expect(sharesAfter).to.equal(parseUnits('100', 8));
+ });
+
+ it('should withdraw missing amount from Morpho when preferLoanLiquidity=true', async () => {
+ const {
+ redemptionVaultWithMorpho,
+ stableCoins,
+ morphoVaultMock,
+ owner,
+ } = await loadFixture(defaultDeploy);
+
+ // Vault has 500 USDC, needs 1000
+ const initialUsdc = parseUnits('500', 8);
+ await stableCoins.usdc.mint(
+ redemptionVaultWithMorpho.address,
+ initialUsdc,
+ );
+
+ // Vault has 600 Morpho shares (1:1 exchange rate by default)
+ const sharesAmount = parseUnits('600', 8);
+ await morphoVaultMock.mint(
+ redemptionVaultWithMorpho.address,
+ sharesAmount,
+ );
+
+ await setPreferLoanLiquidityTest(
+ { redemptionVault: redemptionVaultWithMorpho, owner },
+ true,
+ );
+
+ await redemptionVaultWithMorpho.checkAndRedeemMorpho(
+ stableCoins.usdc.address,
+ parseUnits('1000', 8),
+ );
+
+ // Vault should now have 1000 USDC (500 original + 500 withdrawn from Morpho)
+ const usdcAfter = await stableCoins.usdc.balanceOf(
+ redemptionVaultWithMorpho.address,
+ );
+ expect(usdcAfter).to.equal(parseUnits('1000', 8));
+
+ // Share balance should decrease by 500 (1:1 rate)
+ const sharesAfter = await morphoVaultMock.balanceOf(
+ redemptionVaultWithMorpho.address,
+ );
+ expect(sharesAfter).to.equal(parseUnits('100', 8));
+ });
+
+ it('should revert when Morpho vault has insufficient underlying liquidity', async () => {
+ const { redemptionVaultWithMorpho, stableCoins, morphoVaultMock } =
+ await loadFixture(defaultDeploy);
+
+ // Vault needs to withdraw from Morpho
+ await stableCoins.usdc.mint(
+ redemptionVaultWithMorpho.address,
+ parseUnits('200', 8),
+ );
+
+ // Vault has enough shares
+ await morphoVaultMock.mint(
+ redemptionVaultWithMorpho.address,
+ parseUnits('1000', 8),
+ );
+
+ // Drain the mock's USDC
+ const mockBalance = await stableCoins.usdc.balanceOf(
+ morphoVaultMock.address,
+ );
+ await morphoVaultMock.withdrawAdmin(
+ stableCoins.usdc.address,
+ (
+ await ethers.getSigners()
+ )[10].address,
+ mockBalance,
+ );
+
+ await expect(
+ redemptionVaultWithMorpho.checkAndRedeemMorpho(
+ stableCoins.usdc.address,
+ parseUnits('1000', 8),
+ ),
+ ).to.be.revertedWith('MorphoVaultMock: InsufficientLiquidity');
+ });
+
+ it('should withdraw correctly with non-1:1 exchange rate (shares worth more)', async () => {
+ const { redemptionVaultWithMorpho, stableCoins, morphoVaultMock } =
+ await loadFixture(defaultDeploy);
+
+ // Set exchange rate: 1 share = 1.05 underlying (5% interest accrued)
+ await morphoVaultMock.setExchangeRate(parseUnits('1.05', 18));
+
+ // Vault has 200 USDC, needs 1000 → missing 800
+ await stableCoins.usdc.mint(
+ redemptionVaultWithMorpho.address,
+ parseUnits('200', 8),
+ );
+
+ // At 1.05 rate, 800 assets needs ceil(800 / 1.05) ≈ 762 shares
+ // Mint 800 shares (more than enough)
+ await morphoVaultMock.mint(
+ redemptionVaultWithMorpho.address,
+ parseUnits('800', 8),
+ );
+
+ const sharesBefore = await morphoVaultMock.balanceOf(
+ redemptionVaultWithMorpho.address,
+ );
+
+ await redemptionVaultWithMorpho.checkAndRedeemMorpho(
+ stableCoins.usdc.address,
+ parseUnits('1000', 8),
+ );
+
+ const usdcAfter = await stableCoins.usdc.balanceOf(
+ redemptionVaultWithMorpho.address,
+ );
+ expect(usdcAfter).to.equal(parseUnits('1000', 8));
+
+ const sharesAfter = await morphoVaultMock.balanceOf(
+ redemptionVaultWithMorpho.address,
+ );
+ // Shares burned should be less than 800 because each share is worth 1.05
+ expect(sharesAfter).to.be.gt(0);
+ const sharesBurned = sharesBefore.sub(sharesAfter);
+ expect(sharesBurned).to.be.lt(parseUnits('800', 8));
+ });
+
+ it('shouldnt revert with insufficient shares at non-1:1 exchange rate', async () => {
+ const { redemptionVaultWithMorpho, stableCoins, morphoVaultMock } =
+ await loadFixture(defaultDeploy);
+
+ // Set exchange rate: 1 share = 0.95 underlying (loss scenario)
+ await morphoVaultMock.setExchangeRate(parseUnits('0.95', 18));
+
+ // Vault has 200 USDC, needs 1000 → missing 800
+ await stableCoins.usdc.mint(
+ redemptionVaultWithMorpho.address,
+ parseUnits('200', 8),
+ );
+
+ // At 0.95 rate, 800 assets needs ceil(800 / 0.95) ≈ 843 shares
+ // Mint only 800 shares (not enough at this rate)
+ await morphoVaultMock.mint(
+ redemptionVaultWithMorpho.address,
+ parseUnits('800', 8),
+ );
+
+ await expect(
+ redemptionVaultWithMorpho.checkAndRedeemMorpho(
+ stableCoins.usdc.address,
+ parseUnits('1000', 8),
+ ),
+ ).to.not.be.reverted;
+ });
+ });
+
+ describe('redeemInstant()', () => {
+ describe('preferLoanLiquidity=true', () => {
+ it('redeem 100 mTBILL when vault has enough USDC (no Morpho needed)', async () => {
+ const {
+ owner,
+ redemptionVaultWithMorpho,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ morphoVaultMock,
+ } = await loadFixture(defaultDeploy);
+
+ await mintToken(
+ stableCoins.usdc,
+ redemptionVaultWithMorpho,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVaultWithMorpho, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMorpho, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setPreferLoanLiquidityTest(
+ { redemptionVault: redemptionVaultWithMorpho, owner },
+ true,
+ );
+
+ const sharesBefore = await morphoVaultMock.balanceOf(
+ redemptionVaultWithMorpho.address,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithMorpho,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ stableCoins.usdc,
+ 100,
+ );
+
+ const sharesAfter = await morphoVaultMock.balanceOf(
+ redemptionVaultWithMorpho.address,
+ );
+ expect(sharesAfter).to.equal(sharesBefore);
+ });
+
+ it('when vault has no USDC but has Morpho shares and LP liquidity, LP liquidity should be used first', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithMorpho,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ morphoVaultMock,
+ loanLp,
+ mTokenLoan,
+ redemptionVaultLoanSwapper,
+ } = await loadFixture(defaultDeploy);
+
+ await morphoVaultMock.mint(
+ redemptionVaultWithMorpho.address,
+ parseUnits('9900', 8),
+ );
+ await mintToken(
+ stableCoins.usdc,
+ redemptionVaultLoanSwapper,
+ 100000,
+ );
+ await mintToken(mTokenLoan, loanLp, 100000);
+ await approveBase18(
+ loanLp,
+ mTokenLoan,
+ redemptionVaultWithMorpho,
+ 100000,
+ );
+
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithMorpho, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMorpho, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setInstantFeeTest(
+ { vault: redemptionVaultWithMorpho, owner },
+ 0,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setPreferLoanLiquidityTest(
+ { redemptionVault: redemptionVaultWithMorpho, owner },
+ true,
+ );
+
+ const sharesBefore = await morphoVaultMock.balanceOf(
+ redemptionVaultWithMorpho.address,
+ );
+ const loanLpBalanceBefore = await mTokenLoan.balanceOf(
+ loanLp.address,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithMorpho,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ additionalLiquidity: async () => {
+ return await morphoVaultMock.balanceOf(
+ redemptionVaultWithMorpho.address,
+ );
+ },
+ },
+ stableCoins.usdc,
+ 1000,
+ );
+
+ const sharesAfter = await morphoVaultMock.balanceOf(
+ redemptionVaultWithMorpho.address,
+ );
+ const loanLpBalanceAfter = await mTokenLoan.balanceOf(
+ loanLp.address,
+ );
+
+ expect(sharesAfter).to.equal(sharesBefore);
+ expect(loanLpBalanceAfter).to.be.lt(loanLpBalanceBefore);
+ });
+
+ it('redeem 1000 mTBILL when vault has no USDC but has Morpho shares', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithMorpho,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ morphoVaultMock,
+ } = await loadFixture(defaultDeploy);
+
+ await morphoVaultMock.mint(
+ redemptionVaultWithMorpho.address,
+ parseUnits('9900', 8),
+ );
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithMorpho, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMorpho, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setInstantFeeTest(
+ { vault: redemptionVaultWithMorpho, owner },
+ 0,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setPreferLoanLiquidityTest(
+ { redemptionVault: redemptionVaultWithMorpho, owner },
+ true,
+ );
+
+ const sharesBefore = await morphoVaultMock.balanceOf(
+ redemptionVaultWithMorpho.address,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithMorpho,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ additionalLiquidity: async () => {
+ return await morphoVaultMock.balanceOf(
+ redemptionVaultWithMorpho.address,
+ );
+ },
+ },
+ stableCoins.usdc,
+ 1000,
+ );
+
+ const sharesAfter = await morphoVaultMock.balanceOf(
+ redemptionVaultWithMorpho.address,
+ );
+ expect(sharesAfter).to.be.lt(sharesBefore);
+ });
+
+ it('when vault partially has USDC, partially has Morpho shares and partially has LP liquidity, so all the liquidity should be used', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithMorpho,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ morphoVaultMock,
+ loanLp,
+ mTokenLoan,
+ redemptionVaultLoanSwapper,
+ } = await loadFixture(defaultDeploy);
+
+ await morphoVaultMock.mint(
+ redemptionVaultWithMorpho.address,
+ parseUnits('300', 8),
+ );
+ await mintToken(stableCoins.usdc, redemptionVaultLoanSwapper, 300);
+ await mintToken(stableCoins.usdc, redemptionVaultWithMorpho, 400);
+ await mintToken(mTokenLoan, loanLp, 300);
+ await approveBase18(
+ loanLp,
+ mTokenLoan,
+ redemptionVaultWithMorpho,
+ 300,
+ );
+
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithMorpho, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMorpho, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setInstantFeeTest(
+ { vault: redemptionVaultWithMorpho, owner },
+ 0,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setPreferLoanLiquidityTest(
+ { redemptionVault: redemptionVaultWithMorpho, owner },
+ true,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithMorpho,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ additionalLiquidity: async () => {
+ return await morphoVaultMock.balanceOf(
+ redemptionVaultWithMorpho.address,
+ );
+ },
+ },
+ stableCoins.usdc,
+ 1000,
+ );
+
+ const sharesAfter = await morphoVaultMock.balanceOf(
+ redemptionVaultWithMorpho.address,
+ );
+ expect(sharesAfter).to.be.eq(0);
+ expect(
+ await stableCoins.usdc.balanceOf(
+ redemptionVaultWithMorpho.address,
+ ),
+ ).to.be.eq(0);
+ expect(
+ await stableCoins.usdc.balanceOf(
+ redemptionVaultLoanSwapper.address,
+ ),
+ ).to.be.eq(0);
+ expect(await mTokenLoan.balanceOf(loanLp.address)).to.be.eq(0);
+ });
+ });
+
+ it('redeem 100 mTBILL when vault has enough USDC (no Morpho needed)', async () => {
+ const {
+ owner,
+ redemptionVaultWithMorpho,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ morphoVaultMock,
+ } = await loadFixture(defaultDeploy);
+
+ await mintToken(stableCoins.usdc, redemptionVaultWithMorpho, 100000);
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVaultWithMorpho, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMorpho, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ const sharesBefore = await morphoVaultMock.balanceOf(
+ redemptionVaultWithMorpho.address,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithMorpho,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ stableCoins.usdc,
+ 100,
+ );
+
+ // Share balance should not change
+ const sharesAfter = await morphoVaultMock.balanceOf(
+ redemptionVaultWithMorpho.address,
+ );
+ expect(sharesAfter).to.equal(sharesBefore);
+ });
+
+ it('redeem 1000 mTBILL when vault has no USDC but has Morpho shares', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithMorpho,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ morphoVaultMock,
+ } = await loadFixture(defaultDeploy);
+
+ // Mint shares to vault (enough for redemption at 1:1 rate)
+ await morphoVaultMock.mint(
+ redemptionVaultWithMorpho.address,
+ parseUnits('9900', 8),
+ );
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithMorpho, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMorpho, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setInstantFeeTest(
+ { vault: redemptionVaultWithMorpho, owner },
+ 0,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+
+ const sharesBefore = await morphoVaultMock.balanceOf(
+ redemptionVaultWithMorpho.address,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithMorpho,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ additionalLiquidity: async () => {
+ return await morphoVaultMock.balanceOf(
+ redemptionVaultWithMorpho.address,
+ );
+ },
+ },
+ stableCoins.usdc,
+ 1000,
+ );
+
+ const sharesAfter = await morphoVaultMock.balanceOf(
+ redemptionVaultWithMorpho.address,
+ );
+
+ // Shares should decrease
+ expect(sharesAfter).to.be.lt(sharesBefore);
+ });
+
+ it('redeem 1000 mTBILL when vault has 100 USDC and sufficient shares (partial Morpho)', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithMorpho,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ morphoVaultMock,
+ } = await loadFixture(defaultDeploy);
+
+ // Vault has 100 USDC + shares
+ await mintToken(stableCoins.usdc, redemptionVaultWithMorpho, 100);
+ await morphoVaultMock.mint(
+ redemptionVaultWithMorpho.address,
+ parseUnits('9900', 8),
+ );
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithMorpho, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMorpho, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithMorpho,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ additionalLiquidity: async () => {
+ return await morphoVaultMock.balanceOf(
+ redemptionVaultWithMorpho.address,
+ );
+ },
+ },
+ stableCoins.usdc,
+ 1000,
+ );
+ });
+
+ it('redeem 1000 mTBILL when vault has 100 USDC and insufficient shares (partial Morpho, partial loan lp)', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithMorpho,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ morphoVaultMock,
+ redemptionVaultLoanSwapper,
+ loanLp,
+ mTokenLoan,
+ } = await loadFixture(defaultDeploy);
+
+ await mintToken(mTokenLoan, loanLp, 200);
+ await approveBase18(
+ loanLp,
+ mTokenLoan,
+ redemptionVaultWithMorpho,
+ 200,
+ );
+ await mintToken(stableCoins.usdc, redemptionVaultLoanSwapper, 200);
+
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ // Vault has 100 USDC + shares
+ await mintToken(stableCoins.usdc, redemptionVaultWithMorpho, 100);
+ await morphoVaultMock.mint(
+ redemptionVaultWithMorpho.address,
+ parseUnits('700', 8),
+ );
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithMorpho, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMorpho, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithMorpho,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ additionalLiquidity: async () => {
+ return await morphoVaultMock.balanceOf(
+ redemptionVaultWithMorpho.address,
+ );
+ },
+ },
+ stableCoins.usdc,
+ 1000,
+ );
+ });
+
+ it('redeem 1000 mTBILL with different prices (stable 1.03$, mToken 5$) and partial Morpho', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithMorpho,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ morphoVaultMock,
+ } = await loadFixture(defaultDeploy);
+
+ await mintToken(stableCoins.usdc, redemptionVaultWithMorpho, 100);
+ await morphoVaultMock.mint(
+ redemptionVaultWithMorpho.address,
+ parseUnits('15000', 8),
+ );
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithMorpho, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMorpho, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 100,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await redemptionVaultWithMorpho.freeFromMinAmount(
+ owner.address,
+ true,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithMorpho,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ additionalLiquidity: async () => {
+ return await morphoVaultMock.balanceOf(
+ redemptionVaultWithMorpho.address,
+ );
+ },
+ },
+ stableCoins.usdc,
+ 1000,
+ );
+ });
+
+ it('redeem 1000 mTBILL with waived fee and Morpho withdrawal', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithMorpho,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ morphoVaultMock,
+ } = await loadFixture(defaultDeploy);
+
+ await mintToken(stableCoins.usdc, redemptionVaultWithMorpho, 100);
+ await morphoVaultMock.mint(
+ redemptionVaultWithMorpho.address,
+ parseUnits('15000', 8),
+ );
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithMorpho, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMorpho, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 100,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await setWaivedFeeAccountTest(
+ { vault: redemptionVaultWithMorpho, owner },
+ owner.address,
+ true,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithMorpho,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ waivedFee: true,
+ additionalLiquidity: async () => {
+ return await morphoVaultMock.balanceOf(
+ redemptionVaultWithMorpho.address,
+ );
+ },
+ },
+ stableCoins.usdc,
+ 1000,
+ );
+ });
+
+ it('should fail: insufficient shares so it fallback to loan lp flow', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithMorpho,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ morphoVaultMock,
+ } = await loadFixture(defaultDeploy);
+
+ await setLoanLpTest(
+ { redemptionVault: redemptionVaultWithMorpho, owner },
+ ethers.constants.AddressZero,
+ );
+ // Vault has no USDC and only 10 shares (not enough for 1000 mTBILL redemption)
+ await morphoVaultMock.mint(
+ redemptionVaultWithMorpho.address,
+ parseUnits('10', 8),
+ );
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithMorpho, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMorpho, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setInstantFeeTest(
+ { vault: redemptionVaultWithMorpho, owner },
+ 0,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+
+ await expect(
+ redemptionVaultWithMorpho['redeemInstant(address,uint256,uint256)'](
+ stableCoins.usdc.address,
+ parseUnits('1000'),
+ 0,
+ ),
+ ).to.be.revertedWith('ERC20: transfer amount exceeds balance');
+ });
+
+ it('should fail: Morpho vault has insufficient liquidity during redeemInstant', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithMorpho,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ morphoVaultMock,
+ } = await loadFixture(defaultDeploy);
+
+ // Vault has shares but mock has no liquidity
+ await morphoVaultMock.mint(
+ redemptionVaultWithMorpho.address,
+ parseUnits('10000', 8),
+ );
+ await mintToken(mTBILL, owner, 100000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithMorpho, 100000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithMorpho, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setInstantFeeTest(
+ { vault: redemptionVaultWithMorpho, owner },
+ 0,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+
+ // Drain the mock
+ const mockBalance = await stableCoins.usdc.balanceOf(
+ morphoVaultMock.address,
+ );
+ await morphoVaultMock.withdrawAdmin(
+ stableCoins.usdc.address,
+ (
+ await ethers.getSigners()
+ )[10].address,
+ mockBalance,
+ );
+
+ await expect(
+ redemptionVaultWithMorpho['redeemInstant(address,uint256,uint256)'](
+ stableCoins.usdc.address,
+ parseUnits('1000'),
+ 0,
+ ),
+ ).to.be.revertedWith('ERC20: transfer amount exceeds balance');
+ });
+ });
+ });
+ },
+);
diff --git a/test/unit/RedemptionVaultWithSwapper.test.ts b/test/unit/RedemptionVaultWithSwapper.test.ts
deleted file mode 100644
index f284be69..00000000
--- a/test/unit/RedemptionVaultWithSwapper.test.ts
+++ /dev/null
@@ -1,1475 +0,0 @@
-import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
-import { expect } from 'chai';
-import { constants } from 'ethers';
-import { parseUnits } from 'ethers/lib/utils';
-
-import { encodeFnSelector } from '../../helpers/utils';
-import { RedemptionVaultWithSwapperTest__factory } from '../../typechain-types';
-import { acErrors, blackList, greenList } from '../common/ac.helpers';
-import {
- approveBase18,
- mintToken,
- pauseVaultFn,
-} from '../common/common.helpers';
-import { setRoundData } from '../common/data-feed.helpers';
-import { defaultDeploy } from '../common/fixtures';
-import {
- addPaymentTokenTest,
- changeTokenAllowanceTest,
- setInstantFeeTest,
- setInstantDailyLimitTest,
- setMinAmountTest,
- changeTokenFeeTest,
-} from '../common/manageable-vault.helpers';
-import {
- redeemInstantWithSwapperTest,
- setLiquidityProviderTest,
- setSwapperVaultTest,
-} from '../common/redemption-vault-swapper.helpers';
-import { sanctionUser } from '../common/with-sanctions-list.helpers';
-
-describe('MBasisRedemptionVaultWithSwapper', () => {
- describe('deployment', () => {
- it('should fail: cal; initialize() when already initialized', async () => {
- const { redemptionVaultWithSwapper } = await loadFixture(defaultDeploy);
-
- await expect(
- redemptionVaultWithSwapper[
- 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,(uint256,uint256,uint256),address,address,address)'
- ](
- constants.AddressZero,
- {
- mToken: constants.AddressZero,
- mTokenDataFeed: constants.AddressZero,
- },
- {
- feeReceiver: constants.AddressZero,
- tokensReceiver: constants.AddressZero,
- },
- {
- instantFee: 0,
- instantDailyLimit: 0,
- },
- constants.AddressZero,
- 0,
- 0,
- {
- fiatAdditionalFee: 0,
- fiatFlatFee: 0,
- minFiatRedeemAmount: 0,
- },
- constants.AddressZero,
- constants.AddressZero,
- constants.AddressZero,
- ),
- ).revertedWith('Initializable: contract is already initialized');
- });
-
- it('should fail: incorrect initialize parameters', async () => {
- const {
- accessControl,
- tokensReceiver,
- feeReceiver,
- mBASIS,
- mBasisToUsdDataFeed,
- mockedSanctionsList,
- owner,
- requestRedeemer,
- liquidityProvider,
- redemptionVault,
- } = await loadFixture(defaultDeploy);
-
- const redemptionVaultWithSwapper =
- await new RedemptionVaultWithSwapperTest__factory(owner).deploy();
-
- await expect(
- redemptionVaultWithSwapper[
- 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,(uint256,uint256,uint256),address,address,address)'
- ](
- accessControl.address,
- {
- mToken: mBASIS.address,
- mTokenDataFeed: mBasisToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- 1000,
- {
- fiatAdditionalFee: 100,
- fiatFlatFee: parseUnits('1'),
- minFiatRedeemAmount: 1000,
- },
- requestRedeemer.address,
- constants.AddressZero,
- liquidityProvider.address,
- ),
- ).to.be.reverted;
-
- await expect(
- redemptionVaultWithSwapper[
- 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,(uint256,uint256,uint256),address,address,address)'
- ](
- accessControl.address,
- {
- mToken: mBASIS.address,
- mTokenDataFeed: mBasisToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- 1000,
- {
- fiatAdditionalFee: 100,
- fiatFlatFee: parseUnits('1'),
- minFiatRedeemAmount: 1000,
- },
- requestRedeemer.address,
- redemptionVault.address,
- constants.AddressZero,
- ),
- ).to.be.reverted;
- });
- });
-
- describe('setLiquidityProvider()', () => {
- it('should fail: call from address without M_BASIS_REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithSwapper, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await setLiquidityProviderTest(
- { vault: redemptionVaultWithSwapper, owner },
- constants.AddressZero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
-
- it('should fail: if provider address zero', async () => {
- const { redemptionVaultWithSwapper, owner } = await loadFixture(
- defaultDeploy,
- );
- await setLiquidityProviderTest(
- { vault: redemptionVaultWithSwapper, owner },
- constants.AddressZero,
- { revertMessage: 'zero address' },
- );
- });
-
- it('should fail: if provider address equal current provider address', async () => {
- const { redemptionVaultWithSwapper, liquidityProvider, owner } =
- await loadFixture(defaultDeploy);
- await setLiquidityProviderTest(
- { vault: redemptionVaultWithSwapper, owner },
- liquidityProvider.address,
- { revertMessage: 'MRVS: already provider' },
- );
- });
-
- it('call from address with M_BASIS_REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithSwapper, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await setLiquidityProviderTest(
- { vault: redemptionVaultWithSwapper, owner },
- regularAccounts[0].address,
- );
- });
- });
-
- describe('setSwapperVault()', () => {
- it('should fail: call from address without M_BASIS_REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithSwapper, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await setSwapperVaultTest(
- { vault: redemptionVaultWithSwapper, owner },
- constants.AddressZero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
-
- it('should fail: if provider address zero', async () => {
- const { redemptionVaultWithSwapper, owner } = await loadFixture(
- defaultDeploy,
- );
- await setSwapperVaultTest(
- { vault: redemptionVaultWithSwapper, owner },
- constants.AddressZero,
- { revertMessage: 'zero address' },
- );
- });
-
- it('should fail: if provider address equal current provider address', async () => {
- const { redemptionVaultWithSwapper, redemptionVault, owner } =
- await loadFixture(defaultDeploy);
- await setSwapperVaultTest(
- { vault: redemptionVaultWithSwapper, owner },
- redemptionVault.address,
- { revertMessage: 'MRVS: already provider' },
- );
- });
-
- it('call from address with M_BASIS_REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithSwapper, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await setSwapperVaultTest(
- { vault: redemptionVaultWithSwapper, owner },
- regularAccounts[0].address,
- );
- });
- });
- describe('redeemInstant()', () => {
- it('should fail: when there is no token in vault', async () => {
- const {
- owner,
- redemptionVaultWithSwapper,
- stableCoins,
- mTBILL,
- mBASIS,
- mTokenToUsdDataFeed,
- mBasisToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mBASIS,
- mBasisToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'MV: token not exists',
- },
- );
- });
-
- it('should fail: when trying to redeem 0 amount', async () => {
- const {
- owner,
- redemptionVaultWithSwapper,
- stableCoins,
- mTBILL,
- mBASIS,
- mTokenToUsdDataFeed,
- mBasisToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithSwapper, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mBASIS,
- mBasisToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 0,
- {
- revertMessage: 'RV: invalid amount',
- },
- );
- });
-
- it('should fail: when function paused', async () => {
- const {
- owner,
- redemptionVaultWithSwapper,
- stableCoins,
- mTBILL,
- mBASIS,
- mTokenToUsdDataFeed,
- mBasisToUsdDataFeed,
- dataFeed,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mBASIS, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- redemptionVaultWithSwapper,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithSwapper, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector(
- 'redeemInstant(address,uint256,uint256)',
- );
- await pauseVaultFn(redemptionVaultWithSwapper, selector);
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mBASIS,
- mBasisToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: call with insufficient allowance', async () => {
- const {
- owner,
- redemptionVaultWithSwapper,
- stableCoins,
- mTBILL,
- mBASIS,
- mTokenToUsdDataFeed,
- mBasisToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mBASIS, owner, 100);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithSwapper, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mBASIS,
- mBasisToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'ERC20: insufficient allowance',
- },
- );
- });
-
- it('should fail: call with insufficient balance', async () => {
- const {
- owner,
- redemptionVaultWithSwapper,
- stableCoins,
- mTBILL,
- mBASIS,
- mTokenToUsdDataFeed,
- mBasisToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, mBASIS, redemptionVaultWithSwapper, 15);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithSwapper, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mBASIS,
- mBasisToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'ERC20: transfer amount exceeds balance',
- },
- );
-
- await mintToken(stableCoins.dai, redemptionVaultWithSwapper, 100);
- await mintToken(mBASIS, owner, 10);
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mBASIS,
- mBasisToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 15,
- {
- revertMessage: 'ERC20: burn amount exceeds balance',
- },
- );
- });
-
- it('should fail: dataFeed rate 0 ', async () => {
- const {
- owner,
- redemptionVaultWithSwapper,
- stableCoins,
- mTBILL,
- mBASIS,
- mTokenToUsdDataFeed,
- mBasisToUsdDataFeed,
- dataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- mockedAggregatorMBasis,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mBASIS, owner, 100_000);
- await approveBase18(owner, mBASIS, redemptionVaultWithSwapper, 100_000);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithSwapper, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 0);
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mBASIS,
- mBasisToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 10,
- {
- revertMessage: 'DF: feed is deprecated',
- },
- );
-
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 0);
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mBASIS,
- mBasisToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 10,
- {
- revertMessage: 'DF: feed is deprecated',
- },
- );
-
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMBasis }, 0);
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mBASIS,
- mBasisToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 10,
- {
- revertMessage: 'DF: feed is deprecated',
- },
- );
- });
-
- it('should fail: if min receive amount greater then actual', async () => {
- const {
- owner,
- redemptionVaultWithSwapper,
- stableCoins,
- mTBILL,
- mBASIS,
- mTokenToUsdDataFeed,
- mBasisToUsdDataFeed,
- dataFeed,
- mockedAggregator,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithSwapper, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mBASIS, owner, 100_000);
- await approveBase18(owner, mBASIS, redemptionVaultWithSwapper, 100_000);
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mBASIS,
- mBasisToUsdDataFeed,
- mTokenToUsdDataFeed,
- minAmount: parseUnits('10000'),
- },
- stableCoins.dai,
- 999,
- {
- revertMessage: 'RVS: minReceiveAmount > actual',
- },
- );
- });
-
- it('should fail: call for amount < minAmount', async () => {
- const {
- owner,
- redemptionVaultWithSwapper,
- stableCoins,
- mTBILL,
- mBASIS,
- mTokenToUsdDataFeed,
- mBasisToUsdDataFeed,
- dataFeed,
- mockedAggregator,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithSwapper, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mBASIS, owner, 100_000);
- await approveBase18(owner, mBASIS, redemptionVaultWithSwapper, 100_000);
-
- await setMinAmountTest(
- { vault: redemptionVaultWithSwapper, owner },
- 100_000,
- );
-
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mBASIS,
- mBasisToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'RV: amount < min',
- },
- );
- });
-
- it('should fail: if exceed allowance of deposit by token', async () => {
- const {
- owner,
- redemptionVaultWithSwapper,
- stableCoins,
- mTBILL,
- mBASIS,
- mTokenToUsdDataFeed,
- mBasisToUsdDataFeed,
- dataFeed,
- mockedAggregator,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithSwapper, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mBASIS, owner, 100_000);
- await mintToken(stableCoins.dai, redemptionVaultWithSwapper, 1_000_000);
- await approveBase18(owner, mBASIS, redemptionVaultWithSwapper, 100_000);
-
- await changeTokenAllowanceTest(
- { vault: redemptionVaultWithSwapper, owner },
- stableCoins.dai.address,
- 100,
- );
-
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mBASIS,
- mBasisToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'MV: exceed allowance',
- },
- );
- });
-
- it('should fail: if redeem daily limit exceeded', async () => {
- const {
- owner,
- redemptionVaultWithSwapper,
- stableCoins,
- mTBILL,
- mBASIS,
- mTokenToUsdDataFeed,
- mBasisToUsdDataFeed,
- dataFeed,
- mockedAggregator,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithSwapper, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mBASIS, owner, 100_000);
- await mintToken(stableCoins.dai, redemptionVaultWithSwapper, 1_000_000);
- await approveBase18(owner, mBASIS, redemptionVaultWithSwapper, 100_000);
-
- await setInstantDailyLimitTest(
- { vault: redemptionVaultWithSwapper, owner },
- 1000,
- );
-
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mBASIS,
- mBasisToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'MV: exceed limit',
- },
- );
- });
-
- it('should fail: if some fee = 100%', async () => {
- const {
- owner,
- redemptionVaultWithSwapper,
- stableCoins,
- mTBILL,
- mBASIS,
- mTokenToUsdDataFeed,
- mBasisToUsdDataFeed,
- dataFeed,
- mockedAggregator,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithSwapper, owner },
- stableCoins.dai,
- dataFeed.address,
- 10000,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mBASIS, owner, 100_000);
- await mintToken(stableCoins.dai, redemptionVaultWithSwapper, 1_000_000);
- await approveBase18(owner, mBASIS, redemptionVaultWithSwapper, 100_000);
-
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mBASIS,
- mBasisToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'RV: amountMTokenIn < fee',
- },
- );
- changeTokenFeeTest(
- { vault: redemptionVaultWithSwapper, owner },
- stableCoins.dai.address,
- 0,
- );
- await setInstantFeeTest(
- { vault: redemptionVaultWithSwapper, owner },
- 10000,
- );
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mBASIS,
- mBasisToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'RV: amountMTokenIn < fee',
- },
- );
- });
-
- it('should fail: greenlist enabled and user not in greenlist ', async () => {
- const {
- owner,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- redemptionVaultWithSwapper,
- mBASIS,
- mBasisToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redemptionVaultWithSwapper.setGreenlistEnable(true);
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mBASIS,
- mBasisToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: user in blacklist ', async () => {
- const {
- owner,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- redemptionVaultWithSwapper,
- mBASIS,
- mBasisToUsdDataFeed,
- regularAccounts,
- blackListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- regularAccounts[0],
- );
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mBASIS,
- mBasisToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: user in sanctions list', async () => {
- const {
- owner,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- redemptionVaultWithSwapper,
- mBASIS,
- mBasisToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- regularAccounts[0],
- );
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mBASIS,
- mBasisToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('should fail: when function with custom recipient is paused', async () => {
- const {
- owner,
- redemptionVaultWithSwapper,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- customRecipient,
- mBasisToUsdDataFeed,
- mBASIS,
- } = await loadFixture(defaultDeploy);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- redemptionVaultWithSwapper,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithSwapper, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector(
- 'redeemInstant(address,uint256,uint256,address)',
- );
- await pauseVaultFn(redemptionVaultWithSwapper, selector);
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- mBASIS,
- mBasisToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: greenlist enabled and recipient not in greenlist (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithSwapper,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- customRecipient,
- mBasisToUsdDataFeed,
- mBASIS,
- } = await loadFixture(defaultDeploy);
-
- await redemptionVaultWithSwapper.setGreenlistEnable(true);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- mBasisToUsdDataFeed,
- mBASIS,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: recipient in blacklist (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithSwapper,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- customRecipient,
- mBasisToUsdDataFeed,
- mBASIS,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- customRecipient,
- );
-
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- mBasisToUsdDataFeed,
- mBASIS,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: recipient in sanctions list (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithSwapper,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- customRecipient,
- mBasisToUsdDataFeed,
- mBASIS,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- customRecipient,
- );
-
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- mBasisToUsdDataFeed,
- mBASIS,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('should fail: user try to instant redeem fiat', async () => {
- const {
- owner,
- redemptionVaultWithSwapper,
- stableCoins,
- mTBILL,
- mBASIS,
- mTokenToUsdDataFeed,
- mBasisToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mBASIS, owner, 100_000);
- await mintToken(stableCoins.dai, redemptionVaultWithSwapper, 1_000_000);
- await approveBase18(owner, mBASIS, redemptionVaultWithSwapper, 100_000);
-
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mBASIS,
- mBasisToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- await redemptionVaultWithSwapper.MANUAL_FULLFILMENT_TOKEN(),
- 99_999,
- {
- revertMessage: 'MV: token not exists',
- },
- );
- });
-
- it('should fail: liquidity provider do not have mTBILL to swap', async () => {
- const {
- owner,
- redemptionVaultWithSwapper,
- stableCoins,
- mTBILL,
- mBASIS,
- mTokenToUsdDataFeed,
- mBasisToUsdDataFeed,
- dataFeed,
- redemptionVault,
- liquidityProvider,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mBASIS, owner, 100_000);
- await mintToken(stableCoins.dai, redemptionVaultWithSwapper, 10);
- await approveBase18(owner, mBASIS, redemptionVaultWithSwapper, 100_000);
- await approveBase18(
- liquidityProvider,
- mTBILL,
- redemptionVaultWithSwapper,
- 100_000,
- );
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithSwapper, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
-
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
-
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mBASIS,
- mBasisToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 200,
- {
- revertMessage: 'ERC20: transfer amount exceeds balance',
- },
- );
- });
-
- it('redeem 100 mBASIS, when contract have enough DAI and all fees are 0', async () => {
- const {
- owner,
- redemptionVaultWithSwapper,
- stableCoins,
- mTBILL,
- mBASIS,
- mTokenToUsdDataFeed,
- mBasisToUsdDataFeed,
- dataFeed,
- mockedAggregator,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithSwapper, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mBASIS, owner, 100_000);
- await mintToken(stableCoins.dai, redemptionVaultWithSwapper, 1_000_000);
- await approveBase18(owner, mBASIS, redemptionVaultWithSwapper, 100_000);
-
- await setInstantFeeTest(
- {
- vault: redemptionVaultWithSwapper,
- owner,
- },
- 0,
- );
-
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mBASIS,
- mBasisToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- });
-
- it('redeem 100 mBASIS, when contract have enough DAI', async () => {
- const {
- owner,
- redemptionVaultWithSwapper,
- stableCoins,
- mTBILL,
- mBASIS,
- mTokenToUsdDataFeed,
- mBasisToUsdDataFeed,
- dataFeed,
- mockedAggregator,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithSwapper, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mBASIS, owner, 100_000);
- await mintToken(stableCoins.dai, redemptionVaultWithSwapper, 1_000_000);
- await approveBase18(owner, mBASIS, redemptionVaultWithSwapper, 100_000);
-
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mBASIS,
- mBasisToUsdDataFeed,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- });
-
- it('redeem 100 mBASIS, when contract do not have enough DAI and need to use mTBILL vault', async () => {
- const {
- owner,
- redemptionVaultWithSwapper,
- stableCoins,
- mTBILL,
- mBASIS,
- mTokenToUsdDataFeed,
- mBasisToUsdDataFeed,
- dataFeed,
- redemptionVault,
- liquidityProvider,
- } = await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithSwapper, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
-
- await mintToken(mBASIS, owner, 100_000);
- await mintToken(mTBILL, liquidityProvider, 100_000);
- await mintToken(stableCoins.dai, redemptionVaultWithSwapper, 10);
- await mintToken(stableCoins.dai, redemptionVault, 1_000_000);
- await approveBase18(owner, mBASIS, redemptionVaultWithSwapper, 100_000);
- await approveBase18(
- liquidityProvider,
- mTBILL,
- redemptionVaultWithSwapper,
- 1000000,
- );
-
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mBASIS,
- mBasisToUsdDataFeed,
- mTokenToUsdDataFeed,
- swap: true,
- },
- stableCoins.dai,
- 100,
- );
- });
- });
-
- it('redeem 100 mTBILL (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithSwapper,
- stableCoins,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customRecipient,
- mBasisToUsdDataFeed,
- mBASIS,
- mTBILL,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVaultWithSwapper, 100000);
- await mintToken(mBASIS, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mBASIS,
- redemptionVaultWithSwapper,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithSwapper, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- mBasisToUsdDataFeed,
- mBASIS,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem 100 mTBILL when recipient == msg.sender (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithSwapper,
- stableCoins,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- mBasisToUsdDataFeed,
- mBASIS,
- mTBILL,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVaultWithSwapper, 100000);
- await mintToken(mBASIS, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mBASIS,
- redemptionVaultWithSwapper,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithSwapper, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient: regularAccounts[0],
- mBasisToUsdDataFeed,
- mBASIS,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem 100 mTBILL when other fn overload is paused (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithSwapper,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customRecipient,
- mBasisToUsdDataFeed,
- mBASIS,
- } = await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- redemptionVaultWithSwapper,
- encodeFnSelector('redeemInstant(address,uint256,uint256)'),
- );
- await mintToken(stableCoins.dai, redemptionVaultWithSwapper, 100000);
- await mintToken(mBASIS, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mBASIS,
- redemptionVaultWithSwapper,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithSwapper, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- mBasisToUsdDataFeed,
- mBASIS,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem 100 mTBILL when other fn overload is paused', async () => {
- const {
- owner,
- redemptionVaultWithSwapper,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- mBasisToUsdDataFeed,
- mBASIS,
- } = await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- redemptionVaultWithSwapper,
- encodeFnSelector('redeemInstant(address,uint256,uint256,address)'),
- );
- await mintToken(stableCoins.dai, redemptionVaultWithSwapper, 100000);
- await mintToken(mBASIS, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mBASIS,
- redemptionVaultWithSwapper,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithSwapper, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantWithSwapperTest(
- {
- redemptionVaultWithSwapper,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- mBasisToUsdDataFeed,
- mBASIS,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-});
diff --git a/test/unit/RedemptionVaultWithUSTB.test.ts b/test/unit/RedemptionVaultWithUSTB.test.ts
index 9ce5b13d..b1360685 100644
--- a/test/unit/RedemptionVaultWithUSTB.test.ts
+++ b/test/unit/RedemptionVaultWithUSTB.test.ts
@@ -1,4804 +1,805 @@
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
+import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { expect } from 'chai';
import { constants } from 'ethers';
import { parseUnits } from 'ethers/lib/utils';
-import { ethers } from 'hardhat';
-import { encodeFnSelector } from '../../helpers/utils';
import {
- ManageableVaultTester__factory,
+ baseInitParamsRv,
+ redemptionVaultSuits,
+} from './suits/redemption-vault.suits';
+
+import {
+ RedemptionVaultWithUSTB__factory,
RedemptionVaultWithUSTBTest__factory,
} from '../../typechain-types';
-import { acErrors, blackList, greenList } from '../common/ac.helpers';
import {
approveBase18,
+ InitializeParamCase,
mintToken,
- pauseVault,
- pauseVaultFn,
+ validateImplementation,
} from '../common/common.helpers';
import { setRoundData } from '../common/data-feed.helpers';
import { defaultDeploy } from '../common/fixtures';
import {
addPaymentTokenTest,
setInstantFeeTest,
- setMinAmountTest,
- setInstantDailyLimitTest,
- addWaivedFeeAccountTest,
- removeWaivedFeeAccountTest,
- setVariabilityToleranceTest,
- removePaymentTokenTest,
- withdrawTest,
- changeTokenFeeTest,
- changeTokenAllowanceTest,
+ setWaivedFeeAccountTest,
} from '../common/manageable-vault.helpers';
import {
- approveRedeemRequestTest,
- redeemFiatRequestTest,
redeemInstantTest,
- redeemRequestTest,
- rejectRedeemRequestTest,
- safeApproveRedeemRequestTest,
- setFiatAdditionalFeeTest,
- setMinFiatRedeemAmountTest,
+ setLoanLpTest,
+ setPreferLoanLiquidityTest,
} from '../common/redemption-vault.helpers';
-import { sanctionUser } from '../common/with-sanctions-list.helpers';
-
-describe('RedemptionVaultWithUSTB', function () {
- it('deployment', async () => {
- const {
- redemptionVaultWithUSTB,
- ustbRedemption,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- roles,
- } = await loadFixture(defaultDeploy);
-
- expect(await redemptionVaultWithUSTB.mToken()).eq(mTBILL.address);
-
- expect(await redemptionVaultWithUSTB.ONE_HUNDRED_PERCENT()).eq('10000');
-
- expect(await redemptionVaultWithUSTB.paused()).eq(false);
-
- expect(await redemptionVaultWithUSTB.tokensReceiver()).eq(
- tokensReceiver.address,
- );
- expect(await redemptionVaultWithUSTB.feeReceiver()).eq(feeReceiver.address);
-
- expect(await redemptionVaultWithUSTB.minAmount()).eq(1000);
- expect(await redemptionVaultWithUSTB.minFiatRedeemAmount()).eq(1000);
-
- expect(await redemptionVaultWithUSTB.instantFee()).eq('100');
-
- expect(await redemptionVaultWithUSTB.instantDailyLimit()).eq(
- parseUnits('100000'),
- );
-
- expect(await redemptionVaultWithUSTB.mTokenDataFeed()).eq(
- mTokenToUsdDataFeed.address,
- );
- expect(await redemptionVaultWithUSTB.variationTolerance()).eq(1);
-
- expect(await redemptionVaultWithUSTB.vaultRole()).eq(
- roles.tokenRoles.mTBILL.redemptionVaultAdmin,
- );
-
- expect(await redemptionVaultWithUSTB.MANUAL_FULLFILMENT_TOKEN()).eq(
- ethers.constants.AddressZero,
- );
+import {
+ initializeRvWithUstb,
+ InitializerParamsRvWithUstb,
+} from '../common/vault-initializer.helpers';
+
+const baseInitParamsRvWithUstb = (
+ fixture: Parameters[0],
+): InitializerParamsRvWithUstb => ({
+ ...baseInitParamsRv(fixture),
+ ustbRedemption: fixture.ustbRedemption,
+});
+const rvWithUstbInitializeParamCases: InitializeParamCase[] =
+ [
+ {
+ title: 'ustbRedemption is zero address',
+ params: { ustbRedemption: constants.AddressZero },
+ revertCustomError: {
+ customErrorName: 'InvalidAddress',
+ args: [constants.AddressZero],
+ },
+ },
+ ];
+
+redemptionVaultSuits(
+ 'RedemptionVaultWithUSTB',
+ defaultDeploy,
+ {
+ createNew: async (owner: SignerWithAddress) =>
+ new RedemptionVaultWithUSTBTest__factory(owner).deploy(),
+ key: 'redemptionVaultWithUSTB',
+ },
+ async (fixture) => {
+ const { redemptionVaultWithUSTB, ustbRedemption } = fixture;
expect(await redemptionVaultWithUSTB.ustbRedemption()).eq(
ustbRedemption.address,
);
- });
-
- it('failing deployment', async () => {
- const {
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- accessControl,
- mockedSanctionsList,
- owner,
- } = await loadFixture(defaultDeploy);
-
- const redemptionVaultWithUSTB =
- await new RedemptionVaultWithUSTBTest__factory(owner).deploy();
-
- await expect(
- redemptionVaultWithUSTB[
- 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,(uint256,uint256,uint256),address)'
- ](
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- {
- fiatAdditionalFee: 10000,
- fiatFlatFee: parseUnits('1'),
- minFiatRedeemAmount: parseUnits('100'),
- },
- constants.AddressZero,
- ),
- ).to.be.reverted;
- });
-
- describe('initialization', () => {
- it('should fail: call initialize() when already initialized', async () => {
- const { redemptionVaultWithUSTB } = await loadFixture(defaultDeploy);
-
- await expect(
- redemptionVaultWithUSTB[
- 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,(uint256,uint256,uint256),address,address)'
- ](
- constants.AddressZero,
- {
- mToken: constants.AddressZero,
- mTokenDataFeed: constants.AddressZero,
- },
- {
- feeReceiver: constants.AddressZero,
- tokensReceiver: constants.AddressZero,
- },
- {
- instantFee: 0,
- instantDailyLimit: 0,
- },
- constants.AddressZero,
- 0,
- 0,
- {
- fiatAdditionalFee: 0,
- fiatFlatFee: 0,
- minFiatRedeemAmount: 0,
- },
- constants.AddressZero,
- constants.AddressZero,
- ),
- ).revertedWith('Initializable: contract is already initialized');
- });
-
- it('should fail: call with initializing == false', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initializeWithoutInitializer(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- 1000,
- ),
- ).revertedWith('Initializable: contract is not initializing');
- });
-
- it('should fail: when _tokensReceiver == address(this)', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- feeReceiver,
- mTokenToUsdDataFeed,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: vault.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- 1000,
- ),
- ).revertedWith('invalid address');
- });
-
- it('should fail: when _feeReceiver == address(this)', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- mTokenToUsdDataFeed,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: vault.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- 1000,
- ),
- ).revertedWith('invalid address');
- });
-
- it('should fail: when limit = 0', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: 0,
- },
- mockedSanctionsList.address,
- 1,
- 1000,
- ),
- ).revertedWith('zero limit');
- });
-
- it('should fail: when mToken dataFeed address zero', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: constants.AddressZero,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- 1000,
- ),
- ).revertedWith('zero address');
- });
-
- it('should fail: when variationTolarance zero', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mockedSanctionsList,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- const vault = await new ManageableVaultTester__factory(owner).deploy();
-
- await expect(
- vault.initialize(
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 0,
- 1000,
- ),
- ).revertedWith('fee == 0');
- });
-
- it('should fail: when ustbRedemption address zero', async () => {
- const {
- owner,
- accessControl,
- mTBILL,
- tokensReceiver,
- feeReceiver,
- mTokenToUsdDataFeed,
- mockedSanctionsList,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- const redemptionVaultWithUSTB =
- await new RedemptionVaultWithUSTBTest__factory(owner).deploy();
-
- await expect(
- redemptionVaultWithUSTB[
- 'initialize(address,(address,address),(address,address),(uint256,uint256),address,uint256,uint256,(uint256,uint256,uint256),address,address)'
- ](
- accessControl.address,
- {
- mToken: mTBILL.address,
- mTokenDataFeed: mTokenToUsdDataFeed.address,
- },
- {
- feeReceiver: feeReceiver.address,
- tokensReceiver: tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- mockedSanctionsList.address,
- 1,
- 1000,
- {
- fiatAdditionalFee: 100,
- fiatFlatFee: parseUnits('1'),
- minFiatRedeemAmount: 1000,
- },
- requestRedeemer.address,
- constants.AddressZero,
- ),
- ).revertedWith('zero address');
- });
- });
-
- describe('setMinAmount()', () => {
- it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVaultWithUSTB, regularAccounts } =
- await loadFixture(defaultDeploy);
-
- await setMinAmountTest({ vault: redemptionVaultWithUSTB, owner }, 1.1, {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
+ await validateImplementation(RedemptionVaultWithUSTB__factory);
+ },
+ {
+ deployUninitialized: (fixture) =>
+ new RedemptionVaultWithUSTBTest__factory(fixture.owner).deploy(),
+ initialize: async (fixture, params, opt) => {
+ await initializeRvWithUstb(
+ { ...baseInitParamsRvWithUstb(fixture), ...params },
+ opt?.contract,
+ opt,
+ );
+ },
+ extraParamCases: rvWithUstbInitializeParamCases,
+ },
+ async (defaultDeploy) => {
+ describe('RedemptionVaultWithUSTB', function () {
+ describe('redeemInstant()', () => {
+ describe('preferLoanLiquidity=true', () => {
+ it('redeem 100 mTBILL when vault has enough USDC (no USTB needed)', async () => {
+ const {
+ owner,
+ redemptionVaultWithUSTB,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ ustbToken,
+ } = await loadFixture(defaultDeploy);
+
+ await mintToken(stableCoins.usdc, redemptionVaultWithUSTB, 100000);
+ await mintToken(ustbToken, redemptionVaultWithUSTB, 9900);
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithUSTB, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setPreferLoanLiquidityTest(
+ { redemptionVault: redemptionVaultWithUSTB, owner },
+ true,
+ );
+
+ const ustbBalanceBefore = await ustbToken.balanceOf(
+ redemptionVaultWithUSTB.address,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithUSTB,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ stableCoins.usdc,
+ 100,
+ );
+
+ const ustbBalanceAfter = await ustbToken.balanceOf(
+ redemptionVaultWithUSTB.address,
+ );
+ expect(ustbBalanceAfter).to.equal(ustbBalanceBefore);
+ });
+
+ it('when vault has no USDC but has USTB and LP liquidity, LP liquidity should be used first', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithUSTB,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ ustbToken,
+ ustbRedemption,
+ loanLp,
+ mTokenLoan,
+ redemptionVaultLoanSwapper,
+ } = await loadFixture(defaultDeploy);
+
+ await ustbRedemption.setChainlinkData(parseUnits('1', 8), false);
+ await ustbRedemption.setMaxUstbRedemptionAmount(
+ parseUnits('2000', 6),
+ );
+ await mintToken(ustbToken, redemptionVaultWithUSTB, 9900);
+
+ await mintToken(
+ stableCoins.usdc,
+ redemptionVaultLoanSwapper,
+ 100000,
+ );
+ await mintToken(mTokenLoan, loanLp, 100000);
+ await approveBase18(
+ loanLp,
+ mTokenLoan,
+ redemptionVaultWithUSTB,
+ 100000,
+ );
+
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithUSTB, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setInstantFeeTest(
+ { vault: redemptionVaultWithUSTB, owner },
+ 0,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setPreferLoanLiquidityTest(
+ { redemptionVault: redemptionVaultWithUSTB, owner },
+ true,
+ );
+
+ const ustbBalanceBefore = await ustbToken.balanceOf(
+ redemptionVaultWithUSTB.address,
+ );
+ const loanLpBalanceBefore = await mTokenLoan.balanceOf(
+ loanLp.address,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithUSTB,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ additionalLiquidity: async () => {
+ return parseUnits('300', 6);
+ },
+ },
+ stableCoins.usdc,
+ 1000,
+ );
+
+ const ustbBalanceAfter = await ustbToken.balanceOf(
+ redemptionVaultWithUSTB.address,
+ );
+ const loanLpBalanceAfter = await mTokenLoan.balanceOf(
+ loanLp.address,
+ );
+
+ expect(ustbBalanceAfter).to.equal(ustbBalanceBefore);
+ expect(loanLpBalanceAfter).to.be.lt(loanLpBalanceBefore);
+ });
+
+ it('redeem 1000 mTBILL, when vault has no USDC but has USTB', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithUSTB,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ ustbToken,
+ ustbRedemption,
+ } = await loadFixture(defaultDeploy);
+
+ await ustbRedemption.setChainlinkData(parseUnits('1', 8), false);
+ await ustbRedemption.setMaxUstbRedemptionAmount(
+ parseUnits('2000', 6),
+ );
+ await mintToken(ustbToken, redemptionVaultWithUSTB, 9900);
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithUSTB, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setInstantFeeTest(
+ { vault: redemptionVaultWithUSTB, owner },
+ 0,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setPreferLoanLiquidityTest(
+ { redemptionVault: redemptionVaultWithUSTB, owner },
+ true,
+ );
+
+ const ustbBalanceBefore = await ustbToken.balanceOf(
+ redemptionVaultWithUSTB.address,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithUSTB,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ additionalLiquidity: async () => {
+ return (
+ await ustbRedemption.calculateUsdcOut(
+ await ustbToken.balanceOf(
+ redemptionVaultWithUSTB.address,
+ ),
+ )
+ ).usdcOutAmountAfterFee;
+ },
+ },
+ stableCoins.usdc,
+ 1000,
+ );
+
+ const ustbBalanceAfter = await ustbToken.balanceOf(
+ redemptionVaultWithUSTB.address,
+ );
+ expect(ustbBalanceAfter).to.be.lt(ustbBalanceBefore);
+ });
+
+ it('when vault partially has USDC, partially has USTB and partially has LP liquidity, so all the liquidity should be used', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithUSTB,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ ustbToken,
+ ustbRedemption,
+ loanLp,
+ mTokenLoan,
+ redemptionVaultLoanSwapper,
+ } = await loadFixture(defaultDeploy);
+
+ await ustbRedemption.setChainlinkData(parseUnits('1', 8), false);
+ await ustbRedemption.setMaxUstbRedemptionAmount(
+ parseUnits('2000', 6),
+ );
+ await mintToken(ustbToken, redemptionVaultWithUSTB, 9900);
+ await mintToken(stableCoins.usdc, redemptionVaultLoanSwapper, 300);
+ await mintToken(stableCoins.usdc, redemptionVaultWithUSTB, 400);
+ await mintToken(mTokenLoan, loanLp, 300);
+ await approveBase18(
+ loanLp,
+ mTokenLoan,
+ redemptionVaultWithUSTB,
+ 300,
+ );
+
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithUSTB, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setInstantFeeTest(
+ { vault: redemptionVaultWithUSTB, owner },
+ 0,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setPreferLoanLiquidityTest(
+ { redemptionVault: redemptionVaultWithUSTB, owner },
+ true,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithUSTB,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ additionalLiquidity: async () => {
+ return (
+ await ustbRedemption.calculateUsdcOut(
+ await ustbToken.balanceOf(
+ redemptionVaultWithUSTB.address,
+ ),
+ )
+ ).usdcOutAmountAfterFee;
+ },
+ },
+ stableCoins.usdc,
+ 1000,
+ );
+
+ expect(
+ await ustbToken.balanceOf(redemptionVaultWithUSTB.address),
+ ).to.be.lt(parseUnits('9900', 6));
+ expect(
+ await stableCoins.usdc.balanceOf(redemptionVaultWithUSTB.address),
+ ).eq(0);
+ expect(
+ await stableCoins.usdc.balanceOf(
+ redemptionVaultLoanSwapper.address,
+ ),
+ ).eq(0);
+ expect(await mTokenLoan.balanceOf(loanLp.address)).eq(0);
+ });
+ });
+
+ it('should fail: user try to instant redeem more than contract can redeem and it hits loan lp flow', async () => {
+ const {
+ owner,
+ redemptionVaultWithUSTB,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ ustbToken,
+ } = await loadFixture(defaultDeploy);
+
+ await setLoanLpTest(
+ { redemptionVault: redemptionVaultWithUSTB, owner },
+ constants.AddressZero,
+ );
+
+ await mintToken(mTBILL, owner, 100000);
+ await mintToken(stableCoins.usdc, redemptionVaultWithUSTB, 100);
+ await mintToken(ustbToken, redemptionVaultWithUSTB, 100);
+
+ await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 100000);
+
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithUSTB, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 100,
+ true,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithUSTB,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ stableCoins.usdc,
+ 100000,
+ {
+ revertMessage: 'ERC20: transfer amount exceeds balance',
+ },
+ );
+ });
+
+ it('redeem 1000 mTBILL, when price of stable is 1$ and mToken price is 1$ and contract does not have USDC, but has 9900 USTB', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithUSTB,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ ustbToken,
+ ustbRedemption,
+ } = await loadFixture(defaultDeploy);
+
+ await ustbRedemption.setChainlinkData(parseUnits('1', 8), false); // Set USTB price to $1
+
+ await mintToken(ustbToken, redemptionVaultWithUSTB, 9900);
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithUSTB, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setInstantFeeTest({ vault: redemptionVaultWithUSTB, owner }, 0);
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+
+ const ustbBalanceBefore = await ustbToken.balanceOf(
+ redemptionVaultWithUSTB.address,
+ );
+
+ await ustbRedemption.setMaxUstbRedemptionAmount(
+ parseUnits('1000', 6),
+ );
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithUSTB,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ additionalLiquidity: async () => {
+ return (
+ await ustbRedemption.calculateUsdcOut(
+ await ustbToken.balanceOf(redemptionVaultWithUSTB.address),
+ )
+ ).usdcOutAmountAfterFee;
+ },
+ },
+ stableCoins.usdc,
+ 1000,
+ );
+
+ const ustbBalanceAfter = await ustbToken.balanceOf(
+ redemptionVaultWithUSTB.address,
+ );
+
+ expect(ustbBalanceAfter).to.be.lt(ustbBalanceBefore);
+ });
+
+ it('redeem 1000 mTBILL, when price of stable is 1$ and mToken price is 1$ and contract has 100 USDC and 9900 USTB', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithUSTB,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ ustbToken,
+ ustbRedemption,
+ } = await loadFixture(defaultDeploy);
+
+ await ustbRedemption.setChainlinkData(parseUnits('1', 8), false); // Set USTB price to $1
+
+ await mintToken(stableCoins.usdc, redemptionVaultWithUSTB, 100);
+ await mintToken(ustbToken, redemptionVaultWithUSTB, 9900);
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithUSTB, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+
+ await ustbRedemption.setMaxUstbRedemptionAmount(
+ parseUnits('1000', 6),
+ );
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithUSTB,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ additionalLiquidity: async () => {
+ return (
+ await ustbRedemption.calculateUsdcOut(
+ await ustbToken.balanceOf(redemptionVaultWithUSTB.address),
+ )
+ ).usdcOutAmountAfterFee;
+ },
+ },
+ stableCoins.usdc,
+ 1000,
+ );
+ });
+
+ it('redeem 1000 mTBILL, when price of stable is 1.03$ and mToken price is 5$ and contract has 100 USDC and sufficient USTB without checking of minDepositAmount', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithUSTB,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ ustbToken,
+ ustbRedemption,
+ } = await loadFixture(defaultDeploy);
+
+ await ustbRedemption.setChainlinkData(parseUnits('1', 8), false); // Set USTB price to $1
+
+ await mintToken(stableCoins.usdc, redemptionVaultWithUSTB, 100);
+ await mintToken(ustbToken, redemptionVaultWithUSTB, 15000);
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithUSTB, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 100,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await redemptionVaultWithUSTB.freeFromMinAmount(owner.address, true);
+
+ await ustbRedemption.setMaxUstbRedemptionAmount(
+ parseUnits('6000', 6),
+ );
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithUSTB,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ additionalLiquidity: async () => {
+ return (
+ await ustbRedemption.calculateUsdcOut(
+ await ustbToken.balanceOf(redemptionVaultWithUSTB.address),
+ )
+ ).usdcOutAmountAfterFee;
+ },
+ },
+ stableCoins.usdc,
+ 1000,
+ );
+ });
+
+ it('redeem 1000 mTBILL, when price of stable is 1.03$ and mToken price is 5$ and contract has 100 USDC and sufficient USTB and user in waivedFeeRestriction', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVaultWithUSTB,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ ustbToken,
+ ustbRedemption,
+ } = await loadFixture(defaultDeploy);
+
+ await ustbRedemption.setChainlinkData(parseUnits('1', 8), false); // Set USTB price to $1
+
+ await mintToken(stableCoins.usdc, redemptionVaultWithUSTB, 100);
+ await mintToken(ustbToken, redemptionVaultWithUSTB, 15000);
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithUSTB, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 100,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await setWaivedFeeAccountTest(
+ { vault: redemptionVaultWithUSTB, owner },
+ owner.address,
+ true,
+ );
+
+ await ustbRedemption.setMaxUstbRedemptionAmount(
+ parseUnits('6000', 6),
+ );
+ await redeemInstantTest(
+ {
+ redemptionVault: redemptionVaultWithUSTB,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ waivedFee: true,
+ additionalLiquidity: async () => {
+ return (
+ await ustbRedemption.calculateUsdcOut(
+ await ustbToken.balanceOf(redemptionVaultWithUSTB.address),
+ )
+ ).usdcOutAmountAfterFee;
+ },
+ },
+ stableCoins.usdc,
+ 1000,
+ );
+ });
+
+ it('should fail: when redemption exceeds available USDC in USTB redemption contract', async () => {
+ const {
+ owner,
+ redemptionVaultWithUSTB,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ ustbToken,
+ ustbRedemption,
+ } = await loadFixture(defaultDeploy);
+
+ await ustbRedemption.setChainlinkData(parseUnits('1', 8), false); // Set USTB price to $1
+
+ await mintToken(ustbToken, redemptionVaultWithUSTB, 1000000);
+ await mintToken(stableCoins.usdc, redemptionVaultWithUSTB, 100);
+
+ const mockBalance = await stableCoins.usdc.balanceOf(
+ ustbRedemption.address,
+ );
+ await ustbRedemption.withdraw(
+ stableCoins.usdc.address,
+ owner.address,
+ mockBalance,
+ );
+
+ expect(
+ await stableCoins.usdc.balanceOf(ustbRedemption.address),
+ ).to.equal(0);
+
+ await mintToken(mTBILL, owner, 100000);
+ await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 100000);
+ await addPaymentTokenTest(
+ { vault: redemptionVaultWithUSTB, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ // Try to redeem - should fail: because USTB redemption has no USDC
+ await expect(
+ redemptionVaultWithUSTB['redeemInstant(address,uint256,uint256)'](
+ stableCoins.usdc.address,
+ parseUnits('10000'),
+ 0,
+ ),
+ ).to.be.revertedWith('ERC20: transfer amount exceeds balance');
+ });
});
- });
-
- it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVaultWithUSTB } = await loadFixture(
- defaultDeploy,
- );
- await setMinAmountTest({ vault: redemptionVaultWithUSTB, owner }, 1.1);
- });
- });
-
- describe('setMinFiatRedeemAmount()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVaultWithUSTB, regularAccounts } =
- await loadFixture(defaultDeploy);
-
- await setMinFiatRedeemAmountTest(
- { redemptionVault: redemptionVaultWithUSTB, owner },
- 1.1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- },
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVaultWithUSTB } = await loadFixture(
- defaultDeploy,
- );
- await setMinFiatRedeemAmountTest(
- { redemptionVault: redemptionVaultWithUSTB, owner },
- 1.1,
- );
- });
- });
-
- describe('setFiatAdditionalFee()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVaultWithUSTB, regularAccounts } =
- await loadFixture(defaultDeploy);
-
- await setFiatAdditionalFeeTest(
- { redemptionVault: redemptionVaultWithUSTB, owner },
- 100,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- },
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVaultWithUSTB } = await loadFixture(
- defaultDeploy,
- );
- await setFiatAdditionalFeeTest(
- { redemptionVault: redemptionVaultWithUSTB, owner },
- 100,
- );
- });
- });
-
- describe('setInstantDailyLimit()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVaultWithUSTB, regularAccounts } =
- await loadFixture(defaultDeploy);
-
- await setInstantDailyLimitTest(
- { vault: redemptionVaultWithUSTB, owner },
- parseUnits('1000'),
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
- },
- );
- });
-
- it('should fail: try to set 0 limit', async () => {
- const { owner, redemptionVaultWithUSTB } = await loadFixture(
- defaultDeploy,
- );
-
- await setInstantDailyLimitTest(
- { vault: redemptionVaultWithUSTB, owner },
- constants.Zero,
- {
- revertMessage: 'MV: limit zero',
- },
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { owner, redemptionVaultWithUSTB } = await loadFixture(
- defaultDeploy,
- );
- await setInstantDailyLimitTest(
- { vault: redemptionVaultWithUSTB, owner },
- parseUnits('1000'),
- );
- });
- });
-
- describe('addPaymentToken()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithUSTB, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- ethers.constants.AddressZero,
- ethers.constants.AddressZero,
- 0,
- true,
- constants.MaxUint256,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
-
- it('should fail: when token is already added', async () => {
- const { redemptionVaultWithUSTB, stableCoins, owner, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- constants.MaxUint256,
- {
- revertMessage: 'MV: already added',
- },
- );
- });
-
- it('should fail: when token dataFeed address zero', async () => {
- const { redemptionVaultWithUSTB, stableCoins, owner } = await loadFixture(
- defaultDeploy,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- constants.AddressZero,
- 0,
- true,
- constants.MaxUint256,
- {
- revertMessage: 'zero address',
- },
- );
- });
-
- it('call when allowance is zero', async () => {
- const { redemptionVaultWithUSTB, stableCoins, owner, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- false,
- constants.Zero,
- );
- });
-
- it('call when allowance is not uint256 max', async () => {
- const { redemptionVaultWithUSTB, stableCoins, owner, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- false,
- parseUnits('100'),
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithUSTB, stableCoins, owner, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role and add 3 options on a row', async () => {
- const { redemptionVaultWithUSTB, stableCoins, owner, dataFeed } =
- await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdt,
- dataFeed.address,
- 0,
- true,
- );
- });
- });
-
- describe('addWaivedFeeAccount()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithUSTB, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithUSTB, owner },
- ethers.constants.AddressZero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('should fail: if account fee already waived', async () => {
- const { redemptionVaultWithUSTB, owner } = await loadFixture(
- defaultDeploy,
- );
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithUSTB, owner },
- owner.address,
- );
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithUSTB, owner },
- owner.address,
- { revertMessage: 'MV: already added' },
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithUSTB, owner } = await loadFixture(
- defaultDeploy,
- );
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithUSTB, owner },
- owner.address,
- );
- });
- });
-
- describe('removeWaivedFeeAccount()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithUSTB, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await removeWaivedFeeAccountTest(
- { vault: redemptionVaultWithUSTB, owner },
- ethers.constants.AddressZero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('should fail: if account not found in restriction', async () => {
- const { redemptionVaultWithUSTB, owner } = await loadFixture(
- defaultDeploy,
- );
- await removeWaivedFeeAccountTest(
- { vault: redemptionVaultWithUSTB, owner },
- owner.address,
- { revertMessage: 'MV: not found' },
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithUSTB, owner } = await loadFixture(
- defaultDeploy,
- );
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithUSTB, owner },
- owner.address,
- );
- await removeWaivedFeeAccountTest(
- { vault: redemptionVaultWithUSTB, owner },
- owner.address,
- );
- });
- });
-
- describe('setFee()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithUSTB, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await setInstantFeeTest(
- { vault: redemptionVaultWithUSTB, owner },
- ethers.constants.Zero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
-
- it('should fail: if new value greater then 100%', async () => {
- const { redemptionVaultWithUSTB, owner } = await loadFixture(
- defaultDeploy,
- );
- await setInstantFeeTest(
- { vault: redemptionVaultWithUSTB, owner },
- 10001,
- {
- revertMessage: 'fee > 100%',
- },
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithUSTB, owner } = await loadFixture(
- defaultDeploy,
- );
- await setInstantFeeTest({ vault: redemptionVaultWithUSTB, owner }, 100);
- });
- });
-
- describe('setVariabilityTolerance()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithUSTB, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await setVariabilityToleranceTest(
- { vault: redemptionVaultWithUSTB, owner },
- ethers.constants.Zero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('should fail: if new value zero', async () => {
- const { redemptionVaultWithUSTB, owner } = await loadFixture(
- defaultDeploy,
- );
- await setVariabilityToleranceTest(
- { vault: redemptionVaultWithUSTB, owner },
- ethers.constants.Zero,
- { revertMessage: 'fee == 0' },
- );
- });
-
- it('should fail: if new value greater then 100%', async () => {
- const { redemptionVaultWithUSTB, owner } = await loadFixture(
- defaultDeploy,
- );
- await setVariabilityToleranceTest(
- { vault: redemptionVaultWithUSTB, owner },
- 10001,
- { revertMessage: 'fee > 100%' },
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithUSTB, owner } = await loadFixture(
- defaultDeploy,
- );
- await setVariabilityToleranceTest(
- { vault: redemptionVaultWithUSTB, owner },
- 100,
- );
- });
- });
-
- describe('removePaymentToken()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithUSTB, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await removePaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- ethers.constants.AddressZero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
-
- it('should fail: when token is not exists', async () => {
- const { owner, redemptionVaultWithUSTB, stableCoins } = await loadFixture(
- defaultDeploy,
- );
- await removePaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai.address,
- { revertMessage: 'MV: not exists' },
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithUSTB, stableCoins, owner, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await removePaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai.address,
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role and add 3 options on a row', async () => {
- const { redemptionVaultWithUSTB, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdt,
- dataFeed.address,
- 0,
- true,
- );
-
- await removePaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai.address,
- );
- await removePaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc.address,
- );
- await removePaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdt.address,
- );
-
- await removePaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdt.address,
- { revertMessage: 'MV: not exists' },
- );
- });
- });
-
- describe('withdrawToken()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithUSTB, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await withdrawTest(
- { vault: redemptionVaultWithUSTB, owner },
- ethers.constants.AddressZero,
- 0,
- ethers.constants.AddressZero,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
-
- it('should fail: when there is no token in vault', async () => {
- const { owner, redemptionVaultWithUSTB, regularAccounts, stableCoins } =
- await loadFixture(defaultDeploy);
- await withdrawTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- 1,
- regularAccounts[0],
- { revertMessage: 'ERC20: transfer amount exceeds balance' },
- );
- });
-
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithUSTB, regularAccounts, stableCoins, owner } =
- await loadFixture(defaultDeploy);
- await mintToken(stableCoins.dai, redemptionVaultWithUSTB, 1);
- await withdrawTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- 1,
- regularAccounts[0],
- );
- });
- });
-
- describe('checkAndRedeemUSTB', () => {
- it('should not redeem USTB when contract has enough balance', async () => {
- const { redemptionVaultWithUSTB, stableCoins } = await loadFixture(
- defaultDeploy,
- );
-
- const usdcAmount = parseUnits('1000', 8);
- await stableCoins.usdc.mint(redemptionVaultWithUSTB.address, usdcAmount);
-
- const balanceBefore = await stableCoins.usdc.balanceOf(
- redemptionVaultWithUSTB.address,
- );
-
- await redemptionVaultWithUSTB.checkAndRedeemUSTB(
- stableCoins.usdc.address,
- parseUnits('500', 8),
- );
-
- const balanceAfter = await stableCoins.usdc.balanceOf(
- redemptionVaultWithUSTB.address,
- );
- expect(balanceAfter).to.equal(balanceBefore);
- });
-
- it('should revert when contract has insufficient USTB balance', async () => {
- const { redemptionVaultWithUSTB, stableCoins } = await loadFixture(
- defaultDeploy,
- );
-
- // Contract has less USDC than needed
- const usdcAmount = parseUnits('500', 8);
- await stableCoins.usdc.mint(redemptionVaultWithUSTB.address, usdcAmount);
-
- // Try to redeem more than available
- await expect(
- redemptionVaultWithUSTB.checkAndRedeemUSTB(
- stableCoins.usdc.address,
- parseUnits('1000', 8),
- ),
- ).to.be.revertedWith('RVU: insufficient USTB balance');
- });
- });
-
- describe('freeFromMinAmount()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const { redemptionVaultWithUSTB, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- redemptionVaultWithUSTB
- .connect(regularAccounts[0])
- .freeFromMinAmount(regularAccounts[1].address, true),
- ).to.be.revertedWith('WMAC: hasnt role');
- });
- it('should not fail', async () => {
- const { redemptionVaultWithUSTB, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- redemptionVaultWithUSTB.freeFromMinAmount(
- regularAccounts[0].address,
- true,
- ),
- ).to.not.reverted;
-
- expect(
- await redemptionVaultWithUSTB.isFreeFromMinAmount(
- regularAccounts[0].address,
- ),
- ).to.eq(true);
- });
- it('should fail: already in list', async () => {
- const { redemptionVaultWithUSTB, regularAccounts } = await loadFixture(
- defaultDeploy,
- );
- await expect(
- redemptionVaultWithUSTB.freeFromMinAmount(
- regularAccounts[0].address,
- true,
- ),
- ).to.not.reverted;
-
- expect(
- await redemptionVaultWithUSTB.isFreeFromMinAmount(
- regularAccounts[0].address,
- ),
- ).to.eq(true);
-
- await expect(
- redemptionVaultWithUSTB.freeFromMinAmount(
- regularAccounts[0].address,
- true,
- ),
- ).to.revertedWith('DV: already free');
- });
- });
-
- describe('changeTokenAllowance()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithUSTB, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await changeTokenAllowanceTest(
- { vault: redemptionVaultWithUSTB, owner },
- ethers.constants.AddressZero,
- 0,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('should fail: token not exist', async () => {
- const { redemptionVaultWithUSTB, owner, stableCoins } = await loadFixture(
- defaultDeploy,
- );
- await changeTokenAllowanceTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai.address,
- 0,
- { revertMessage: 'MV: token not exists' },
- );
- });
- it('should fail: allowance zero', async () => {
- const { redemptionVaultWithUSTB, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai.address,
- 0,
- { revertMessage: 'MV: zero allowance' },
- );
- });
- it('should fail: if mint exceed allowance', async () => {
- const {
- redemptionVaultWithUSTB,
- stableCoins,
- owner,
- dataFeed,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await mintToken(mTBILL, owner, 100000);
- await mintToken(stableCoins.usdc, redemptionVaultWithUSTB, 100000);
- await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 100000);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc.address,
- 100,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 99_999,
- {
- revertMessage: 'MV: exceed allowance',
- },
- );
- });
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithUSTB, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai.address,
- 100000000,
- );
- });
- it('should decrease if allowance < UINT_MAX', async () => {
- const {
- redemptionVaultWithUSTB,
- stableCoins,
- owner,
- dataFeed,
- mTBILL,
- mTokenToUsdDataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.usdc, redemptionVaultWithUSTB, 100000);
- await mintToken(mTBILL, owner, 100000);
- await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 100000);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc.address,
- parseUnits('1000'),
- );
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
-
- const tokenConfigBefore = await redemptionVaultWithUSTB.tokensConfig(
- stableCoins.usdc.address,
- );
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 999,
- );
-
- const tokenConfigAfter = await redemptionVaultWithUSTB.tokensConfig(
- stableCoins.usdc.address,
- );
-
- expect(tokenConfigBefore.allowance.sub(tokenConfigAfter.allowance)).eq(
- parseUnits('999'),
- );
+ describe('redeemInstant() complex', () => {
+ it('redeem 100 mtbill, when price is 5$ and contract balance 100 USDC and USTB to cover remaining amount, 125 mtbill when price is 5.1$, 114 mtbill when price is 5.4$', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ redemptionVaultWithUSTB: redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ mockedAggregatorMToken,
+ ustbToken,
+ ustbRedemption,
+ } = await loadFixture(defaultDeploy);
+
+ await ustbRedemption.setChainlinkData(parseUnits('1', 8), false); // Set USTB price to $1
+
+ await mintToken(mTBILL, owner, 100 + 125 + 114);
+
+ await mintToken(stableCoins.usdc, redemptionVault, 100);
+ await mintToken(ustbToken, redemptionVault, 100000);
+
+ await approveBase18(owner, mTBILL, redemptionVault, 100 + 125 + 114);
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await ustbRedemption.setMaxUstbRedemptionAmount(
+ parseUnits('10000', 6),
+ );
+
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setRoundData({ mockedAggregator }, 1.04);
+
+ const additionalLiquidity = async () =>
+ (
+ await ustbRedemption.calculateUsdcOut(
+ await ustbToken.balanceOf(redemptionVault.address),
+ )
+ ).usdcOutAmountAfterFee;
+
+ await redeemInstantTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ additionalLiquidity,
+ },
+ stableCoins.usdc,
+ 100,
+ );
+
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5.1);
+ await redeemInstantTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ additionalLiquidity,
+ },
+ stableCoins.usdc,
+ 125,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.01);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5.4);
+ await redeemInstantTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ additionalLiquidity,
+ },
+ stableCoins.usdc,
+ 114,
+ );
+ });
+ });
});
- it('should not decrease if allowance = UINT_MAX', async () => {
- const {
- redemptionVaultWithUSTB,
- stableCoins,
- owner,
- dataFeed,
- mTBILL,
- mTokenToUsdDataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- } = await loadFixture(defaultDeploy);
- await mintToken(stableCoins.usdc, redemptionVaultWithUSTB, 100000);
- await mintToken(mTBILL, owner, 100000);
- await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 100000);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenAllowanceTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc.address,
- constants.MaxUint256,
- );
-
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
-
- const tokenConfigBefore = await redemptionVaultWithUSTB.tokensConfig(
- stableCoins.usdc.address,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 999,
- );
-
- const tokenConfigAfter = await redemptionVaultWithUSTB.tokensConfig(
- stableCoins.usdc.address,
- );
-
- expect(tokenConfigBefore.allowance).eq(tokenConfigAfter.allowance);
- });
- });
-
- describe('changeTokenFee()', () => {
- it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithUSTB, regularAccounts, owner } =
- await loadFixture(defaultDeploy);
- await changeTokenFeeTest(
- { vault: redemptionVaultWithUSTB, owner },
- ethers.constants.AddressZero,
- 0,
- { revertMessage: acErrors.WMAC_HASNT_ROLE, from: regularAccounts[0] },
- );
- });
- it('should fail: token not exist', async () => {
- const { redemptionVaultWithUSTB, owner, stableCoins } = await loadFixture(
- defaultDeploy,
- );
- await changeTokenFeeTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai.address,
- 0,
- { revertMessage: 'MV: token not exists' },
- );
- });
- it('should fail: fee > 100%', async () => {
- const { redemptionVaultWithUSTB, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenFeeTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai.address,
- 10001,
- { revertMessage: 'fee > 100%' },
- );
- });
- it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
- const { redemptionVaultWithUSTB, owner, stableCoins, dataFeed } =
- await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await changeTokenFeeTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai.address,
- 100,
- );
- });
- });
-
- describe('redeemInstant()', () => {
- it('should fail: when there is no token in vault', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 1,
- {
- revertMessage: 'MV: token not exists',
- },
- );
- });
-
- it('should fail: when trying to redeem 0 amount', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 0,
- {
- revertMessage: 'RV: invalid amount',
- },
- );
- });
-
- it('should fail: when function paused', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- redemptionVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector(
- 'redeemInstant(address,uint256,uint256)',
- );
- await pauseVaultFn(redemptionVaultWithUSTB, selector);
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: call with insufficient allowance', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- revertMessage: 'ERC20: insufficient allowance',
- },
- );
- });
-
- it('should fail: call with insufficient balance', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- revertMessage: 'ERC20: burn amount exceeds balance',
- },
- );
- });
-
- it('should fail: dataFeed rate 0 ', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, stableCoins.usdc, redemptionVaultWithUSTB, 10);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await mintToken(mTBILL, owner, 100_000);
- await setRoundData({ mockedAggregator }, 0);
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 1,
- {
- revertMessage: 'DF: feed is deprecated',
- },
- );
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 0);
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 1,
- {
- revertMessage: 'DF: feed is deprecated',
- },
- );
- });
-
- it('should fail: if min receive amount greater then actual', async () => {
- const {
- redemptionVaultWithUSTB,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(mTBILL, owner, 100_000);
-
- await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 100_000);
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- minAmount: parseUnits('1000000'),
- },
- stableCoins.usdc,
- 99_999,
- {
- revertMessage: 'RVU: minReceiveAmount > actual',
- },
- );
- });
-
- it('should fail: call for amount < minAmount', async () => {
- const {
- redemptionVaultWithUSTB,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mTBILL, owner, 100_000);
- await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 100_000);
-
- await setMinAmountTest(
- { vault: redemptionVaultWithUSTB, owner },
- 100_000,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 99_999,
- {
- revertMessage: 'RV: amount < min',
- },
- );
- });
-
- it('should fail: call when token is invalid', async () => {
- const {
- redemptionVaultWithUSTB,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- ustbToken,
- ustbRedemption,
- mockedAggregator,
- mockedAggregatorMToken,
- } = await loadFixture(defaultDeploy);
-
- await ustbRedemption.setChainlinkData(parseUnits('1', 8), false);
- await mintToken(mTBILL, owner, 1000);
- await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 1000);
- await mintToken(ustbToken, redemptionVaultWithUSTB, 9900);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setInstantFeeTest({ vault: redemptionVaultWithUSTB, owner }, 0);
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1000,
- {
- revertMessage: 'RVU: invalid token',
- },
- );
- });
-
- it('should fail: if exceed allowance of redeem by token', async () => {
- const {
- redemptionVaultWithUSTB,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(mTBILL, owner, 100_000);
- await changeTokenAllowanceTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc.address,
- 100,
- );
- await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 100_000);
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 99_999,
- {
- revertMessage: 'MV: exceed allowance',
- },
- );
- });
-
- it('should fail: if redeem daily limit exceeded', async () => {
- const {
- redemptionVaultWithUSTB,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 4);
-
- await mintToken(mTBILL, owner, 100_000);
- await setInstantDailyLimitTest(
- { vault: redemptionVaultWithUSTB, owner },
- 1000,
- );
-
- await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 100_000);
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 99_999,
- {
- revertMessage: 'MV: exceed limit',
- },
- );
- });
-
- it('should fail: if some fee = 100%', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 10000,
- true,
- );
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- revertMessage: 'RV: amountMTokenIn < fee',
- },
- );
-
- await removePaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setInstantFeeTest({ vault: redemptionVaultWithUSTB, owner }, 10000);
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- { revertMessage: 'RV: amountMTokenIn < fee' },
- );
- });
-
- it('should fail: greenlist enabled and user not in greenlist ', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redemptionVaultWithUSTB.setGreenlistEnable(true);
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: user in blacklist ', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: user in sanctions list', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- regularAccounts[0],
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('should fail: when function with custom recipient is paused', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- customRecipient,
- } = await loadFixture(defaultDeploy);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- redemptionVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector(
- 'redeemInstant(address,uint256,uint256,address)',
- );
- await pauseVaultFn(redemptionVaultWithUSTB, selector);
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: greenlist enabled and recipient not in greenlist (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await redemptionVaultWithUSTB.setGreenlistEnable(true);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: recipient in blacklist (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- customRecipient,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: recipient in sanctions list (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- customRecipient,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('should fail: user try to instant redeem more than contract can redeem', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- ustbToken,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100000);
- await mintToken(stableCoins.usdc, redemptionVaultWithUSTB, 100);
- await mintToken(ustbToken, redemptionVaultWithUSTB, 100);
-
- await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 100000);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 100,
- true,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100000,
- {
- revertMessage: 'RVU: insufficient USTB balance',
- },
- );
- });
-
- it('redeem 100 mTBILL, when price of stable is 1$ and mToken price is 1$ and contract has 100 USDC', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithUSTB,
- ustbRedemption,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, redemptionVaultWithUSTB, 100);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await ustbRedemption.setMaxUstbRedemptionAmount(parseUnits('100', 6));
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- );
- });
-
- it('redeem 1000 mTBILL, when price of stable is 1$ and mToken price is 1$ and contract does not have USDC, but has 9900 USTB', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- ustbToken,
- ustbRedemption,
- } = await loadFixture(defaultDeploy);
-
- await ustbRedemption.setChainlinkData(parseUnits('1', 8), false); // Set USTB price to $1
-
- await mintToken(ustbToken, redemptionVaultWithUSTB, 9900);
- await mintToken(mTBILL, owner, 1000);
- await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 1000);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setInstantFeeTest({ vault: redemptionVaultWithUSTB, owner }, 0);
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
-
- const ustbBalanceBefore = await ustbToken.balanceOf(
- redemptionVaultWithUSTB.address,
- );
-
- await ustbRedemption.setMaxUstbRedemptionAmount(parseUnits('1000', 6));
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 1000,
- );
-
- const ustbBalanceAfter = await ustbToken.balanceOf(
- redemptionVaultWithUSTB.address,
- );
-
- expect(ustbBalanceAfter).to.be.lt(ustbBalanceBefore);
- });
-
- it('redeem 1000 mTBILL, when price of stable is 1$ and mToken price is 1$ and contract has 100 USDC and 9900 USTB', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- ustbToken,
- ustbRedemption,
- } = await loadFixture(defaultDeploy);
-
- await ustbRedemption.setChainlinkData(parseUnits('1', 8), false); // Set USTB price to $1
-
- await mintToken(stableCoins.usdc, redemptionVaultWithUSTB, 100);
- await mintToken(ustbToken, redemptionVaultWithUSTB, 9900);
- await mintToken(mTBILL, owner, 1000);
- await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 1000);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
-
- await ustbRedemption.setMaxUstbRedemptionAmount(parseUnits('1000', 6));
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 1000,
- );
- });
-
- it('redeem 1000 mTBILL, when price of stable is 1.03$ and mToken price is 5$ and contract has 100 USDC and sufficient USTB without checking of minDepositAmount', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- ustbToken,
- ustbRedemption,
- } = await loadFixture(defaultDeploy);
-
- await ustbRedemption.setChainlinkData(parseUnits('1', 8), false); // Set USTB price to $1
-
- await mintToken(stableCoins.usdc, redemptionVaultWithUSTB, 100);
- await mintToken(ustbToken, redemptionVaultWithUSTB, 15000);
- await mintToken(mTBILL, owner, 1000);
- await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 1000);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 100,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await redemptionVaultWithUSTB.freeFromMinAmount(owner.address, true);
-
- await ustbRedemption.setMaxUstbRedemptionAmount(parseUnits('6000', 6));
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 1000,
- );
- });
-
- it('redeem 1000 mTBILL, when price of stable is 1.03$ and mToken price is 5$ and contract has 100 USDC and sufficient USTB and user in waivedFeeRestriction', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- ustbToken,
- ustbRedemption,
- } = await loadFixture(defaultDeploy);
-
- await ustbRedemption.setChainlinkData(parseUnits('1', 8), false); // Set USTB price to $1
-
- await mintToken(stableCoins.usdc, redemptionVaultWithUSTB, 100);
- await mintToken(ustbToken, redemptionVaultWithUSTB, 15000);
- await mintToken(mTBILL, owner, 1000);
- await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 1000);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 100,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithUSTB, owner },
- owner.address,
- );
-
- await ustbRedemption.setMaxUstbRedemptionAmount(parseUnits('6000', 6));
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- waivedFee: true,
- },
- stableCoins.usdc,
- 1000,
- );
- });
-
- it('should fail: when redemption exceeds available USDC in USTB redemption contract', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- ustbToken,
- ustbRedemption,
- } = await loadFixture(defaultDeploy);
-
- await ustbRedemption.setChainlinkData(parseUnits('1', 8), false); // Set USTB price to $1
-
- await mintToken(ustbToken, redemptionVaultWithUSTB, 1000000);
- await mintToken(stableCoins.usdc, redemptionVaultWithUSTB, 100);
-
- const mockBalance = await stableCoins.usdc.balanceOf(
- ustbRedemption.address,
- );
- await ustbRedemption.withdraw(
- stableCoins.usdc.address,
- owner.address,
- mockBalance,
- );
-
- expect(await stableCoins.usdc.balanceOf(ustbRedemption.address)).to.equal(
- 0,
- );
-
- await mintToken(mTBILL, owner, 100000);
- await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 100000);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- // Try to redeem - should fail because USTB redemption has no USDC
- await expect(
- redemptionVaultWithUSTB['redeemInstant(address,uint256,uint256)'](
- stableCoins.usdc.address,
- parseUnits('10000'),
- 0,
- ),
- ).to.be.revertedWith('USTBRedemptionMock: InsufficientBalance');
- });
-
- it('redeem 100 mTBILL (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, redemptionVaultWithUSTB, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mTBILL,
- redemptionVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem 100 mTBILL when recipient == msg.sender (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, redemptionVaultWithUSTB, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mTBILL,
- redemptionVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient: regularAccounts[0],
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem 100 mTBILL when other fn overload is paused (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- redemptionVaultWithUSTB,
- encodeFnSelector('redeemInstant(address,uint256,uint256)'),
- );
- await mintToken(stableCoins.usdc, redemptionVaultWithUSTB, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mTBILL,
- redemptionVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem 100 mTBILL when other fn overload is paused', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- redemptionVaultWithUSTB,
- encodeFnSelector('redeemInstant(address,uint256,uint256,address)'),
- );
- await mintToken(stableCoins.usdc, redemptionVaultWithUSTB, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mTBILL,
- redemptionVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
- });
-
- describe('redeemRequest()', () => {
- it('should fail: when there is no token in vault', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'MV: token not exists',
- },
- );
- });
-
- it('should fail: when trying to redeem 0 amount', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 0,
- {
- revertMessage: 'RV: invalid amount',
- },
- );
- });
-
- it('should fail: when function paused', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- redemptionVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector('redeemRequest(address,uint256)');
- await pauseVaultFn(redemptionVaultWithUSTB, selector);
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: call with insufficient allowance', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'ERC20: insufficient allowance',
- },
- );
- });
-
- it('should fail: call with insufficient balance', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'ERC20: transfer amount exceeds balance',
- },
- );
- });
-
- it('should fail: dataFeed rate 0 ', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mockedAggregator,
- mockedAggregatorMToken,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await approveBase18(owner, stableCoins.dai, redemptionVaultWithUSTB, 10);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await mintToken(mTBILL, owner, 100_000);
- await setRoundData({ mockedAggregator }, 0);
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'DF: feed is deprecated',
- },
- );
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 0);
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'DF: feed is deprecated',
- },
- );
- });
-
- it('should fail: call for amount < minAmount', async () => {
- const {
- redemptionVaultWithUSTB,
- mockedAggregator,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mTBILL, owner, 100_000);
- await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 100_000);
-
- await setMinAmountTest(
- { vault: redemptionVaultWithUSTB, owner },
- 100_000,
- );
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 99_999,
- {
- revertMessage: 'RV: amount < min',
- },
- );
- });
-
- it('should fail: if some fee = 100%', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 10000,
- true,
- );
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'RV: amountMTokenIn < fee',
- },
- );
- });
-
- it('should fail: greenlist enabled and user not in greenlist ', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redemptionVaultWithUSTB.setGreenlistEnable(true);
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: user in blacklist ', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: user in sanctions list', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- regularAccounts[0],
- );
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('should fail: when function paused (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- customRecipient,
- } = await loadFixture(defaultDeploy);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- redemptionVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector(
- 'redeemRequest(address,uint256,address)',
- );
- await pauseVaultFn(redemptionVaultWithUSTB, selector);
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: greenlist enabled and recipient not in greenlist (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await redemptionVaultWithUSTB.setGreenlistEnable(true);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: recipient in blacklist (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- accessControl,
- regularAccounts,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- customRecipient,
- );
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: recipient in sanctions list (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- customRecipient,
- );
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.dai,
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('should fail: user try to redeem fiat in basic request (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 100);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- await redemptionVaultWithUSTB.MANUAL_FULLFILMENT_TOKEN(),
- 100,
- {
- revertMessage: 'RV: tokenOut == fiat',
- },
- );
- });
-
- it('should fail: user try to redeem fiat in basic request', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 100);
-
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- await redemptionVaultWithUSTB.MANUAL_FULLFILMENT_TOKEN(),
- 100,
- {
- revertMessage: 'RV: tokenOut == fiat',
- },
- );
- });
-
- it('redeem request 100 mTBILL, greenlist enabled and user in greenlist ', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- greenListableTester,
- mTokenToUsdDataFeed,
- accessControl,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redemptionVaultWithUSTB.setGreenlistEnable(true);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await mintToken(stableCoins.dai, redemptionVaultWithUSTB, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mTBILL,
- redemptionVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem request 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVaultWithUSTB, 100000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- });
-
- it('redeem request 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$ and token fee 1%', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVaultWithUSTB, 100000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- });
-
- it('redeem request 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$ without checking of minDepositAmount', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVaultWithUSTB, 100000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await redemptionVaultWithUSTB.freeFromMinAmount(owner.address, true);
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.dai,
- 100,
- );
- });
-
- it('redeem request 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$ and user in waivedFeeRestriction', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVaultWithUSTB, 100000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVaultWithUSTB, 100);
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.dai,
- dataFeed.address,
- 100,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await addWaivedFeeAccountTest(
- { vault: redemptionVaultWithUSTB, owner },
- owner.address,
- );
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- waivedFee: true,
- },
- stableCoins.dai,
- 100,
- );
- });
-
- it('redeem request 100 mTBILL (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, redemptionVaultWithUSTB, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mTBILL,
- redemptionVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem request 100 mTBILL when recipient == msg.sender (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.usdc, redemptionVaultWithUSTB, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mTBILL,
- redemptionVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient: regularAccounts[0],
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem request 100 mTBILL when other fn overload is paused (custom recipient overload)', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- customRecipient,
- } = await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- redemptionVaultWithUSTB,
- encodeFnSelector('redeemRequest(address,uint256)'),
- );
- await mintToken(stableCoins.usdc, redemptionVaultWithUSTB, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mTBILL,
- redemptionVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- customRecipient,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem request 100 mTBILL when other fn overload is paused', async () => {
- const {
- owner,
- redemptionVaultWithUSTB,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVaultFn(
- redemptionVaultWithUSTB,
- encodeFnSelector('redeemRequest(address,uint256,address)'),
- );
- await mintToken(stableCoins.usdc, redemptionVaultWithUSTB, 100000);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- mTBILL,
- redemptionVaultWithUSTB,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVaultWithUSTB, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemRequestTest(
- {
- redemptionVault: redemptionVaultWithUSTB,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
- });
-
- describe('redeemFiatRequest()', () => {
- it('should fail: when trying to redeem 0 amount', async () => {
- const {
- owner,
- redemptionVaultWithUSTB: redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 0,
- {
- from: regularAccounts[0],
- revertMessage: 'RV: invalid amount',
- },
- );
- });
-
- it('should fail: call with insufficient allowance', async () => {
- const {
- owner,
- redemptionVaultWithUSTB: redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100);
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 100,
- {
- revertMessage: 'ERC20: insufficient allowance',
- },
- );
- });
-
- it('should fail: when function paused', async () => {
- const {
- owner,
- redemptionVaultWithUSTB: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- regularAccounts,
- } = await loadFixture(defaultDeploy);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- redemptionVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- const selector = encodeFnSelector('redeemFiatRequest(uint256)');
- await pauseVaultFn(redemptionVault, selector);
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: fn paused',
- },
- );
- });
-
- it('should fail: call with insufficient balance', async () => {
- const {
- owner,
- redemptionVaultWithUSTB: redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
-
- 100,
- {
- revertMessage: 'ERC20: transfer amount exceeds balance',
- },
- );
- });
-
- it('should fail: dataFeed rate 0 ', async () => {
- const {
- owner,
- redemptionVaultWithUSTB: redemptionVault,
- mTBILL,
- mockedAggregatorMToken,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100_000);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 0);
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 10,
- {
- revertMessage: 'DF: feed is deprecated',
- },
- );
- });
-
- it('should fail: call for amount < minFiatRedeemAmount', async () => {
- const {
- redemptionVaultWithUSTB: redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100_000);
- await approveBase18(owner, mTBILL, redemptionVault, 100_000);
-
- await setMinFiatRedeemAmountTest({ redemptionVault, owner }, 100_000);
-
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 99_999,
- {
- revertMessage: 'RV: amount < min',
- },
- );
- });
-
- it('should fail: if some fee = 100%', async () => {
- const {
- owner,
- redemptionVaultWithUSTB: redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await setFiatAdditionalFeeTest({ redemptionVault, owner }, 10000);
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 100,
- {
- revertMessage: 'RV: amountMTokenIn < fee',
- },
- );
- });
-
- it('should fail: greenlist enabled and user not in greenlist ', async () => {
- const {
- owner,
- redemptionVaultWithUSTB: redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await redemptionVault.setGreenlistEnable(true);
-
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: user in blacklist ', async () => {
- const {
- owner,
- redemptionVaultWithUSTB: redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- blackListableTester,
- regularAccounts,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await blackList(
- { blacklistable: blackListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 1,
- {
- from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HAS_ROLE,
- },
- );
- });
-
- it('should fail: user in sanctions list', async () => {
- const {
- owner,
- redemptionVaultWithUSTB: redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- mockedSanctionsList,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await sanctionUser(
- { sanctionsList: mockedSanctionsList },
- regularAccounts[0],
- );
-
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 1,
- {
- from: regularAccounts[0],
- revertMessage: 'WSL: sanctioned',
- },
- );
- });
-
- it('redeem fiat request 100 mTBILL, greenlist enabled and user in greenlist ', async () => {
- const {
- owner,
- redemptionVaultWithUSTB: redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- regularAccounts,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await redemptionVault.setGreenlistEnable(true);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- regularAccounts[0],
- );
-
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
-
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 100,
- {
- from: regularAccounts[0],
- },
- );
- });
-
- it('redeem fiat request 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithUSTB: redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 100,
- );
- });
-
- it('redeem fiat request 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$ and token fee 1%', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithUSTB: redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 100,
- );
- });
-
- it('redeem fiat request 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$ without checking of minDepositAmount', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithUSTB: redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await redemptionVault.freeFromMinAmount(owner.address, true);
-
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 100,
- );
- });
-
- it('redeem fiat request 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$ and user in waivedFeeRestriction', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithUSTB: redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await addWaivedFeeAccountTest(
- { vault: redemptionVault, owner },
- owner.address,
- );
- await redeemFiatRequestTest(
- {
- redemptionVault,
- owner,
- mTBILL,
- mTokenToUsdDataFeed,
- waivedFee: true,
- },
- 100,
- );
- });
- });
-
- describe('approveRequest()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- redemptionVaultWithUSTB: redemptionVault,
- regularAccounts,
- mTokenToUsdDataFeed,
- mTBILL,
- } = await loadFixture(defaultDeploy);
- await approveRedeemRequestTest(
- {
- redemptionVault,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 1,
- parseUnits('1'),
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- redemptionVaultWithUSTB: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await approveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 1,
- parseUnits('1'),
- {
- revertMessage: 'RV: request not exist',
- },
- );
- });
-
- it('should fail: request already processed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithUSTB: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await approveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('1'),
- );
- await approveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('1'),
- { revertMessage: 'RV: request not pending' },
- );
- });
-
- it('approve request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithUSTB: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await approveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('1'),
- );
- });
- });
-
- describe('approveRequest() with fiat', async () => {
- it('approve request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithUSTB: redemptionVault,
- mTBILL,
- mTokenToUsdDataFeed,
- greenListableTester,
- accessControl,
- } = await loadFixture(defaultDeploy);
-
- await greenList(
- { greenlistable: greenListableTester, accessControl, owner },
- owner,
- );
-
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemFiatRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 100,
- );
- const requestId = 0;
- await changeTokenAllowanceTest(
- { vault: redemptionVault, owner },
- constants.AddressZero,
- parseUnits('100'),
- );
-
- await approveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('1'),
- );
- });
- });
-
- describe('safeApproveRequest()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- redemptionVaultWithUSTB: redemptionVault,
- regularAccounts,
- mTokenToUsdDataFeed,
- mTBILL,
- } = await loadFixture(defaultDeploy);
- await safeApproveRedeemRequestTest(
- {
- redemptionVault,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 1,
- parseUnits('1'),
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- redemptionVaultWithUSTB: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 1,
- parseUnits('1'),
- {
- revertMessage: 'RV: request not exist',
- },
- );
- });
-
- it('should fail: if new rate greater then variabilityTolerance', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithUSTB: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('6'),
- { revertMessage: 'MV: exceed price diviation' },
- );
- });
-
- it('should fail: request already processed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithUSTB: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('5.000001'),
- );
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('5.00001'),
- { revertMessage: 'RV: request not pending' },
- );
- });
-
- it('safe approve request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithUSTB: redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('5.000001'),
- );
- });
- });
-
- describe('rejectRequest()', async () => {
- it('should fail: call from address without vault admin role', async () => {
- const {
- redemptionVaultWithUSTB: redemptionVault,
- regularAccounts,
- mTokenToUsdDataFeed,
- mTBILL,
- } = await loadFixture(defaultDeploy);
- await rejectRedeemRequestTest(
- {
- redemptionVault,
- owner: regularAccounts[1],
- mTBILL,
- mTokenToUsdDataFeed,
- },
- 1,
- {
- revertMessage: 'WMAC: hasnt role',
- },
- );
- });
-
- it('should fail: request by id not exist', async () => {
- const {
- owner,
- redemptionVaultWithUSTB: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await rejectRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- 1,
- {
- revertMessage: 'RV: request not exist',
- },
- );
- });
-
- it('should fail: request already processed', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithUSTB: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1.001);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await rejectRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- );
- await rejectRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- { revertMessage: 'RV: request not pending' },
- );
- });
-
- it('safe approve request from vaut admin account', async () => {
- const {
- owner,
- mockedAggregator,
- mockedAggregatorMToken,
- redemptionVaultWithUSTB: redemptionVault,
- stableCoins,
- mTBILL,
- mTokenToUsdDataFeed,
- dataFeed,
- } = await loadFixture(defaultDeploy);
-
- await mintToken(stableCoins.dai, redemptionVault, 100000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await setRoundData({ mockedAggregator }, 1.03);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- );
- const requestId = 0;
-
- await rejectRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- );
- });
- });
-
- describe('redeemRequest() complex', () => {
- it('should fail: when is paused', async () => {
- const {
- redemptionVaultWithUSTB: redemptionVault,
- owner,
- mTBILL,
- stableCoins,
- regularAccounts,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVault(redemptionVault);
- await mintToken(stableCoins.dai, redemptionVault, 100);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.dai,
- redemptionVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: paused',
- },
- );
- });
-
- it('is on pause, but admin can use everything', async () => {
- const {
- redemptionVaultWithUSTB: redemptionVault,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVault(redemptionVault);
-
- await mintToken(stableCoins.dai, redemptionVault, 1000);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, stableCoins.dai, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100,
- {
- revertMessage: 'Pausable: paused',
- },
- );
- });
-
- it('call for amount == minAmount, then approve', async () => {
- const {
- redemptionVaultWithUSTB: redemptionVault,
- mockedAggregator,
- mockedAggregatorMToken,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- requestRedeemer,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mTBILL, owner, 100_000);
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await approveBase18(owner, mTBILL, redemptionVault, 100_000);
-
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setMinAmountTest({ vault: redemptionVault, owner }, 100_000);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100_000,
- );
-
- const requestId = 0;
-
- await approveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('1'),
- );
- });
-
- it('call for amount == minAmount, then safe approve', async () => {
- const {
- redemptionVaultWithUSTB: redemptionVault,
- mockedAggregator,
- mockedAggregatorMToken,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- requestRedeemer,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mTBILL, owner, 100_000);
- await mintToken(stableCoins.dai, requestRedeemer, 100000);
- await approveBase18(
- requestRedeemer,
- stableCoins.dai,
- redemptionVault,
- 100000,
- );
- await approveBase18(owner, mTBILL, redemptionVault, 100_000);
-
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setMinAmountTest({ vault: redemptionVault, owner }, 10_000);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 10_000,
- );
-
- const requestId = 0;
-
- await safeApproveRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- parseUnits('1.000001'),
- );
- });
-
- it('call for amount == minAmount, then reject', async () => {
- const {
- redemptionVaultWithUSTB: redemptionVault,
- mockedAggregator,
- mockedAggregatorMToken,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.dai,
- dataFeed.address,
- 0,
- true,
- );
- await setRoundData({ mockedAggregator }, 1);
-
- await mintToken(mTBILL, owner, 100_000);
- await mintToken(stableCoins.dai, redemptionVault, 100_000);
- await approveBase18(owner, mTBILL, redemptionVault, 100_000);
-
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
- await setMinAmountTest({ vault: redemptionVault, owner }, 100_000);
-
- await redeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.dai,
- 100_000,
- );
-
- const requestId = 0;
-
- await rejectRedeemRequestTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- +requestId,
- );
- });
- });
-
- describe('redeemInstant() complex', () => {
- it('should fail: when is paused', async () => {
- const {
- redemptionVaultWithUSTB: redemptionVault,
- owner,
- mTBILL,
- stableCoins,
- regularAccounts,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVault(redemptionVault);
- await mintToken(stableCoins.usdc, redemptionVault, 100);
- await mintToken(mTBILL, regularAccounts[0], 100);
- await approveBase18(
- regularAccounts[0],
- stableCoins.usdc,
- redemptionVault,
- 100,
- );
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.usdc,
- 100,
- {
- from: regularAccounts[0],
- revertMessage: 'Pausable: paused',
- },
- );
- });
-
- it('is on pause, but admin can use everything', async () => {
- const {
- redemptionVaultWithUSTB: redemptionVault,
- owner,
- mTBILL,
- stableCoins,
- dataFeed,
- mTokenToUsdDataFeed,
- } = await loadFixture(defaultDeploy);
-
- await pauseVault(redemptionVault);
-
- await mintToken(stableCoins.usdc, redemptionVault, 100);
- await mintToken(mTBILL, owner, 100);
- await approveBase18(owner, mTBILL, redemptionVault, 100);
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.usdc,
- 100,
- {
- revertMessage: 'Pausable: paused',
- },
- );
- });
-
- it('redeem 100 mtbill, when price is 5$ and contract balance 100 USDC and USTB to cover remaining amount, 125 mtbill when price is 5.1$, 114 mtbill when price is 5.4$', async () => {
- const {
- owner,
- mockedAggregator,
- redemptionVaultWithUSTB: redemptionVault,
- stableCoins,
- mTBILL,
- dataFeed,
- mTokenToUsdDataFeed,
- mockedAggregatorMToken,
- ustbToken,
- ustbRedemption,
- } = await loadFixture(defaultDeploy);
-
- await ustbRedemption.setChainlinkData(parseUnits('1', 8), false); // Set USTB price to $1
-
- await mintToken(mTBILL, owner, 100 + 125 + 114);
-
- await mintToken(stableCoins.usdc, redemptionVault, 100);
- await mintToken(ustbToken, redemptionVault, 100000);
-
- await approveBase18(owner, mTBILL, redemptionVault, 100 + 125 + 114);
-
- await addPaymentTokenTest(
- { vault: redemptionVault, owner },
- stableCoins.usdc,
- dataFeed.address,
- 0,
- true,
- );
-
- await ustbRedemption.setMaxUstbRedemptionAmount(parseUnits('10000', 6));
-
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
- await setRoundData({ mockedAggregator }, 1.04);
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.usdc,
- 100,
- );
-
- await setRoundData({ mockedAggregator }, 1);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5.1);
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.usdc,
- 125,
- );
-
- await setRoundData({ mockedAggregator }, 1.01);
- await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5.4);
- await redeemInstantTest(
- { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
- stableCoins.usdc,
- 114,
- );
- });
- });
-});
+ },
+);
diff --git a/test/unit/WithSanctionsList.test.ts b/test/unit/WithSanctionsList.test.ts
index f25b6e11..344319b1 100644
--- a/test/unit/WithSanctionsList.test.ts
+++ b/test/unit/WithSanctionsList.test.ts
@@ -2,8 +2,13 @@ import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
import { expect } from 'chai';
import { constants } from 'ethers';
+import { encodeFnSelector } from '../../helpers/utils';
import { WithSanctionsListTester__factory } from '../../typechain-types';
-import { acErrors } from '../common/ac.helpers';
+import {
+ acErrors,
+ setPermissionRoleTester,
+ setupGrantOperatorRole,
+} from '../common/ac.helpers';
import { defaultDeploy } from '../common/fixtures';
import {
sanctionUser,
@@ -29,8 +34,7 @@ describe('WithSanctionsList', function () {
).deploy();
await expect(
- withSanctionsList.initializeWithoutInitializer(
- constants.AddressZero,
+ withSanctionsList.initializeUnchainedWithoutInitializer(
constants.AddressZero,
),
).revertedWith('Initializable: contract is not initializing');
@@ -56,7 +60,7 @@ describe('WithSanctionsList', function () {
withSanctionsListTester.onlyNotSanctionedTester(
regularAccounts[0].address,
),
- ).revertedWith('WSL: sanctioned');
+ ).revertedWithCustomError(withSanctionsListTester, 'Sanctioned');
});
it('call from not sanctioned user', async () => {
@@ -82,7 +86,7 @@ describe('WithSanctionsList', function () {
constants.AddressZero,
{
from: regularAccounts[0],
- revertMessage: acErrors.WMAC_HASNT_ROLE,
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(),
},
);
});
@@ -91,7 +95,7 @@ describe('WithSanctionsList', function () {
const { accessControl, withSanctionsListTester, owner } =
await loadFixture(defaultDeploy);
- await accessControl.grantRole(
+ await accessControl['grantRole(bytes32,address)'](
await withSanctionsListTester.sanctionsListAdminRole(),
owner.address,
);
@@ -101,6 +105,100 @@ describe('WithSanctionsList', function () {
constants.AddressZero,
);
});
+
+ it('succeeds with only scoped function permission', async () => {
+ const { accessControl, withSanctionsListTester, owner, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ const sanctionsListAdmin =
+ await withSanctionsListTester.sanctionsListAdminRole();
+ const selector = encodeFnSelector('setSanctionsList(address)');
+
+ await accessControl['grantRole(bytes32,address)'](
+ sanctionsListAdmin,
+ owner.address,
+ );
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: sanctionsListAdmin,
+ targetContract: withSanctionsListTester.address,
+ functionSelector: selector,
+ grantOperator: owner,
+ });
+
+ const user = regularAccounts[0];
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ withSanctionsListTester.address,
+ selector,
+ [
+ {
+ account: user.address,
+ enabled: true,
+ },
+ ],
+ );
+ expect(await accessControl.hasRole(sanctionsListAdmin, user.address)).eq(
+ false,
+ );
+
+ await setSanctionsList(
+ { withSanctionsList: withSanctionsListTester, owner },
+ constants.AddressZero,
+ { from: user },
+ );
+ });
+
+ it('succeeds with scoped permission and sanctions list admin role', async () => {
+ const { accessControl, withSanctionsListTester, owner, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ const sanctionsListAdmin =
+ await withSanctionsListTester.sanctionsListAdminRole();
+ const selector = encodeFnSelector('setSanctionsList(address)');
+ const user = regularAccounts[0];
+
+ await accessControl['grantRole(bytes32,address)'](
+ sanctionsListAdmin,
+ owner.address,
+ );
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: sanctionsListAdmin,
+ targetContract: withSanctionsListTester.address,
+ functionSelector: selector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ withSanctionsListTester.address,
+ selector,
+ [
+ {
+ account: user.address,
+ enabled: true,
+ },
+ ],
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ sanctionsListAdmin,
+ user.address,
+ );
+
+ await setSanctionsList(
+ { withSanctionsList: withSanctionsListTester, owner },
+ constants.AddressZero,
+ { from: user },
+ );
+ });
});
describe('onlyNotSanctionedTester()', () => {
@@ -113,7 +211,7 @@ describe('WithSanctionsList', function () {
regularAccounts,
} = await loadFixture(defaultDeploy);
- await accessControl.grantRole(
+ await accessControl['grantRole(bytes32,address)'](
await withSanctionsListTester.sanctionsListAdminRole(),
owner.address,
);
diff --git a/test/unit/misc/AcreAdapter.test.ts b/test/unit/misc/AcreAdapter.test.ts
index 55e4e893..eae77c4f 100644
--- a/test/unit/misc/AcreAdapter.test.ts
+++ b/test/unit/misc/AcreAdapter.test.ts
@@ -1,25 +1,21 @@
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
import { expect } from 'chai';
-import { constants } from 'ethers';
import { parseUnits } from 'ethers/lib/utils';
-import {
- AcreAdapter__factory,
- DepositVaultTest__factory,
-} from '../../../typechain-types';
+import { AcreAdapter__factory } from '../../../typechain-types';
import { acreAdapterFixture } from '../../common/fixtures';
import {
addPaymentTokenTest,
- addWaivedFeeAccountTest,
changeTokenFeeTest,
removePaymentTokenTest,
- removeWaivedFeeAccountTest,
+ setWaivedFeeAccountTest,
setInstantFeeTest,
} from '../../common/manageable-vault.helpers';
import {
acreWrapperDepositTest,
acreWrapperRequestRedeemTest,
} from '../../common/misc/acre.helpers';
+import { initializeDv } from '../../common/vault-initializer.helpers';
describe('AcreAdapter', () => {
it('initialize', async () => {
@@ -42,30 +38,10 @@ describe('AcreAdapter', () => {
fixture.mTokenToUsdDataFeed.address,
);
- const dvWithDifferentDataFeed = await new DepositVaultTest__factory(
- fixture.owner,
- ).deploy();
-
- await dvWithDifferentDataFeed.initialize(
- fixture.accessControl.address,
- {
- mToken: fixture.mTBILL.address,
- mTokenDataFeed: fixture.dataFeed.address,
- },
- {
- feeReceiver: fixture.feeReceiver.address,
- tokensReceiver: fixture.tokensReceiver.address,
- },
- {
- instantFee: 100,
- instantDailyLimit: parseUnits('100000'),
- },
- fixture.mockedSanctionsList.address,
- 1,
- parseUnits('100'),
- 0,
- constants.MaxUint256,
- );
+ const dvWithDifferentDataFeed = await initializeDv({
+ ...fixture,
+ mTokenToUsdDataFeed: fixture.dataFeedGrowth,
+ });
await expect(
fixture.owner.sendTransaction(
@@ -81,7 +57,7 @@ describe('AcreAdapter', () => {
fixture.owner.sendTransaction(
new AcreAdapter__factory().getDeployTransaction(
fixture.depositVault.address,
- fixture.redemptionVaultWithSwapper.address,
+ fixture.redemptionVaultLoanSwapper.address,
fixture.stableCoins.usdc.address,
),
),
@@ -119,9 +95,10 @@ describe('AcreAdapter', () => {
10,
);
- await addWaivedFeeAccountTest(
+ await setWaivedFeeAccountTest(
{ vault: fixture.depositVault, owner: fixture.owner },
fixture.acreUsdcMTbillAdapter.address,
+ true,
);
await acreWrapperDepositTest(fixture, 100);
});
@@ -154,7 +131,10 @@ describe('AcreAdapter', () => {
);
await acreWrapperDepositTest(fixture, 100, undefined, {
- revertMessage: 'DV: minReceiveAmount > actual',
+ revertCustomError: {
+ contract: fixture.depositVault,
+ customErrorName: 'SlippageExceeded',
+ },
});
});
@@ -168,7 +148,10 @@ describe('AcreAdapter', () => {
);
await acreWrapperDepositTest(fixture, 100, undefined, {
- revertMessage: 'DV: minReceiveAmount > actual',
+ revertCustomError: {
+ contract: fixture.depositVault,
+ customErrorName: 'SlippageExceeded',
+ },
});
});
});
@@ -246,9 +229,10 @@ describe('AcreAdapter', () => {
it('should fail: when not fee waived', async () => {
const fixture = await loadFixture(acreAdapterFixture);
- await removeWaivedFeeAccountTest(
+ await setWaivedFeeAccountTest(
{ vault: fixture.redemptionVault, owner: fixture.owner },
fixture.acreUsdcMTbillAdapter.address,
+ false,
);
await acreWrapperRequestRedeemTest(fixture, 20, undefined, {
@@ -296,9 +280,10 @@ describe('AcreAdapter', () => {
it('should not account any fees', async () => {
const fixture = await loadFixture(acreAdapterFixture);
- await removeWaivedFeeAccountTest(
+ await setWaivedFeeAccountTest(
{ vault: fixture.redemptionVault, owner: fixture.owner },
fixture.acreUsdcMTbillAdapter.address,
+ false,
);
await setInstantFeeTest(
diff --git a/test/unit/Axelar.test.ts b/test/unit/misc/Axelar.test.ts
similarity index 94%
rename from test/unit/Axelar.test.ts
rename to test/unit/misc/Axelar.test.ts
index 3af62094..6b07a9ac 100644
--- a/test/unit/Axelar.test.ts
+++ b/test/unit/misc/Axelar.test.ts
@@ -7,17 +7,17 @@ import { constants, ethers } from 'ethers';
import { parseUnits } from 'ethers/lib/utils';
import hre from 'hardhat';
-import { MidasAxelarVaultExecutableTester } from '../../typechain-types';
-import { depositAndSend, redeemAndSend } from '../common/axelar.helpers';
-import { approveBase18, mintToken } from '../common/common.helpers';
-import { setRoundData } from '../common/data-feed.helpers';
-import { deployProxyContract } from '../common/deploy.helpers';
-import { axelarFixture } from '../common/fixtures';
+import { MidasAxelarVaultExecutableTester } from '../../../typechain-types';
+import { depositAndSend, redeemAndSend } from '../../common/axelar.helpers';
+import { approveBase18, mintToken } from '../../common/common.helpers';
+import { setRoundData } from '../../common/data-feed.helpers';
+import { deployProxyContract } from '../../common/deploy.helpers';
+import { axelarFixture } from '../../common/fixtures';
import {
addPaymentTokenTest,
setInstantFeeTest,
setMinAmountTest,
-} from '../common/manageable-vault.helpers';
+} from '../../common/manageable-vault.helpers';
describe('Axelar', function () {
describe('MidasAxelarVaultExecutable', () => {
@@ -29,7 +29,6 @@ describe('Axelar', function () {
mTBILL,
chainNameHashA,
axelarItsA,
- mTokenToUsdDataFeed,
depositVault,
redemptionVault,
paymentTokenId,
@@ -245,7 +244,10 @@ describe('Axelar', function () {
minReceiveAmount: parseUnits('101'),
},
{
- revertMessage: 'DV: minReceiveAmount > actual',
+ revertCustomError: {
+ contract: depositVault,
+ customErrorName: 'SlippageExceeded',
+ },
},
);
});
@@ -429,7 +431,10 @@ describe('Axelar', function () {
minReceiveAmount: parseUnits('1000'),
},
{
- revertMessage: 'RV: minReceiveAmount > actual',
+ revertCustomError: {
+ contract: redemptionVault,
+ customErrorName: 'SlippageExceeded',
+ },
},
);
});
@@ -699,20 +704,7 @@ describe('Axelar', function () {
).eq(parseUnits('100', 8));
await expect(executor.redeemPublic(owner.address, parseUnits('50'), 0))
- .emit(
- redemptionVault,
- redemptionVault.interface.events[
- 'RedeemInstantWithCustomRecipient(address,address,address,uint256,uint256,uint256)'
- ].name,
- )
- .withArgs(
- executor.address,
- stableCoins.usdc.address,
- owner.address,
- parseUnits('50'),
- 0,
- parseUnits('100'),
- );
+ .not.reverted;
});
});
@@ -905,23 +897,7 @@ describe('Axelar', function () {
0,
constants.HashZero,
),
- )
- .emit(
- depositVault,
- depositVault.interface.events[
- 'DepositInstantWithCustomRecipient(address,address,address,uint256,uint256,uint256,uint256,bytes32)'
- ].name,
- )
- .withArgs(
- executor.address,
- stableCoins.usdc.address,
- owner.address,
- parseUnits('100'),
- parseUnits('100'),
- 0,
- parseUnits('50'),
- constants.HashZero,
- );
+ ).not.reverted;
});
});
diff --git a/test/unit/BandProtocolAdapter.test.ts b/test/unit/misc/BandProtocolAdapter.test.ts
similarity index 97%
rename from test/unit/BandProtocolAdapter.test.ts
rename to test/unit/misc/BandProtocolAdapter.test.ts
index 6c80f334..fa65cce3 100644
--- a/test/unit/BandProtocolAdapter.test.ts
+++ b/test/unit/misc/BandProtocolAdapter.test.ts
@@ -7,9 +7,9 @@ import {
DataFeedToBandStdAdapter__factory,
CompositeDataFeedToBandStdAdapter__factory,
CompositeDataFeedTest__factory,
-} from '../../typechain-types';
-import { setRoundData } from '../common/data-feed.helpers';
-import { defaultDeploy } from '../common/fixtures';
+} from '../../../typechain-types';
+import { setRoundData } from '../../common/data-feed.helpers';
+import { defaultDeploy } from '../../common/fixtures';
describe('DataFeedToBandStdAdapter', function () {
const baseSymbol = 'mTBILL';
@@ -165,8 +165,9 @@ describe('DataFeedToBandStdAdapter', function () {
expect(referenceData.rate).eq(parseUnits(largePrice));
});
- it('should fail when underlying feed is unhealthy', async () => {
- const { owner, dataFeedUnhealthy } = fixture;
+ it('should fail: when underlying feed is unhealthy', async () => {
+ const { owner, deployUnhealthyFeed } = fixture;
+ const { dataFeedUnhealthy } = await deployUnhealthyFeed();
const dataFeedToBandStdAdapter =
await new DataFeedToBandStdAdapter__factory(owner).deploy(
@@ -180,9 +181,10 @@ describe('DataFeedToBandStdAdapter', function () {
).to.be.reverted;
});
- it('should fail when underlying feed is deprecated', async () => {
- const { owner, dataFeedDeprecated } = fixture;
+ it('should fail: when underlying feed is deprecated', async () => {
+ const { owner, deployDeprecatedFeed } = fixture;
+ const { dataFeedDeprecated } = await deployDeprecatedFeed();
const dataFeedToBandStdAdapter =
await new DataFeedToBandStdAdapter__factory(owner).deploy(
dataFeedDeprecated.address,
@@ -368,7 +370,7 @@ describe('DataFeedToBandStdAdapter', function () {
});
describe('Error handling', () => {
- it('should fail with proper error messages for invalid pairs', async () => {
+ it('should fail: with proper error messages for invalid pairs', async () => {
const { dataFeedToBandStdAdapter } = fixture;
// Test various invalid combinations
@@ -385,7 +387,7 @@ describe('DataFeedToBandStdAdapter', function () {
).revertedWith('DFBSA: unsupported pair');
});
- it('should fail with proper error messages for bulk operations', async () => {
+ it('should fail: with proper error messages for bulk operations', async () => {
const { dataFeedToBandStdAdapter } = fixture;
await expect(
@@ -638,7 +640,7 @@ describe('CompositeDataFeedToBandStdAdapter', function () {
expect(referenceData.rate).eq(parseUnits(largeNumerator));
});
- it('should fail when underlying composite feed is unhealthy', async () => {
+ it('should fail: when underlying composite feed is unhealthy', async () => {
const { owner, compositeDataFeed } = fixture;
// Create a new composite feed with unhealthy settings
@@ -1006,7 +1008,7 @@ describe('CompositeDataFeedToBandStdAdapter', function () {
});
describe('Error handling', () => {
- it('should fail with proper error messages for invalid pairs', async () => {
+ it('should fail: with proper error messages for invalid pairs', async () => {
const { compositeDataFeedToBandStdAdapter } = fixture;
// Test various invalid combinations
@@ -1029,7 +1031,7 @@ describe('CompositeDataFeedToBandStdAdapter', function () {
).revertedWith('DFBSA: unsupported pair');
});
- it('should fail with proper error messages for bulk operations', async () => {
+ it('should fail: with proper error messages for bulk operations', async () => {
const { compositeDataFeedToBandStdAdapter } = fixture;
await expect(
diff --git a/test/unit/LayerZero.test.ts b/test/unit/misc/LayerZero.test.ts
similarity index 96%
rename from test/unit/LayerZero.test.ts
rename to test/unit/misc/LayerZero.test.ts
index 46f9ae7d..0510d7ab 100644
--- a/test/unit/LayerZero.test.ts
+++ b/test/unit/misc/LayerZero.test.ts
@@ -11,25 +11,25 @@ import hre from 'hardhat';
import {
MidasLzOFTAdapter__factory,
MidasLzVaultComposerSyncTester,
-} from '../../typechain-types';
-import { acErrors, blackList } from '../common/ac.helpers';
-import { approveBase18, mintToken } from '../common/common.helpers';
-import { setRoundData } from '../common/data-feed.helpers';
-import { deployProxyContract } from '../common/deploy.helpers';
-import { layerZeroFixture } from '../common/fixtures';
+} from '../../../typechain-types';
+import { acErrors, blackList } from '../../common/ac.helpers';
+import { approveBase18, mintToken } from '../../common/common.helpers';
+import { setRoundData } from '../../common/data-feed.helpers';
+import { deployProxyContract } from '../../common/deploy.helpers';
+import { layerZeroFixture } from '../../common/fixtures';
import {
depositAndSend,
redeemAndSend,
sendOft,
sendOftLockBox,
setRateLimitConfig,
-} from '../common/layerzero.helpers';
+} from '../../common/layerzero.helpers';
import {
addPaymentTokenTest,
setInstantFeeTest,
setMinAmountTest,
-} from '../common/manageable-vault.helpers';
-import { mint } from '../common/mTBILL.helpers';
+} from '../../common/manageable-vault.helpers';
+import { mint } from '../../common/mtoken.helpers';
describe('LayerZero', function () {
describe('MidasLzMintBurnOFTAdapter', () => {
@@ -127,7 +127,12 @@ describe('LayerZero', function () {
await sendOft(
fixture,
{ amount: 1000000 },
- { revertMessage: 'WMAC: hasnt role' },
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION(
+ undefined,
+ fixture.mTBILL,
+ ),
+ },
);
});
@@ -172,7 +177,7 @@ describe('LayerZero', function () {
{ amount: 100 },
{
from: blacklisted,
- revertMessage: acErrors.WMAC_HAS_ROLE,
+ revertCustomError: acErrors.WMAC_BLACKLISTED(undefined, mTBILL),
},
);
});
@@ -196,9 +201,9 @@ describe('LayerZero', function () {
fixture,
{ amount: 101 },
{
- revertWithCustomError: {
+ revertCustomError: {
contract: oftAdapterA,
- error: 'RateLimitExceeded',
+ customErrorName: 'RateLimitExceeded',
},
},
);
@@ -795,9 +800,9 @@ describe('LayerZero', function () {
},
{
overrideValue: '1',
- revertWithCustomError: {
+ revertCustomError: {
contract: composer,
- error: 'NoMsgValueExpected',
+ customErrorName: 'NoMsgValueExpected',
},
},
);
@@ -905,9 +910,9 @@ describe('LayerZero', function () {
minAmountLD: parseUnits('19.6264444441', 18),
},
{
- revertWithCustomError: {
+ revertCustomError: {
contract: oftAdapterA,
- error: 'SlippageExceeded',
+ customErrorName: 'SlippageExceeded',
},
},
);
@@ -1152,9 +1157,9 @@ describe('LayerZero', function () {
},
{
overrideValue: '1',
- revertWithCustomError: {
+ revertCustomError: {
contract: composer,
- error: 'NoMsgValueExpected',
+ customErrorName: 'NoMsgValueExpected',
},
},
);
@@ -1698,20 +1703,7 @@ describe('LayerZero', function () {
).eq(parseUnits('100', 8));
await expect(composer.redeemPublic(owner.address, parseUnits('50'), 0))
- .emit(
- redemptionVault,
- redemptionVault.interface.events[
- 'RedeemInstantWithCustomRecipient(address,address,address,uint256,uint256,uint256)'
- ].name,
- )
- .withArgs(
- composer.address,
- stableCoins.usdc.address,
- owner.address,
- parseUnits('50'),
- 0,
- parseUnits('100'),
- );
+ .not.reverted;
});
});
@@ -1897,23 +1889,7 @@ describe('LayerZero', function () {
0,
referrerId,
),
- )
- .emit(
- depositVault,
- depositVault.interface.events[
- 'DepositInstantWithCustomRecipient(address,address,address,uint256,uint256,uint256,uint256,bytes32)'
- ].name,
- )
- .withArgs(
- composer.address,
- stableCoins.usdc.address,
- owner.address,
- parseUnits('100'),
- parseUnits('100'),
- 0,
- parseUnits('50'),
- referrerId,
- );
+ ).not.reverted;
});
});
});
diff --git a/test/unit/mtoken.test.ts b/test/unit/mtoken.test.ts
index 76807887..14067c2f 100644
--- a/test/unit/mtoken.test.ts
+++ b/test/unit/mtoken.test.ts
@@ -1,477 +1,4234 @@
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
+import { increase } from '@nomicfoundation/hardhat-network-helpers/dist/src/helpers/time';
+import {
+ days,
+ hours,
+} from '@nomicfoundation/hardhat-network-helpers/dist/src/helpers/time/duration';
import { expect } from 'chai';
+import { Contract } from 'ethers';
import { parseUnits } from 'ethers/lib/utils';
+import { ethers } from 'hardhat';
-import { MTokenNameEnum } from '../../config';
-import { getRolesForToken, getRolesNamesForToken } from '../../helpers/roles';
-import { acErrors, blackList } from '../common/ac.helpers';
-import { defaultDeploy, mTokenPermissionedFixture } from '../common/fixtures';
-import { burn, mint } from '../common/mTBILL.helpers';
-import { tokenContractsTests } from '../common/token.tests';
+import { mTokensMetadata } from '../../helpers/mtokens-metadata';
+import { encodeFnSelector } from '../../helpers/utils';
+import { MToken } from '../../typechain-types';
+import {
+ acErrors,
+ blackList,
+ NO_DELAY,
+ setPermissionRoleTester,
+ setRoleTimelocksTester,
+ setupGrantOperatorRole,
+ unBlackList,
+} from '../common/ac.helpers';
+import {
+ adminPauseContractTest,
+ pauseGlobalTest,
+ pauseVault,
+ pauseVaultFn,
+} from '../common/common.helpers';
+import { deployProxyContract } from '../common/deploy.helpers';
+import {
+ defaultDeploy,
+ DefaultFixture,
+ mTokenMinBalanceFixture,
+ mTokenPermissionedFixture,
+ mTokenPermissionedMinBalanceFixture,
+} from '../common/fixtures';
+import {
+ burn,
+ clawbackTest,
+ decreaseMintRateLimitTest,
+ increaseMintRateLimitTest,
+ removeMintRateLimitTest,
+ mint,
+ setClawbackReceiverTest,
+ setMaxSupplyCapTest,
+ setMetadataTest,
+} from '../common/mtoken.helpers';
+import {
+ bulkScheduleTimelockOperationTester,
+ executeTimelockOperationTester,
+} from '../common/timelock-manager.helpers';
-const mProducts = Object.values(MTokenNameEnum);
+const DEFAULT_UNPAUSE_DELAY = 86400;
-describe('Token contracts', () => {
- mProducts.forEach((product) => {
- describe(`${product}`, () => {
- tokenContractsTests(product);
+const MINT_SEL = encodeFnSelector('mint(address,uint256)');
+const BURN_SEL = encodeFnSelector('burn(address,uint256)');
+const MINT_GOVERNED_SEL = encodeFnSelector('mintGoverned(address,uint256)');
+const BURN_GOVERNED_SEL = encodeFnSelector('burnGoverned(address,uint256)');
+const TRANSFER_SEL = encodeFnSelector('transfer(address,uint256)');
+const TRANSFER_FROM_SEL = encodeFnSelector(
+ 'transferFrom(address,address,uint256)',
+);
+const CLAWBACK_SEL = encodeFnSelector('clawback(uint256,address)');
+const SET_METADATA_SEL = encodeFnSelector('setMetadata(bytes32,bytes)');
+const SET_CLAWBACK_RECEIVER_SEL = encodeFnSelector(
+ 'setClawbackReceiver(address)',
+);
+const INCREASE_MINT_RATE_LIMIT_SEL = encodeFnSelector(
+ 'increaseMintRateLimit(uint256,uint256)',
+);
+const DECREASE_MINT_RATE_LIMIT_SEL = encodeFnSelector(
+ 'decreaseMintRateLimit(uint256,uint256)',
+);
+const REMOVE_MINT_RATE_LIMIT_SEL = encodeFnSelector(
+ 'removeMintRateLimitConfig(uint256)',
+);
+const ERC20_PAUSABLE_PAUSED_STORAGE_SLOT = 101;
+export const ERC20_PAUSED_MSG = 'ERC20Pausable: token transfer while paused';
+const PAUSE_TEST_AMOUNT = parseUnits('100');
+
+const SET_NAME_SYMBOL_DELAY = 2 * 24 * 3600;
+const SET_NAME_SYMBOL_SEL = encodeFnSelector('setNameSymbol(string,string)');
+const SET_IS_PERMISSIONED_DELAY = SET_NAME_SYMBOL_DELAY;
+const SET_IS_PERMISSIONED_SEL = encodeFnSelector('setIsPermissioned(bool)');
+const SET_MIN_HOLDING_BALANCE_ENFORCED_DELAY = SET_NAME_SYMBOL_DELAY;
+const SET_MIN_HOLDING_BALANCE_ENFORCED_SEL = encodeFnSelector(
+ 'setMinHoldingBalanceEnforced(bool)',
+);
+
+const toStorageSlotHex = (slot: number) =>
+ '0x' + slot.toString(16).padStart(64, '0');
+
+const PROXY_ADMIN_SLOT =
+ '0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103';
+
+const setProxyAdmin = (address: string, admin: string) =>
+ ethers.provider.send('hardhat_setStorageAt', [
+ address,
+ PROXY_ADMIN_SLOT,
+ ethers.utils.hexZeroPad(admin, 32),
+ ]);
+
+const setInitializedVersion = (address: string, version: number) =>
+ ethers.provider.send('hardhat_setStorageAt', [
+ address,
+ toStorageSlotHex(0),
+ ethers.utils.hexZeroPad(ethers.utils.hexlify(version), 32),
+ ]);
+
+const toBoolWordHex = (value: boolean) =>
+ '0x' + (value ? '1' : '0').padStart(64, '0');
+
+export const setErc20PausablePaused = async (
+ tokenContract: Contract,
+ paused = true,
+) => {
+ await ethers.provider.send('hardhat_setStorageAt', [
+ tokenContract.address,
+ toStorageSlotHex(ERC20_PAUSABLE_PAUSED_STORAGE_SLOT),
+ toBoolWordHex(paused),
+ ]);
+ expect(await tokenContract.paused()).eq(paused);
+};
+
+const pausedRevert = (tokenContract: MToken, selector: string) => ({
+ revertCustomError: {
+ customErrorName: 'Paused',
+ args: [tokenContract.address, selector],
+ },
+});
+
+const adminUnpauseContractViaTimelock = async (fixture: DefaultFixture) => {
+ const { pauseManager, timelockManager, timelock, owner, accessControl } =
+ fixture;
+ const calldata = pauseManager.interface.encodeFunctionData(
+ 'contractAdminUnpause',
+ [fixture.tokenContract.address],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [pauseManager.address],
+ [calldata],
+ );
+ await increase(DEFAULT_UNPAUSE_DELAY);
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ pauseManager.address,
+ calldata,
+ owner.address,
+ );
+};
+
+describe(`mToken`, function () {
+ it('deployment', async () => {
+ const { mTBILL } = await loadFixture(defaultDeploy);
+
+ const expected = mTokensMetadata['mTBILL'];
+ expect(await mTBILL.name()).eq(expected.name);
+ expect(await mTBILL.symbol()).eq(expected.symbol);
+
+ expect(await mTBILL.paused()).eq(false);
+
+ const limits = await mTBILL.getMintRateLimitStatuses();
+
+ expect(limits.length).eq(0);
+ });
+
+ it('roles', async () => {
+ const { mTBILL, roles } = await loadFixture(defaultDeploy);
+
+ const tokenRoles = roles.tokenRoles.mTBILL;
+
+ expect(await mTBILL.burnerRole()).eq(tokenRoles.burner);
+ expect(await mTBILL.minterRole()).eq(tokenRoles.minter);
+
+ expect(await mTBILL.contractAdminRole()).eq(tokenRoles.tokenManager);
+ expect(await mTBILL.greenlistedRole()).eq(tokenRoles.greenlisted);
+ expect(await mTBILL.minBalanceExemptRole()).eq(tokenRoles.minBalanceExempt);
+ expect(await mTBILL.isPermissioned()).eq(false);
+ expect(await mTBILL.isMinHoldingBalanceEnforced()).eq(false);
+ expect(await mTBILL.maxSupplyCap()).eq(ethers.constants.MaxUint256);
+ });
+
+ it('initialize and v2 initialize', async () => {
+ const { tokenContract, clawbackReceiver } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await expect(
+ tokenContract.initialize(
+ ethers.constants.AddressZero,
+ clawbackReceiver.address,
+ ethers.constants.MaxUint256,
+ false,
+ false,
+ mTokensMetadata.mTBILL.name,
+ mTokensMetadata.mTBILL.symbol,
+ ),
+ ).revertedWith('Initializable: contract is already initialized');
+
+ await expect(
+ tokenContract.initializeV3(
+ clawbackReceiver.address,
+ ethers.constants.MaxUint256,
+ false,
+ false,
+ ),
+ ).to.revertedWith('Initializable: contract is already initialized');
+ });
+
+ describe('initializeV3() proxy admin restriction', () => {
+ const deployMToken = async () => {
+ const { accessControl, clawbackReceiver } = await loadFixture(
+ defaultDeploy,
+ );
+
+ const mTBILL = await deployProxyContract(
+ 'mToken',
+ [
+ accessControl.address,
+ clawbackReceiver.address,
+ ethers.constants.MaxUint256,
+ false,
+ false,
+ mTokensMetadata.mTBILL.name,
+ mTokensMetadata.mTBILL.symbol,
+ ],
+ 'initialize',
+ [
+ ethers.utils.id('M_TOKEN_MANAGER_ROLE'),
+ ethers.utils.id('M_TOKEN_MINTER_ROLE'),
+ ethers.utils.id('M_TOKEN_BURNER_ROLE'),
+ ethers.utils.id('M_TOKEN_GREENLISTED_ROLE'),
+ ethers.utils.id('M_TOKEN_MIN_BALANCE_EXEMPT_ROLE'),
+ ],
+ );
+
+ return { mTBILL, clawbackReceiver };
+ };
+
+ it('fresh deploy: initializeV3 runs while proxy admin is zero', async () => {
+ const { mTBILL, clawbackReceiver } = await deployMToken();
+
+ expect(await mTBILL.clawbackReceiver()).eq(clawbackReceiver.address);
+ expect(await mTBILL.maxSupplyCap()).eq(ethers.constants.MaxUint256);
+ expect(await mTBILL.isPermissioned()).eq(false);
+ expect(await mTBILL.isMinHoldingBalanceEnforced()).eq(false);
+ });
+
+ it('fresh deploy: initializeV3 can enable feature flags', async () => {
+ const { accessControl, clawbackReceiver } = await loadFixture(
+ defaultDeploy,
+ );
+
+ const mTBILL = await deployProxyContract(
+ 'mToken',
+ [
+ accessControl.address,
+ clawbackReceiver.address,
+ ethers.constants.MaxUint256,
+ true,
+ true,
+ mTokensMetadata.mTBILL.name,
+ mTokensMetadata.mTBILL.symbol,
+ ],
+ 'initialize',
+ [
+ ethers.utils.id('M_TOKEN_MANAGER_ROLE'),
+ ethers.utils.id('M_TOKEN_MINTER_ROLE'),
+ ethers.utils.id('M_TOKEN_BURNER_ROLE'),
+ ethers.utils.id('M_TOKEN_GREENLISTED_ROLE'),
+ ethers.utils.id('M_TOKEN_MIN_BALANCE_EXEMPT_ROLE'),
+ ],
+ );
+
+ expect(await mTBILL.isPermissioned()).eq(true);
+ expect(await mTBILL.isMinHoldingBalanceEnforced()).eq(true);
+ });
+
+ it('should fail: when already reinitialized, even from the proxy admin', async () => {
+ const [, admin] = await ethers.getSigners();
+ const { mTBILL, clawbackReceiver } = await deployMToken();
+
+ await setProxyAdmin(mTBILL.address, admin.address);
+
+ await expect(
+ mTBILL
+ .connect(admin)
+ .initializeV3(
+ clawbackReceiver.address,
+ ethers.constants.MaxUint256,
+ false,
+ false,
+ ),
+ ).revertedWith('Initializable: contract is already initialized');
+ });
+
+ it('should fail: when initialized and caller is not the proxy admin', async () => {
+ const [, admin, stranger] = await ethers.getSigners();
+ const { mTBILL, clawbackReceiver } = await deployMToken();
+
+ await setProxyAdmin(mTBILL.address, admin.address);
+ await setInitializedVersion(mTBILL.address, 1);
+
+ await expect(
+ mTBILL
+ .connect(stranger)
+ .initializeV3(
+ clawbackReceiver.address,
+ ethers.constants.MaxUint256,
+ false,
+ false,
+ ),
+ ).revertedWithCustomError(mTBILL, 'SenderNotProxyAdmin');
+ });
+
+ it('when initialized and caller is the proxy admin', async () => {
+ const [, admin, newClawbackReceiver] = await ethers.getSigners();
+ const { mTBILL } = await deployMToken();
+
+ await setProxyAdmin(mTBILL.address, admin.address);
+ await setInitializedVersion(mTBILL.address, 1);
+
+ const maxSupplyCap = parseUnits('1000');
+
+ await mTBILL
+ .connect(admin)
+ .initializeV3(newClawbackReceiver.address, maxSupplyCap, true, true);
+
+ expect(await mTBILL.clawbackReceiver()).eq(newClawbackReceiver.address);
+ expect(await mTBILL.maxSupplyCap()).eq(maxSupplyCap);
+ expect(await mTBILL.isPermissioned()).eq(true);
+ expect(await mTBILL.isMinHoldingBalanceEnforced()).eq(true);
});
});
- describe('mTokenPermissioned (mTokenPermissionedTest)', () => {
- describe('transfer()', () => {
- it('should fail: transfer when sender is not greenlisted', async () => {
- const baseFixture = await defaultDeploy();
- const {
- owner,
- accessControl,
- regularAccounts,
- mTokenPermissioned,
- mTokenPermissionedRoles,
- } = await loadFixture(
- mTokenPermissionedFixture.bind(this, baseFixture),
- );
- const from = regularAccounts[0];
- const to = regularAccounts[1];
+ describe('setIsPermissioned()', () => {
+ it('should fail: when called directly', async () => {
+ const { mTBILL, accessControl, owner } = await loadFixture(defaultDeploy);
- await accessControl.grantRole(
- mTokenPermissionedRoles.greenlisted,
- from.address,
- );
- await mint({ tokenContract: mTokenPermissioned, owner }, from, 1);
- await accessControl.revokeRole(
- mTokenPermissionedRoles.greenlisted,
- from.address,
- );
- await accessControl.grantRole(
- mTokenPermissionedRoles.greenlisted,
- to.address,
- );
+ const tokenManagerRole = await mTBILL.contractAdminRole();
- await expect(
- mTokenPermissioned.connect(from).transfer(to.address, 1),
- ).revertedWith(acErrors.WMAC_HASNT_ROLE);
- });
+ await expect(mTBILL.connect(owner).setIsPermissioned(true))
+ .revertedWithCustomError(accessControl, 'SenderIsNotTimelock')
+ .withArgs(tokenManagerRole, SET_IS_PERMISSIONED_SEL, owner.address);
+ });
- it('should fail: transfer when recipient is not greenlisted', async () => {
- const baseFixture = await defaultDeploy();
- const {
- owner,
- accessControl,
- regularAccounts,
- mTokenPermissioned,
- mTokenPermissionedRoles,
- } = await loadFixture(
- mTokenPermissionedFixture.bind(this, baseFixture),
- );
+ it('should fail: call from address without token manager role', async () => {
+ const { mTBILL, accessControl, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
- const from = regularAccounts[0];
- const to = regularAccounts[1];
+ const caller = regularAccounts[0];
+ const tokenManagerRole = await mTBILL.contractAdminRole();
- await accessControl.grantRole(
- mTokenPermissionedRoles.greenlisted,
- from.address,
- );
- await mint({ tokenContract: mTokenPermissioned, owner }, from, 1);
+ await expect(
+ mTBILL.connect(caller).setIsPermissioned(true),
+ ).revertedWithCustomError(accessControl, 'NoFunctionPermission');
- await expect(
- mTokenPermissioned.connect(from).transfer(to.address, 1),
- ).revertedWith(acErrors.WMAC_HASNT_ROLE);
- });
+ expect(await accessControl.hasRole(tokenManagerRole, caller.address)).eq(
+ false,
+ );
+ expect(await mTBILL.isPermissioned()).eq(false);
+ });
- it('should fail: transfer when from is blacklisted', async () => {
- const baseFixture = await defaultDeploy();
- const {
- owner,
- accessControl,
- regularAccounts,
- mTokenPermissioned,
- mTokenPermissionedRoles,
- } = await loadFixture(
- mTokenPermissionedFixture.bind(this, baseFixture),
- );
+ it('should always require 2 days timelock even if contract admin/function admin role delay is different (no timelock for example)', async () => {
+ const { mTBILL, accessControl, owner, timelock, timelockManager } =
+ await loadFixture(defaultDeploy);
- const from = regularAccounts[0];
- const to = regularAccounts[1];
+ const tokenManagerRole = await mTBILL.contractAdminRole();
+ const functionRoleKey = await accessControl.permissionRoleKey(
+ tokenManagerRole,
+ mTBILL.address,
+ SET_IS_PERMISSIONED_SEL,
+ );
- await accessControl.grantRole(
- mTokenPermissionedRoles.greenlisted,
- from.address,
- );
- await accessControl.grantRole(
- mTokenPermissionedRoles.greenlisted,
- to.address,
- );
- await mint({ tokenContract: mTokenPermissioned, owner }, from, 1);
- await blackList(
- {
- blacklistable: mTokenPermissioned,
- accessControl,
- owner,
+ await setRoleTimelocksTester(
+ { accessControl, owner, timelock, timelockManager },
+ [tokenManagerRole, functionRoleKey],
+ [NO_DELAY, NO_DELAY],
+ );
+
+ await expect(mTBILL.connect(owner).setIsPermissioned(true))
+ .revertedWithCustomError(accessControl, 'SenderIsNotTimelock')
+ .withArgs(tokenManagerRole, SET_IS_PERMISSIONED_SEL, owner.address);
+
+ const calldata = mTBILL.interface.encodeFunctionData(
+ 'setIsPermissioned',
+ [true],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [mTBILL.address],
+ [calldata],
+ );
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ mTBILL.address,
+ calldata,
+ owner.address,
+ {
+ revertCustomError: {
+ contract: timelockManager,
+ customErrorName: 'TimelockOperationNotReady',
},
- from,
- );
+ },
+ );
- await expect(
- mTokenPermissioned.connect(from).transfer(to.address, 1),
- ).revertedWith(acErrors.WMAC_HAS_ROLE);
- });
+ await increase(SET_IS_PERMISSIONED_DELAY);
- it('should fail: transfer when token is paused', async () => {
- const baseFixture = await defaultDeploy();
- const {
- owner,
- accessControl,
- regularAccounts,
- mTokenPermissioned,
- mTokenPermissionedRoles,
- } = await loadFixture(
- mTokenPermissionedFixture.bind(this, baseFixture),
- );
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ mTBILL.address,
+ calldata,
+ owner.address,
+ );
- const from = regularAccounts[0];
- const to = regularAccounts[1];
+ expect(await mTBILL.isPermissioned()).eq(true);
+ });
- await accessControl.grantRole(
- mTokenPermissionedRoles.greenlisted,
- from.address,
- );
- await accessControl.grantRole(
- mTokenPermissionedRoles.greenlisted,
- to.address,
- );
- await mint({ tokenContract: mTokenPermissioned, owner }, from, 1);
+ it('when called through timelock manager with 2 days delay', async () => {
+ const { mTBILL, accessControl, owner, timelock, timelockManager } =
+ await loadFixture(defaultDeploy);
+
+ const calldata = mTBILL.interface.encodeFunctionData(
+ 'setIsPermissioned',
+ [true],
+ );
- await mTokenPermissioned.connect(owner).pause();
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [mTBILL.address],
+ [calldata],
+ );
- await expect(
- mTokenPermissioned.connect(from).transfer(to.address, 1),
- ).revertedWith('ERC20Pausable: token transfer while paused');
- });
+ await increase(SET_IS_PERMISSIONED_DELAY);
- it('should fail: mint when receiver is not greenlisted', async () => {
- const baseFixture = await defaultDeploy();
- const { owner, regularAccounts, mTokenPermissioned } =
- await loadFixture(mTokenPermissionedFixture.bind(this, baseFixture));
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ mTBILL.address,
+ calldata,
+ owner.address,
+ );
- await mint(
- { tokenContract: mTokenPermissioned, owner },
- regularAccounts[0],
- 1,
- { revertMessage: acErrors.WMAC_HASNT_ROLE },
- );
- });
+ expect(await mTBILL.isPermissioned()).eq(true);
+ });
- it('transfer when both parties are greenlisted', async () => {
- const baseFixture = await defaultDeploy();
- const {
- owner,
- accessControl,
- regularAccounts,
- mTokenPermissioned,
- mTokenPermissionedRoles,
- } = await loadFixture(
- mTokenPermissionedFixture.bind(this, baseFixture),
- );
+ it('does not change state when setting the same value', async () => {
+ const { mTBILL, accessControl, owner, timelock, timelockManager } =
+ await loadFixture(defaultDeploy);
- const from = regularAccounts[0];
- const to = regularAccounts[1];
+ const enableCalldata = mTBILL.interface.encodeFunctionData(
+ 'setIsPermissioned',
+ [true],
+ );
+ const sameValueCalldata = mTBILL.interface.encodeFunctionData(
+ 'setIsPermissioned',
+ [true],
+ );
- await accessControl.grantRole(
- mTokenPermissionedRoles.greenlisted,
- from.address,
- );
- await accessControl.grantRole(
- mTokenPermissionedRoles.greenlisted,
- to.address,
- );
- await mint({ tokenContract: mTokenPermissioned, owner }, from, 1);
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [mTBILL.address],
+ [enableCalldata],
+ );
+ await increase(SET_IS_PERMISSIONED_DELAY);
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ mTBILL.address,
+ enableCalldata,
+ owner.address,
+ );
- await expect(mTokenPermissioned.connect(from).transfer(to.address, 1))
- .not.reverted;
- expect(await mTokenPermissioned.balanceOf(to.address)).eq(1);
- });
+ expect(await mTBILL.isPermissioned()).eq(true);
- it('mint when receiver is greenlisted', async () => {
- const baseFixture = await defaultDeploy();
- const {
- owner,
- accessControl,
- regularAccounts,
- mTokenPermissioned,
- mTokenPermissionedRoles,
- } = await loadFixture(
- mTokenPermissionedFixture.bind(this, baseFixture),
- );
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [mTBILL.address],
+ [sameValueCalldata],
+ );
+ await increase(SET_IS_PERMISSIONED_DELAY);
- const to = regularAccounts[0];
- await accessControl.grantRole(
- mTokenPermissionedRoles.greenlisted,
- to.address,
- );
+ const filter = mTBILL.filters.SetIsPermissioned();
+ const eventsBefore = await mTBILL.queryFilter(filter);
- await mint(
- { tokenContract: mTokenPermissioned, owner },
- to,
- parseUnits('1'),
- );
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ mTBILL.address,
+ sameValueCalldata,
+ owner.address,
+ );
+
+ const eventsAfter = await mTBILL.queryFilter(filter);
+ expect(eventsAfter.length).eq(eventsBefore.length);
+ expect(await mTBILL.isPermissioned()).eq(true);
+ });
+
+ it('disabling permissioned flag allows transfers without greenlist', async () => {
+ const { mTBILL, accessControl, owner, timelock, timelockManager } =
+ await loadFixture(defaultDeploy);
+ const [, from, to] = await ethers.getSigners();
+
+ const enableCalldata = mTBILL.interface.encodeFunctionData(
+ 'setIsPermissioned',
+ [true],
+ );
+ const disableCalldata = mTBILL.interface.encodeFunctionData(
+ 'setIsPermissioned',
+ [false],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [mTBILL.address],
+ [enableCalldata],
+ );
+ await increase(SET_IS_PERMISSIONED_DELAY);
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ mTBILL.address,
+ enableCalldata,
+ owner.address,
+ );
+
+ await mint({ tokenContract: mTBILL, owner }, from, parseUnits('1'), {
+ revertCustomError: { customErrorName: 'NotGreenlisted' },
});
- it('burn without greenlist on holder', async () => {
- const baseFixture = await defaultDeploy();
- const {
- owner,
- accessControl,
- regularAccounts,
- mTokenPermissioned,
- mTokenPermissionedRoles,
- } = await loadFixture(
- mTokenPermissionedFixture.bind(this, baseFixture),
- );
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [mTBILL.address],
+ [disableCalldata],
+ );
+ await increase(SET_IS_PERMISSIONED_DELAY);
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ mTBILL.address,
+ disableCalldata,
+ owner.address,
+ );
- const holder = regularAccounts[0];
- await accessControl.grantRole(
- mTokenPermissionedRoles.greenlisted,
- holder.address,
- );
- await mint({ tokenContract: mTokenPermissioned, owner }, holder, 1);
- await accessControl.revokeRole(
- mTokenPermissionedRoles.greenlisted,
- holder.address,
+ await mint({ tokenContract: mTBILL, owner }, from, parseUnits('1'));
+ await expect(mTBILL.connect(from).transfer(to.address, parseUnits('1')))
+ .not.reverted;
+ });
+ });
+
+ describe('setMinHoldingBalanceEnforced()', () => {
+ it('should fail: when called directly', async () => {
+ const { mTBILL, accessControl, owner } = await loadFixture(defaultDeploy);
+
+ const tokenManagerRole = await mTBILL.contractAdminRole();
+
+ await expect(mTBILL.connect(owner).setMinHoldingBalanceEnforced(true))
+ .revertedWithCustomError(accessControl, 'SenderIsNotTimelock')
+ .withArgs(
+ tokenManagerRole,
+ SET_MIN_HOLDING_BALANCE_ENFORCED_SEL,
+ owner.address,
);
+ });
- await burn({ tokenContract: mTokenPermissioned, owner }, holder, 1);
- });
+ it('should fail: call from address without token manager role', async () => {
+ const { mTBILL, accessControl, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ const caller = regularAccounts[0];
+ const tokenManagerRole = await mTBILL.contractAdminRole();
+
+ await expect(
+ mTBILL.connect(caller).setMinHoldingBalanceEnforced(true),
+ ).revertedWithCustomError(accessControl, 'NoFunctionPermission');
+
+ expect(await accessControl.hasRole(tokenManagerRole, caller.address)).eq(
+ false,
+ );
+ expect(await mTBILL.isMinHoldingBalanceEnforced()).eq(false);
});
- describe('transferFrom()', () => {
- const greenlistComboCases: {
- fromGreenlisted: boolean;
- toGreenlisted: boolean;
- callerGreenlisted: boolean;
- expectSuccess: boolean;
- }[] = [
- {
- fromGreenlisted: true,
- toGreenlisted: true,
- callerGreenlisted: true,
- expectSuccess: true,
- },
- {
- fromGreenlisted: true,
- toGreenlisted: true,
- callerGreenlisted: false,
- expectSuccess: true,
- },
- {
- fromGreenlisted: false,
- toGreenlisted: true,
- callerGreenlisted: true,
- expectSuccess: false,
- },
- {
- fromGreenlisted: false,
- toGreenlisted: true,
- callerGreenlisted: false,
- expectSuccess: false,
- },
- {
- fromGreenlisted: false,
- toGreenlisted: false,
- callerGreenlisted: true,
- expectSuccess: false,
- },
- {
- fromGreenlisted: false,
- toGreenlisted: false,
- callerGreenlisted: false,
- expectSuccess: false,
- },
- {
- fromGreenlisted: true,
- toGreenlisted: false,
- callerGreenlisted: true,
- expectSuccess: false,
- },
- {
- fromGreenlisted: true,
- toGreenlisted: false,
- callerGreenlisted: false,
- expectSuccess: false,
- },
- ];
-
- greenlistComboCases.forEach(
- ({
- fromGreenlisted,
- toGreenlisted,
- callerGreenlisted,
- expectSuccess,
- }) => {
- const fromL = fromGreenlisted ? 'greenlisted' : 'not greenlisted';
- const toL = toGreenlisted ? 'greenlisted' : 'not greenlisted';
- const callerL = callerGreenlisted ? 'greenlisted' : 'not greenlisted';
-
- it(
- expectSuccess
- ? `succeeds: from ${fromL}, to ${toL}, caller ${callerL}`
- : `should fail: from ${fromL}, to ${toL}, caller ${callerL}`,
- async () => {
- const baseFixture = await defaultDeploy();
- const {
- owner,
- accessControl,
- regularAccounts,
- mTokenPermissioned,
- mTokenPermissionedRoles,
- } = await loadFixture(
- mTokenPermissionedFixture.bind(this, baseFixture),
- );
+ it('should always require 2 days timelock even if contract admin/function admin role delay is different (no timelock for example)', async () => {
+ const { mTBILL, accessControl, owner, timelock, timelockManager } =
+ await loadFixture(defaultDeploy);
- const from = regularAccounts[0];
- const caller = regularAccounts[1];
- const to = regularAccounts[2];
- const { greenlisted } = mTokenPermissionedRoles;
-
- await accessControl.grantRole(greenlisted, from.address);
- await mint({ tokenContract: mTokenPermissioned, owner }, from, 1);
- await mTokenPermissioned.connect(from).approve(caller.address, 1);
-
- if (!fromGreenlisted) {
- await accessControl.revokeRole(greenlisted, from.address);
- }
- if (toGreenlisted) {
- await accessControl.grantRole(greenlisted, to.address);
- }
- if (callerGreenlisted) {
- await accessControl.grantRole(greenlisted, caller.address);
- }
-
- const tx = mTokenPermissioned
- .connect(caller)
- .transferFrom(from.address, to.address, 1);
-
- if (expectSuccess) {
- await expect(tx).not.reverted;
- expect(await mTokenPermissioned.balanceOf(to.address)).eq(1);
- } else {
- await expect(tx).revertedWith(acErrors.WMAC_HASNT_ROLE);
- }
- },
- );
- },
+ const tokenManagerRole = await mTBILL.contractAdminRole();
+ const functionRoleKey = await accessControl.permissionRoleKey(
+ tokenManagerRole,
+ mTBILL.address,
+ SET_MIN_HOLDING_BALANCE_ENFORCED_SEL,
);
- it('should fail: transferFrom when from is blacklisted', async () => {
- const baseFixture = await defaultDeploy();
- const {
- owner,
- accessControl,
- regularAccounts,
- mTokenPermissioned,
- mTokenPermissionedRoles,
- } = await loadFixture(
- mTokenPermissionedFixture.bind(this, baseFixture),
+ await setRoleTimelocksTester(
+ { accessControl, owner, timelock, timelockManager },
+ [tokenManagerRole, functionRoleKey],
+ [NO_DELAY, NO_DELAY],
+ );
+
+ await expect(mTBILL.connect(owner).setMinHoldingBalanceEnforced(true))
+ .revertedWithCustomError(accessControl, 'SenderIsNotTimelock')
+ .withArgs(
+ tokenManagerRole,
+ SET_MIN_HOLDING_BALANCE_ENFORCED_SEL,
+ owner.address,
);
- const from = regularAccounts[0];
- const spender = regularAccounts[1];
- const to = regularAccounts[2];
+ const calldata = mTBILL.interface.encodeFunctionData(
+ 'setMinHoldingBalanceEnforced',
+ [true],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [mTBILL.address],
+ [calldata],
+ );
- await accessControl.grantRole(
- mTokenPermissionedRoles.greenlisted,
- from.address,
- );
- await accessControl.grantRole(
- mTokenPermissionedRoles.greenlisted,
- to.address,
- );
- await mint({ tokenContract: mTokenPermissioned, owner }, from, 1);
- await blackList(
- {
- blacklistable: mTokenPermissioned,
- accessControl,
- owner,
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ mTBILL.address,
+ calldata,
+ owner.address,
+ {
+ revertCustomError: {
+ contract: timelockManager,
+ customErrorName: 'TimelockOperationNotReady',
},
- from,
- );
- await mTokenPermissioned.connect(from).approve(spender.address, 1);
+ },
+ );
+
+ await increase(SET_MIN_HOLDING_BALANCE_ENFORCED_DELAY);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ mTBILL.address,
+ calldata,
+ owner.address,
+ );
+
+ expect(await mTBILL.isMinHoldingBalanceEnforced()).eq(true);
+ });
+
+ it('when called through timelock manager with 2 days delay', async () => {
+ const { mTBILL, accessControl, owner, timelock, timelockManager } =
+ await loadFixture(defaultDeploy);
+
+ const calldata = mTBILL.interface.encodeFunctionData(
+ 'setMinHoldingBalanceEnforced',
+ [true],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [mTBILL.address],
+ [calldata],
+ );
+
+ await increase(SET_MIN_HOLDING_BALANCE_ENFORCED_DELAY);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ mTBILL.address,
+ calldata,
+ owner.address,
+ );
+
+ expect(await mTBILL.isMinHoldingBalanceEnforced()).eq(true);
+ });
+
+ it('does not change state when setting the same value', async () => {
+ const { mTBILL, accessControl, owner, timelock, timelockManager } =
+ await loadFixture(defaultDeploy);
+
+ const enableCalldata = mTBILL.interface.encodeFunctionData(
+ 'setMinHoldingBalanceEnforced',
+ [true],
+ );
+ const sameValueCalldata = mTBILL.interface.encodeFunctionData(
+ 'setMinHoldingBalanceEnforced',
+ [true],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [mTBILL.address],
+ [enableCalldata],
+ );
+ await increase(SET_MIN_HOLDING_BALANCE_ENFORCED_DELAY);
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ mTBILL.address,
+ enableCalldata,
+ owner.address,
+ );
+
+ expect(await mTBILL.isMinHoldingBalanceEnforced()).eq(true);
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [mTBILL.address],
+ [sameValueCalldata],
+ );
+ await increase(SET_MIN_HOLDING_BALANCE_ENFORCED_DELAY);
+
+ const filter = mTBILL.filters.SetIsMinHoldingBalanceEnforced();
+ const eventsBefore = await mTBILL.queryFilter(filter);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ mTBILL.address,
+ sameValueCalldata,
+ owner.address,
+ );
- await expect(
- mTokenPermissioned
- .connect(spender)
- .transferFrom(from.address, to.address, 1),
- ).revertedWith(acErrors.WMAC_HAS_ROLE);
+ const eventsAfter = await mTBILL.queryFilter(filter);
+ expect(eventsAfter.length).eq(eventsBefore.length);
+ expect(await mTBILL.isMinHoldingBalanceEnforced()).eq(true);
+ });
+
+ it('enabling min balance rejects dust mint to empty recipient', async () => {
+ const { mTBILL, accessControl, owner, timelock, timelockManager } =
+ await loadFixture(defaultDeploy);
+ const [, to] = await ethers.getSigners();
+
+ await mint({ tokenContract: mTBILL, owner }, to, parseUnits('0.5'));
+
+ const enableCalldata = mTBILL.interface.encodeFunctionData(
+ 'setMinHoldingBalanceEnforced',
+ [true],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [mTBILL.address],
+ [enableCalldata],
+ );
+ await increase(SET_MIN_HOLDING_BALANCE_ENFORCED_DELAY);
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ mTBILL.address,
+ enableCalldata,
+ owner.address,
+ );
+
+ await mint(
+ { tokenContract: mTBILL, owner },
+ (
+ await ethers.getSigners()
+ )[2],
+ parseUnits('0.5'),
+ { revertCustomError: { customErrorName: 'MinBalanceNotMet' } },
+ );
+ });
+ });
+
+ describe('setMaxSupplyCap()', () => {
+ it('should fail: call from address without token manager role nor function permission', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await setMaxSupplyCapTest({ tokenContract, owner }, parseUnits('100'), {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
});
+ });
- it('should fail: transferFrom when to is blacklisted', async () => {
- const baseFixture = await defaultDeploy();
- const {
- owner,
- accessControl,
- regularAccounts,
- mTokenPermissioned,
- mTokenPermissionedRoles,
- } = await loadFixture(
- mTokenPermissionedFixture.bind(this, baseFixture),
- );
+ it('call from address with token manager role', async () => {
+ const { owner, tokenContract } = await loadFixture(defaultDeploy);
- const from = regularAccounts[0];
- const spender = regularAccounts[1];
- const to = regularAccounts[2];
+ await setMaxSupplyCapTest({ tokenContract, owner }, parseUnits('100'));
+ });
- await accessControl.grantRole(
- mTokenPermissionedRoles.greenlisted,
- from.address,
- );
- await accessControl.grantRole(
- mTokenPermissionedRoles.greenlisted,
- to.address,
- );
- await mint({ tokenContract: mTokenPermissioned, owner }, from, 1);
- await blackList(
- {
- blacklistable: mTokenPermissioned,
- accessControl,
- owner,
- },
- to,
- );
- await mTokenPermissioned.connect(from).approve(spender.address, 1);
+ it('call from address with scoped function permission only', async () => {
+ const { owner, tokenContract, regularAccounts, accessControl, roles } =
+ await loadFixture(defaultDeploy);
- await expect(
- mTokenPermissioned
- .connect(spender)
- .transferFrom(from.address, to.address, 1),
- ).revertedWith(acErrors.WMAC_HAS_ROLE);
+ const user = regularAccounts[0];
+ const selector = encodeFnSelector('setMaxSupplyCap(uint256)');
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: roles.tokenRoles.mTBILL.tokenManager,
+ targetContract: tokenContract.address,
+ functionSelector: selector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ tokenContract.address,
+ selector,
+ [{ account: user.address, enabled: true }],
+ );
+
+ expect(
+ await accessControl.hasRole(
+ roles.tokenRoles.mTBILL.tokenManager,
+ user.address,
+ ),
+ ).eq(false);
+
+ await setMaxSupplyCapTest({ tokenContract, owner }, parseUnits('250'), {
+ from: user,
});
});
- });
-});
-describe('Shared greenlist role (mGLO -> mGLOBAL)', () => {
- it('mGLO greenlist role name is M_GLOBAL_GREENLISTED_ROLE', () => {
- expect(getRolesNamesForToken('mGLO').greenlisted).eq(
- 'M_GLOBAL_GREENLISTED_ROLE',
- );
- });
+ it('allows setting cap below current total supply', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
- it('mGLO greenlist role hash equals mGLOBAL greenlist role hash', () => {
- expect(getRolesForToken('mGLO').greenlisted).eq(
- getRolesForToken('mGLOBAL').greenlisted,
- );
+ await mint(
+ { tokenContract, owner },
+ regularAccounts[0],
+ parseUnits('100'),
+ );
+
+ await setMaxSupplyCapTest({ tokenContract, owner }, parseUnits('50'));
+
+ expect(await tokenContract.totalSupply()).eq(parseUnits('100'));
+ expect(await tokenContract.maxSupplyCap()).eq(parseUnits('50'));
+ });
+
+ it('call when setMaxSupplyCap is paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ tokenContract,
+ encodeFnSelector('setMaxSupplyCap(uint256)'),
+ );
+
+ await setMaxSupplyCapTest({ tokenContract, owner }, parseUnits('100'));
+ });
});
- it('mGLO keeps its own (separated) operational roles', () => {
- const mGloRoles = getRolesNamesForToken('mGLO');
- expect(mGloRoles.minter).eq('M_GLO_MINT_OPERATOR_ROLE');
- expect(mGloRoles.burner).eq('M_GLO_BURN_OPERATOR_ROLE');
- expect(mGloRoles.depositVaultAdmin).eq('M_GLO_DEPOSIT_VAULT_ADMIN_ROLE');
- expect(mGloRoles.redemptionVaultAdmin).eq(
- 'M_GLO_REDEMPTION_VAULT_ADMIN_ROLE',
- );
- expect(mGloRoles.customFeedAdmin).eq(
- 'M_GLO_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE',
- );
+ describe('setNameSymbol()', () => {
+ it('should fail: when called directly', async () => {
+ const { mTBILL, accessControl, owner } = await loadFixture(defaultDeploy);
+
+ const tokenManagerRole = await mTBILL.contractAdminRole();
+ const newName = 'Updated Token Name';
+ const newSymbol = 'UPD';
+
+ await expect(mTBILL.connect(owner).setNameSymbol(newName, newSymbol))
+ .revertedWithCustomError(accessControl, 'SenderIsNotTimelock')
+ .withArgs(tokenManagerRole, SET_NAME_SYMBOL_SEL, owner.address);
+ });
+
+ it('should always require 2 days timelock even if contract admin/function admin role delay is different (no timelock for example)', async () => {
+ const { mTBILL, accessControl, owner, timelock, timelockManager } =
+ await loadFixture(defaultDeploy);
+
+ const tokenManagerRole = await mTBILL.contractAdminRole();
+ const functionRoleKey = await accessControl.permissionRoleKey(
+ tokenManagerRole,
+ mTBILL.address,
+ SET_NAME_SYMBOL_SEL,
+ );
+ const newName = 'No Delay Override Name';
+ const newSymbol = 'NDO';
+
+ await setRoleTimelocksTester(
+ { accessControl, owner, timelock, timelockManager },
+ [tokenManagerRole, functionRoleKey],
+ [NO_DELAY, NO_DELAY],
+ );
+
+ await expect(mTBILL.connect(owner).setNameSymbol(newName, newSymbol))
+ .revertedWithCustomError(accessControl, 'SenderIsNotTimelock')
+ .withArgs(tokenManagerRole, SET_NAME_SYMBOL_SEL, owner.address);
+
+ const calldata = mTBILL.interface.encodeFunctionData('setNameSymbol', [
+ newName,
+ newSymbol,
+ ]);
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [mTBILL.address],
+ [calldata],
+ );
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ mTBILL.address,
+ calldata,
+ owner.address,
+ {
+ revertCustomError: {
+ contract: timelockManager,
+ customErrorName: 'TimelockOperationNotReady',
+ },
+ },
+ );
+
+ await increase(SET_NAME_SYMBOL_DELAY);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ mTBILL.address,
+ calldata,
+ owner.address,
+ );
+
+ expect(await mTBILL.name()).eq(newName);
+ expect(await mTBILL.symbol()).eq(newSymbol);
+ });
+
+ it('when called through timelock manager with 2 days delay', async () => {
+ const { mTBILL, accessControl, owner, timelock, timelockManager } =
+ await loadFixture(defaultDeploy);
+
+ const newName = 'Timelock Updated Name';
+ const newSymbol = 'TLUPD';
+ const calldata = mTBILL.interface.encodeFunctionData('setNameSymbol', [
+ newName,
+ newSymbol,
+ ]);
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [mTBILL.address],
+ [calldata],
+ );
+
+ await increase(SET_NAME_SYMBOL_DELAY);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ mTBILL.address,
+ calldata,
+ owner.address,
+ );
+
+ expect(await mTBILL.name()).eq(newName);
+ expect(await mTBILL.symbol()).eq(newSymbol);
+ });
+ });
+
+ describe('mint()', () => {
+ it('should fail: call from address without "mint operator" role', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+ const caller = regularAccounts[0];
+
+ await mint({ tokenContract, owner }, owner, 0, {
+ from: caller,
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ });
+ });
+
+ it('call from address with "mint operator" role', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ const amount = parseUnits('100');
+ const to = regularAccounts[0].address;
+
+ await mint({ tokenContract, owner }, to, amount);
+ });
+
+ it('should fail: mint would exceed maxSupplyCap', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await setMaxSupplyCapTest({ tokenContract, owner }, parseUnits('100'));
+
+ await mint(
+ { tokenContract, owner },
+ regularAccounts[0],
+ parseUnits('100'),
+ );
+
+ await mint({ tokenContract, owner }, regularAccounts[0], 1, {
+ revertCustomError: { customErrorName: 'MaxSupplyCapExceeded' },
+ });
+ });
+
+ it('mint up to exactly maxSupplyCap succeeds', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ const cap = parseUnits('100');
+ await setMaxSupplyCapTest({ tokenContract, owner }, cap);
+
+ await mint({ tokenContract, owner }, regularAccounts[0], cap);
+ expect(await tokenContract.totalSupply()).eq(cap);
+ });
+
+ it('when 1h limit is set but not exceeded', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ const amount = parseUnits('100');
+ const to = regularAccounts[0].address;
+
+ await increaseMintRateLimitTest(
+ { tokenContract, owner },
+ 3600,
+ parseUnits('10000'),
+ );
+ await mint({ tokenContract, owner }, to, amount);
+ });
+
+ it('when 1h and 10h limit is set but not exceeded', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ const amount = parseUnits('100');
+ const to = regularAccounts[0].address;
+
+ await increaseMintRateLimitTest(
+ { tokenContract, owner },
+ 3600,
+ parseUnits('1000'),
+ );
+ await increaseMintRateLimitTest(
+ { tokenContract, owner },
+ 3600 * 10,
+ parseUnits('10000'),
+ );
+
+ await mint({ tokenContract, owner }, to, amount);
+ });
+
+ it('should fail: amount exceeds mint rate limit', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+ const to = regularAccounts[0];
+ const window = days(1);
+
+ await increaseMintRateLimitTest({ tokenContract, owner }, window, 100);
+ await mint({ tokenContract, owner }, to, 100);
+ await mint({ tokenContract, owner }, to, 1, {
+ revertCustomError: {
+ customErrorName: 'WindowLimitExceeded',
+ args: [window, 0, 1],
+ },
+ });
+ });
+
+ it('should fail: one of multiple mint rate limits is exceeded', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+ const to = regularAccounts[0];
+ const longWindow = days(1);
+ const shortWindow = 60;
+
+ await increaseMintRateLimitTest(
+ { tokenContract, owner },
+ longWindow,
+ 100,
+ );
+ await increaseMintRateLimitTest(
+ { tokenContract, owner },
+ shortWindow,
+ 50,
+ );
+
+ await mint({ tokenContract, owner }, to, 60, {
+ revertCustomError: {
+ customErrorName: 'WindowLimitExceeded',
+ args: [shortWindow, 50, 60],
+ },
+ });
+ });
+
+ describe('mint() sliding rate limit (RateLimitLibrary)', () => {
+ const setupMintRateLimitFixture = async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ return {
+ owner,
+ tokenContract,
+ holder: regularAccounts[0],
+ recipient: regularAccounts[1],
+ };
+ };
+
+ it('10h window: full consume, after 1h restores ~10% and second mint uses it', async () => {
+ const { owner, tokenContract, holder } =
+ await setupMintRateLimitFixture();
+
+ await increaseMintRateLimitTest(
+ { tokenContract, owner },
+ hours(10),
+ parseUnits('1000'),
+ );
+
+ await mint({ tokenContract, owner }, holder, parseUnits('1000'));
+
+ await increase(hours(1));
+
+ await mint({ tokenContract, owner }, holder, parseUnits('100'));
+ });
+
+ it('1d window: after 80% consumed and limit halved, mint fails', async () => {
+ const { owner, tokenContract, holder } =
+ await setupMintRateLimitFixture();
+
+ const window = days(1);
+ const initialLimit = parseUnits('1000');
+
+ await increaseMintRateLimitTest(
+ { tokenContract, owner },
+ window,
+ initialLimit,
+ );
+
+ await mint({ tokenContract, owner }, holder, parseUnits('800'));
+
+ await decreaseMintRateLimitTest(
+ { tokenContract, owner },
+ window,
+ initialLimit.div(2),
+ );
+
+ await mint({ tokenContract, owner }, holder, parseUnits('100'), {
+ revertCustomError: {
+ customErrorName: 'WindowLimitExceeded',
+ },
+ });
+ });
+
+ it('1d window: after limit halved, wait 12h and mint small amount', async () => {
+ const { owner, tokenContract, holder } =
+ await setupMintRateLimitFixture();
+
+ const window = days(1);
+ const initialLimit = parseUnits('1000');
+
+ await increaseMintRateLimitTest(
+ { tokenContract, owner },
+ window,
+ initialLimit,
+ );
+
+ await mint({ tokenContract, owner }, holder, parseUnits('800'));
+
+ await decreaseMintRateLimitTest(
+ { tokenContract, owner },
+ window,
+ initialLimit.div(2),
+ );
+
+ await mint({ tokenContract, owner }, holder, parseUnits('100'), {
+ revertCustomError: {
+ customErrorName: 'WindowLimitExceeded',
+ },
+ });
+
+ await increase(hours(18));
+
+ await mint({ tokenContract, owner }, holder, parseUnits('1'));
+ });
+
+ it('multiple windows active at the same time', async () => {
+ const { owner, tokenContract, holder } =
+ await setupMintRateLimitFixture();
+
+ await increaseMintRateLimitTest(
+ { tokenContract, owner },
+ hours(1),
+ parseUnits('100'),
+ );
+ await increaseMintRateLimitTest(
+ { tokenContract, owner },
+ hours(6),
+ parseUnits('500'),
+ );
+ await increaseMintRateLimitTest(
+ { tokenContract, owner },
+ days(1),
+ parseUnits('10000'),
+ );
+
+ await mint({ tokenContract, owner }, holder, parseUnits('100'));
+
+ await mint({ tokenContract, owner }, holder, parseUnits('50'), {
+ revertCustomError: {
+ customErrorName: 'WindowLimitExceeded',
+ },
+ });
+
+ await increase(hours(1));
+
+ await mint({ tokenContract, owner }, holder, parseUnits('50'));
+ });
+
+ it('burn is not affected when mint rate limit is exhausted', async () => {
+ const { owner, tokenContract, holder } =
+ await setupMintRateLimitFixture();
+
+ const window = days(1);
+
+ await increaseMintRateLimitTest(
+ { tokenContract, owner },
+ window,
+ parseUnits('100'),
+ );
+
+ await mint({ tokenContract, owner }, holder, parseUnits('100'));
+
+ await mint({ tokenContract, owner }, holder, parseUnits('1'), {
+ revertCustomError: {
+ customErrorName: 'WindowLimitExceeded',
+ },
+ });
+
+ await tokenContract
+ .connect(owner)
+ .burn(holder.address, parseUnits('50'));
+ });
+
+ it('transfer is not affected when mint rate limit is exhausted', async () => {
+ const { owner, tokenContract, holder, recipient } =
+ await setupMintRateLimitFixture();
+
+ const window = days(1);
+
+ await increaseMintRateLimitTest(
+ { tokenContract, owner },
+ window,
+ parseUnits('100'),
+ );
+
+ await mint({ tokenContract, owner }, holder, parseUnits('100'));
+
+ await mint({ tokenContract, owner }, holder, parseUnits('1'), {
+ revertCustomError: {
+ customErrorName: 'WindowLimitExceeded',
+ },
+ });
+
+ await tokenContract
+ .connect(holder)
+ .transfer(recipient.address, parseUnits('50'));
+ });
+ });
+
+ it('should fail: contract is paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await pauseVault({ pauseManager, owner }, tokenContract);
+ await mint(
+ { tokenContract, owner },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ pausedRevert(tokenContract, MINT_SEL),
+ );
+ });
+
+ it('should fail: mint is paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await pauseVaultFn({ pauseManager, owner }, tokenContract, MINT_SEL);
+ await mint(
+ { tokenContract, owner },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ pausedRevert(tokenContract, MINT_SEL),
+ );
+ });
+
+ it('should fail: globally paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await pauseGlobalTest({ pauseManager, owner });
+ await mint(
+ { tokenContract, owner },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ pausedRevert(tokenContract, MINT_SEL),
+ );
+ });
+
+ it('should fail: contract admin paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await adminPauseContractTest({ pauseManager, owner }, tokenContract);
+ await mint(
+ { tokenContract, owner },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ pausedRevert(tokenContract, MINT_SEL),
+ );
+ });
+
+ it('should fail: mint when ERC20Pausable is paused', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await setErc20PausablePaused(tokenContract);
+ await mint(
+ { tokenContract, owner },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ { revertMessage: ERC20_PAUSED_MSG },
+ );
+ });
+
+ it('mint after contract admin unpause by pause manager', async () => {
+ const fixture = await loadFixture(defaultDeploy);
+
+ await adminPauseContractTest(
+ { pauseManager: fixture.pauseManager, owner: fixture.owner },
+ fixture.tokenContract,
+ );
+ await adminUnpauseContractViaTimelock(fixture);
+ await mint(
+ { tokenContract: fixture.tokenContract, owner: fixture.owner },
+ fixture.regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ );
+ });
+ });
+
+ describe('burn()', () => {
+ it('should fail: call from address without "burn operator" role', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ const caller = regularAccounts[0];
+
+ await burn({ tokenContract, owner }, owner, 0, {
+ from: caller,
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ });
+ });
+
+ it('should fail: call when user has insufficient balance', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ const amount = parseUnits('100');
+ const to = regularAccounts[0].address;
+
+ await burn({ tokenContract, owner }, to, amount, {
+ revertMessage: 'ERC20: burn amount exceeds balance',
+ });
+ });
+
+ it('call from address with "mint operator" role', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ const amount = parseUnits('100');
+ const to = regularAccounts[0].address;
+
+ await mint({ tokenContract, owner }, to, amount);
+ await burn({ tokenContract, owner }, to, amount);
+ });
+
+ it('burn is not affected by mint rate limits', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+ const holder = regularAccounts[0];
+ const window = days(1);
+
+ await increaseMintRateLimitTest({ tokenContract, owner }, window, 100);
+ await mint({ tokenContract, owner }, holder, 100);
+ await mint({ tokenContract, owner }, holder, 1, {
+ revertCustomError: {
+ customErrorName: 'WindowLimitExceeded',
+ args: [window, 0, 1],
+ },
+ });
+
+ await burn({ tokenContract, owner }, holder, 50);
+ });
+
+ it('should fail: burn when contract is paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await mint(
+ { tokenContract, owner },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ );
+ await pauseVault({ pauseManager, owner }, tokenContract);
+ await burn(
+ { tokenContract, owner },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ pausedRevert(tokenContract, BURN_SEL),
+ );
+ });
+
+ it('should fail: burn when burn is paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await mint(
+ { tokenContract, owner },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ );
+ await pauseVaultFn({ pauseManager, owner }, tokenContract, BURN_SEL);
+ await burn(
+ { tokenContract, owner },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ pausedRevert(tokenContract, BURN_SEL),
+ );
+ });
+
+ it('should fail: burn when globally paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await mint(
+ { tokenContract, owner },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ );
+ await pauseGlobalTest({ pauseManager, owner });
+ await burn(
+ { tokenContract, owner },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ pausedRevert(tokenContract, BURN_SEL),
+ );
+ });
+
+ it('should fail: burn when contract admin paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await mint(
+ { tokenContract, owner },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ );
+ await adminPauseContractTest({ pauseManager, owner }, tokenContract);
+ await burn(
+ { tokenContract, owner },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ pausedRevert(tokenContract, BURN_SEL),
+ );
+ });
+
+ it('should fail: burn when ERC20Pausable is paused', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await mint(
+ { tokenContract, owner },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ );
+ await setErc20PausablePaused(tokenContract);
+ await burn(
+ { tokenContract, owner },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ { revertMessage: ERC20_PAUSED_MSG },
+ );
+ });
+ });
+
+ describe('mintGoverned()', () => {
+ it('should fail: mintGoverned when contract is paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await pauseVault({ pauseManager, owner }, tokenContract);
+ await mint(
+ { tokenContract, owner, isGoverned: true },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ pausedRevert(tokenContract, MINT_GOVERNED_SEL),
+ );
+ });
+
+ it('should fail: mintGoverned when mintGoverned is paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ tokenContract,
+ MINT_GOVERNED_SEL,
+ );
+
+ await mint(
+ { tokenContract, owner, isGoverned: true },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ pausedRevert(tokenContract, MINT_GOVERNED_SEL),
+ );
+ });
+
+ it('should fail: mintGoverned when globally paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await pauseGlobalTest({ pauseManager, owner });
+ await mint(
+ { tokenContract, owner, isGoverned: true },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ pausedRevert(tokenContract, MINT_GOVERNED_SEL),
+ );
+ });
+
+ it('should fail: mintGoverned when contract admin paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await adminPauseContractTest({ pauseManager, owner }, tokenContract);
+ await mint(
+ { tokenContract, owner, isGoverned: true },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ pausedRevert(tokenContract, MINT_GOVERNED_SEL),
+ );
+ });
+
+ it('should fail: mintGoverned when ERC20Pausable is paused', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await setErc20PausablePaused(tokenContract);
+ await mint(
+ { tokenContract, owner, isGoverned: true },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ { revertMessage: ERC20_PAUSED_MSG },
+ );
+ });
+
+ it('should fail: mintGoverned does not bypass maxSupplyCap', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await setMaxSupplyCapTest({ tokenContract, owner }, parseUnits('100'));
+ await mint(
+ { tokenContract, owner, isGoverned: true },
+ regularAccounts[0],
+ parseUnits('100'),
+ );
+
+ await mint(
+ { tokenContract, owner, isGoverned: true },
+ regularAccounts[0],
+ 1,
+ { revertCustomError: { customErrorName: 'MaxSupplyCapExceeded' } },
+ );
+ });
+
+ it('burn remains available when totalSupply exceeds maxSupplyCap', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await mint(
+ { tokenContract, owner },
+ regularAccounts[0],
+ parseUnits('100'),
+ );
+ await setMaxSupplyCapTest({ tokenContract, owner }, parseUnits('50'));
+
+ await burn(
+ { tokenContract, owner },
+ regularAccounts[0],
+ parseUnits('10'),
+ );
+
+ expect(await tokenContract.totalSupply()).eq(parseUnits('90'));
+ expect(await tokenContract.maxSupplyCap()).eq(parseUnits('50'));
+ });
+ });
+
+ describe('burnGoverned()', () => {
+ it('should fail: burnGoverned when contract is paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await mint(
+ { tokenContract, owner },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ );
+ await pauseVault({ pauseManager, owner }, tokenContract);
+ await burn(
+ { tokenContract, owner, isGoverned: true },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ pausedRevert(tokenContract, BURN_GOVERNED_SEL),
+ );
+ });
+
+ it('should fail: burnGoverned when burnGoverned is paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await mint(
+ { tokenContract, owner },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ );
+ await pauseVaultFn(
+ { pauseManager, owner },
+ tokenContract,
+ BURN_GOVERNED_SEL,
+ );
+ await burn(
+ { tokenContract, owner, isGoverned: true },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ pausedRevert(tokenContract, BURN_GOVERNED_SEL),
+ );
+ });
+
+ it('should fail: burnGoverned when globally paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await mint(
+ { tokenContract, owner },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ );
+ await pauseGlobalTest({ pauseManager, owner });
+ await burn(
+ { tokenContract, owner, isGoverned: true },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ pausedRevert(tokenContract, BURN_GOVERNED_SEL),
+ );
+ });
+
+ it('should fail: burnGoverned when contract admin paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await mint(
+ { tokenContract, owner },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ );
+ await adminPauseContractTest({ pauseManager, owner }, tokenContract);
+ await burn(
+ { tokenContract, owner, isGoverned: true },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ pausedRevert(tokenContract, BURN_GOVERNED_SEL),
+ );
+ });
+
+ it('should fail: burnGoverned when ERC20Pausable is paused', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await mint(
+ { tokenContract, owner },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ );
+ await setErc20PausablePaused(tokenContract);
+ await burn(
+ { tokenContract, owner, isGoverned: true },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ { revertMessage: ERC20_PAUSED_MSG },
+ );
+ });
+ });
+
+ describe('transfer()', () => {
+ it('should fail: transfer when contract is paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await mint({ tokenContract, owner }, owner, PAUSE_TEST_AMOUNT);
+ await pauseVault({ pauseManager, owner }, tokenContract);
+ await expect(
+ tokenContract
+ .connect(owner)
+ .transfer(regularAccounts[0].address, PAUSE_TEST_AMOUNT),
+ )
+ .revertedWithCustomError(tokenContract, 'Paused')
+ .withArgs(tokenContract.address, TRANSFER_SEL);
+ });
+
+ it('should fail: transfer when transfer is paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await mint({ tokenContract, owner }, owner, PAUSE_TEST_AMOUNT);
+ await pauseVaultFn({ pauseManager, owner }, tokenContract, TRANSFER_SEL);
+ await expect(
+ tokenContract
+ .connect(owner)
+ .transfer(regularAccounts[0].address, PAUSE_TEST_AMOUNT),
+ )
+ .revertedWithCustomError(tokenContract, 'Paused')
+ .withArgs(tokenContract.address, TRANSFER_SEL);
+ });
+
+ it('should fail: transfer when globally paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await mint({ tokenContract, owner }, owner, PAUSE_TEST_AMOUNT);
+ await pauseGlobalTest({ pauseManager, owner });
+ await expect(
+ tokenContract
+ .connect(owner)
+ .transfer(regularAccounts[0].address, PAUSE_TEST_AMOUNT),
+ )
+ .revertedWithCustomError(tokenContract, 'Paused')
+ .withArgs(tokenContract.address, TRANSFER_SEL);
+ });
+
+ it('should fail: transfer when contract admin paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await mint({ tokenContract, owner }, owner, PAUSE_TEST_AMOUNT);
+ await adminPauseContractTest({ pauseManager, owner }, tokenContract);
+ await expect(
+ tokenContract
+ .connect(owner)
+ .transfer(regularAccounts[0].address, PAUSE_TEST_AMOUNT),
+ )
+ .revertedWithCustomError(tokenContract, 'Paused')
+ .withArgs(tokenContract.address, TRANSFER_SEL);
+ });
+
+ it('should fail: transfer when ERC20Pausable is paused', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await mint({ tokenContract, owner }, owner, PAUSE_TEST_AMOUNT);
+ await setErc20PausablePaused(tokenContract);
+ await expect(
+ tokenContract
+ .connect(owner)
+ .transfer(regularAccounts[0].address, PAUSE_TEST_AMOUNT),
+ ).revertedWith(ERC20_PAUSED_MSG);
+ });
+ });
+
+ describe('transferFrom()', () => {
+ it('should fail: transferFrom when contract is paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await mint(
+ { tokenContract, owner },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ );
+ await tokenContract
+ .connect(regularAccounts[0])
+ .approve(regularAccounts[1].address, PAUSE_TEST_AMOUNT);
+ await pauseVault({ pauseManager, owner }, tokenContract);
+ await expect(
+ tokenContract
+ .connect(regularAccounts[1])
+ .transferFrom(
+ regularAccounts[0].address,
+ owner.address,
+ PAUSE_TEST_AMOUNT,
+ ),
+ )
+ .revertedWithCustomError(tokenContract, 'Paused')
+ .withArgs(tokenContract.address, TRANSFER_FROM_SEL);
+ });
+
+ it('should fail: transferFrom when transferFrom is paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await mint(
+ { tokenContract, owner },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ );
+ await tokenContract
+ .connect(regularAccounts[0])
+ .approve(regularAccounts[1].address, PAUSE_TEST_AMOUNT);
+ await pauseVaultFn(
+ { pauseManager, owner },
+ tokenContract,
+ TRANSFER_FROM_SEL,
+ );
+ await expect(
+ tokenContract
+ .connect(regularAccounts[1])
+ .transferFrom(
+ regularAccounts[0].address,
+ owner.address,
+ PAUSE_TEST_AMOUNT,
+ ),
+ )
+ .revertedWithCustomError(tokenContract, 'Paused')
+ .withArgs(tokenContract.address, TRANSFER_FROM_SEL);
+ });
+
+ it('should fail: transferFrom when globally paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await mint(
+ { tokenContract, owner },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ );
+ await tokenContract
+ .connect(regularAccounts[0])
+ .approve(regularAccounts[1].address, PAUSE_TEST_AMOUNT);
+ await pauseGlobalTest({ pauseManager, owner });
+ await expect(
+ tokenContract
+ .connect(regularAccounts[1])
+ .transferFrom(
+ regularAccounts[0].address,
+ owner.address,
+ PAUSE_TEST_AMOUNT,
+ ),
+ )
+ .revertedWithCustomError(tokenContract, 'Paused')
+ .withArgs(tokenContract.address, TRANSFER_FROM_SEL);
+ });
+
+ it('should fail: transferFrom when contract admin paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await mint(
+ { tokenContract, owner },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ );
+ await tokenContract
+ .connect(regularAccounts[0])
+ .approve(regularAccounts[1].address, PAUSE_TEST_AMOUNT);
+ await adminPauseContractTest({ pauseManager, owner }, tokenContract);
+ await expect(
+ tokenContract
+ .connect(regularAccounts[1])
+ .transferFrom(
+ regularAccounts[0].address,
+ owner.address,
+ PAUSE_TEST_AMOUNT,
+ ),
+ )
+ .revertedWithCustomError(tokenContract, 'Paused')
+ .withArgs(tokenContract.address, TRANSFER_FROM_SEL);
+ });
+
+ it('should fail: transferFrom when ERC20Pausable is paused', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await mint(
+ { tokenContract, owner },
+ regularAccounts[0],
+ PAUSE_TEST_AMOUNT,
+ );
+ await tokenContract
+ .connect(regularAccounts[0])
+ .approve(regularAccounts[1].address, PAUSE_TEST_AMOUNT);
+ await setErc20PausablePaused(tokenContract);
+ await expect(
+ tokenContract
+ .connect(regularAccounts[1])
+ .transferFrom(
+ regularAccounts[0].address,
+ owner.address,
+ PAUSE_TEST_AMOUNT,
+ ),
+ ).revertedWith(ERC20_PAUSED_MSG);
+ });
+ });
+
+ describe('setMetadata()', () => {
+ it('should fail: call from address without token manager role', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ const caller = regularAccounts[0];
+
+ await setMetadataTest({ tokenContract, owner }, 'url', 'some value', {
+ from: caller,
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ });
+ });
+
+ it('call from address with token manager role', async () => {
+ const { owner, tokenContract } = await loadFixture(defaultDeploy);
+
+ await setMetadataTest(
+ { tokenContract, owner },
+ 'url',
+ 'some value',
+ undefined,
+ );
+ });
+
+ it('call when globally paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await pauseGlobalTest({ pauseManager, owner });
+ await setMetadataTest(
+ { tokenContract, owner },
+ 'url',
+ 'some value',
+ undefined,
+ );
+ });
+
+ it('call when contract is paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await pauseVault({ pauseManager, owner }, tokenContract);
+ await setMetadataTest(
+ { tokenContract, owner },
+ 'url',
+ 'some value',
+ undefined,
+ );
+ });
+
+ it('call when setMetadata is paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ tokenContract,
+ SET_METADATA_SEL,
+ );
+ await setMetadataTest(
+ { tokenContract, owner },
+ 'url',
+ 'some value',
+ undefined,
+ );
+ });
+ });
+
+ describe('setClawbackReceiver()', () => {
+ it('should fail: call from address without token manager role nor function permission', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ const caller = regularAccounts[0];
+
+ await setClawbackReceiverTest(
+ { tokenContract, owner },
+ regularAccounts[1].address,
+ {
+ from: caller,
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('should fail: new clawback receiver cannot be address zero', async () => {
+ const { owner, tokenContract } = await loadFixture(defaultDeploy);
+
+ await setClawbackReceiverTest(
+ { tokenContract, owner },
+ ethers.constants.AddressZero,
+ { revertCustomError: { customErrorName: 'InvalidAddress' } },
+ );
+ });
+
+ it('call from address with token manager role', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await setClawbackReceiverTest(
+ { tokenContract, owner },
+ regularAccounts[2].address,
+ undefined,
+ );
+ });
+
+ it('call from address with scoped function permission only', async () => {
+ const { owner, tokenContract, regularAccounts, accessControl, roles } =
+ await loadFixture(defaultDeploy);
+
+ const user = regularAccounts[0];
+ const nextReceiver = regularAccounts[3].address;
+ const selector = encodeFnSelector('setClawbackReceiver(address)');
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: roles.tokenRoles.mTBILL.tokenManager,
+ targetContract: tokenContract.address,
+ functionSelector: selector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ tokenContract.address,
+ selector,
+ [{ account: user.address, enabled: true }],
+ );
+
+ expect(
+ await accessControl.hasRole(roles.common.defaultAdmin, user.address),
+ ).eq(false);
+
+ await setClawbackReceiverTest({ tokenContract, owner }, nextReceiver, {
+ from: user,
+ });
+ });
+
+ it('call when globally paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await pauseGlobalTest({ pauseManager, owner });
+ await setClawbackReceiverTest(
+ { tokenContract, owner },
+ regularAccounts[1].address,
+ undefined,
+ );
+ });
+
+ it('call when contract is paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await pauseVault({ pauseManager, owner }, tokenContract);
+ await setClawbackReceiverTest(
+ { tokenContract, owner },
+ regularAccounts[1].address,
+ undefined,
+ );
+ });
+
+ it('call when setClawbackReceiver is paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ tokenContract,
+ SET_CLAWBACK_RECEIVER_SEL,
+ );
+ await setClawbackReceiverTest(
+ { tokenContract, owner },
+ regularAccounts[1].address,
+ undefined,
+ );
+ });
+ });
+
+ describe('clawback()', () => {
+ it('should fail: call from address without token manager role nor function permission', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ const caller = regularAccounts[0];
+ const victim = regularAccounts[1];
+ const amount = parseUnits('1');
+
+ await mint({ tokenContract, owner }, victim, amount);
+
+ await clawbackTest({ tokenContract, owner }, amount, victim, {
+ from: caller,
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ });
+ });
+
+ it('should not fail when from address is blacklisted', async () => {
+ const { owner, tokenContract, regularAccounts, accessControl } =
+ await loadFixture(defaultDeploy);
+
+ const holder = regularAccounts[0];
+ const amount = parseUnits('10');
+
+ await mint({ tokenContract, owner }, holder, amount);
+ await blackList(
+ { blacklistable: tokenContract, accessControl, owner },
+ holder,
+ );
+
+ await clawbackTest({ tokenContract, owner }, amount, holder, undefined);
+
+ expect(await tokenContract.balanceOf(holder.address)).eq(0);
+ });
+
+ it('should fail: when clawbackReceiver is blacklisted', async () => {
+ const { owner, tokenContract, regularAccounts, accessControl } =
+ await loadFixture(defaultDeploy);
+
+ const holder = regularAccounts[0];
+ const amount = parseUnits('5');
+
+ await mint({ tokenContract, owner }, holder, amount);
+
+ await blackList(
+ { blacklistable: tokenContract, accessControl, owner },
+ regularAccounts[2],
+ );
+ await setClawbackReceiverTest(
+ { tokenContract, owner },
+ regularAccounts[2].address,
+ undefined,
+ );
+
+ await clawbackTest({ tokenContract, owner }, amount, holder, {
+ revertCustomError: acErrors.WMAC_BLACKLISTED,
+ });
+ });
+
+ it('call from address with token manager role', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ const holder = regularAccounts[0];
+ const amount = parseUnits('7');
+
+ await mint({ tokenContract, owner }, holder, amount);
+ await clawbackTest({ tokenContract, owner }, amount, holder, undefined);
+ });
+
+ it('call from address with scoped function permission only', async () => {
+ const { owner, tokenContract, regularAccounts, accessControl, roles } =
+ await loadFixture(defaultDeploy);
+
+ const operator = regularAccounts[0];
+ const holder = regularAccounts[1];
+ const amount = parseUnits('3');
+ const selector = encodeFnSelector('clawback(uint256,address)');
+
+ await mint({ tokenContract, owner }, holder, amount);
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: roles.tokenRoles.mTBILL.tokenManager,
+ targetContract: tokenContract.address,
+ functionSelector: selector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ undefined,
+ tokenContract.address,
+ selector,
+ [{ account: operator.address, enabled: true }],
+ );
+
+ expect(
+ await accessControl.hasRole(
+ roles.tokenRoles.mTBILL.tokenManager,
+ operator.address,
+ ),
+ ).eq(false);
+
+ await clawbackTest({ tokenContract, owner }, amount, holder, {
+ from: operator,
+ });
+ });
+
+ it('should fail: clawback when contract is paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ const holder = regularAccounts[0];
+ await mint({ tokenContract, owner }, holder, PAUSE_TEST_AMOUNT);
+ await pauseVault({ pauseManager, owner }, tokenContract);
+ await clawbackTest(
+ { tokenContract, owner },
+ PAUSE_TEST_AMOUNT,
+ holder,
+ pausedRevert(tokenContract, CLAWBACK_SEL),
+ );
+ });
+
+ it('should fail: clawback when clawback is paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ const holder = regularAccounts[0];
+ await mint({ tokenContract, owner }, holder, PAUSE_TEST_AMOUNT);
+ await pauseVaultFn({ pauseManager, owner }, tokenContract, CLAWBACK_SEL);
+ await clawbackTest(
+ { tokenContract, owner },
+ PAUSE_TEST_AMOUNT,
+ holder,
+ pausedRevert(tokenContract, CLAWBACK_SEL),
+ );
+ });
+
+ it('should fail: clawback when globally paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ const holder = regularAccounts[0];
+ await mint({ tokenContract, owner }, holder, PAUSE_TEST_AMOUNT);
+ await pauseGlobalTest({ pauseManager, owner });
+ await clawbackTest(
+ { tokenContract, owner },
+ PAUSE_TEST_AMOUNT,
+ holder,
+ pausedRevert(tokenContract, CLAWBACK_SEL),
+ );
+ });
+
+ it('should fail: clawback when contract admin paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract, regularAccounts } =
+ await loadFixture(defaultDeploy);
+
+ const holder = regularAccounts[0];
+ await mint({ tokenContract, owner }, holder, PAUSE_TEST_AMOUNT);
+ await adminPauseContractTest({ pauseManager, owner }, tokenContract);
+ await clawbackTest(
+ { tokenContract, owner },
+ PAUSE_TEST_AMOUNT,
+ holder,
+ pausedRevert(tokenContract, CLAWBACK_SEL),
+ );
+ });
+
+ it('should fail: clawback when ERC20Pausable is paused', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ const holder = regularAccounts[0];
+ await mint({ tokenContract, owner }, holder, PAUSE_TEST_AMOUNT);
+ await setErc20PausablePaused(tokenContract);
+ await clawbackTest({ tokenContract, owner }, PAUSE_TEST_AMOUNT, holder, {
+ revertMessage: ERC20_PAUSED_MSG,
+ });
+ });
+ });
+
+ describe('increaseMintRateLimit()', () => {
+ it('should fail: call from address without token manager role', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ const caller = regularAccounts[0];
+
+ await increaseMintRateLimitTest({ tokenContract, owner }, days(1), 1, {
+ from: caller,
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ });
+ });
+
+ it('should fail: call with new limit <= existing limit', async () => {
+ const { owner, tokenContract } = await loadFixture(defaultDeploy);
+ const window = days(1);
+
+ await increaseMintRateLimitTest({ tokenContract, owner }, window, 100);
+ await increaseMintRateLimitTest({ tokenContract, owner }, window, 100, {
+ revertCustomError: {
+ customErrorName: 'InvalidNewLimit',
+ args: [100, 100],
+ },
+ });
+ });
+
+ it('call from address with token manager role', async () => {
+ const { owner, tokenContract } = await loadFixture(defaultDeploy);
+ const window = days(1);
+
+ await increaseMintRateLimitTest({ tokenContract, owner }, window, 100);
+ await increaseMintRateLimitTest({ tokenContract, owner }, window, 200);
+ });
+
+ it('should fail: when window is shorter than 1 minute', async () => {
+ const { owner, tokenContract } = await loadFixture(defaultDeploy);
+
+ await increaseMintRateLimitTest(
+ { tokenContract, owner },
+ 59,
+ parseUnits('1000'),
+ {
+ revertCustomError: {
+ customErrorName: 'WindowTooShort',
+ args: [59],
+ },
+ },
+ );
+ });
+
+ it('call when globally paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await pauseGlobalTest({ pauseManager, owner });
+ await increaseMintRateLimitTest(
+ { tokenContract, owner },
+ days(1),
+ parseUnits('1000'),
+ );
+ });
+
+ it('call when contract is paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await pauseVault({ pauseManager, owner }, tokenContract);
+ await increaseMintRateLimitTest(
+ { tokenContract, owner },
+ days(1),
+ parseUnits('1000'),
+ );
+ });
+
+ it('call when increaseMintRateLimit is paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract } = await loadFixture(
+ defaultDeploy,
+ );
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ tokenContract,
+ INCREASE_MINT_RATE_LIMIT_SEL,
+ );
+ await increaseMintRateLimitTest(
+ { tokenContract, owner },
+ days(1),
+ parseUnits('1000'),
+ );
+ });
+ });
+
+ describe('decreaseMintRateLimit()', () => {
+ it('should fail: call from address without token manager role', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ const caller = regularAccounts[0];
+
+ await decreaseMintRateLimitTest({ tokenContract, owner }, days(1), 1, {
+ from: caller,
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ });
+ });
+
+ it('should fail: call with new limit >= existing limit', async () => {
+ const { owner, tokenContract } = await loadFixture(defaultDeploy);
+ const window = days(1);
+
+ await increaseMintRateLimitTest({ tokenContract, owner }, window, 100);
+ await decreaseMintRateLimitTest({ tokenContract, owner }, window, 100, {
+ revertCustomError: {
+ customErrorName: 'InvalidNewLimit',
+ args: [100, 100],
+ },
+ });
+ });
+
+ it('call from address with token manager role', async () => {
+ const { owner, tokenContract } = await loadFixture(defaultDeploy);
+ const window = days(1);
+
+ await increaseMintRateLimitTest({ tokenContract, owner }, window, 200);
+ await decreaseMintRateLimitTest({ tokenContract, owner }, window, 100);
+ });
+
+ it('call when globally paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract } = await loadFixture(
+ defaultDeploy,
+ );
+ const window = days(1);
+
+ await increaseMintRateLimitTest({ tokenContract, owner }, window, 200);
+ await pauseGlobalTest({ pauseManager, owner });
+ await decreaseMintRateLimitTest({ tokenContract, owner }, window, 100);
+ });
+
+ it('call when contract is paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract } = await loadFixture(
+ defaultDeploy,
+ );
+ const window = days(1);
+
+ await increaseMintRateLimitTest({ tokenContract, owner }, window, 200);
+ await pauseVault({ pauseManager, owner }, tokenContract);
+ await decreaseMintRateLimitTest({ tokenContract, owner }, window, 100);
+ });
+
+ it('call when decreaseMintRateLimit is paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract } = await loadFixture(
+ defaultDeploy,
+ );
+ const window = days(1);
+
+ await increaseMintRateLimitTest({ tokenContract, owner }, window, 200);
+ await pauseVaultFn(
+ { pauseManager, owner },
+ tokenContract,
+ DECREASE_MINT_RATE_LIMIT_SEL,
+ );
+ await decreaseMintRateLimitTest({ tokenContract, owner }, window, 100);
+ });
+ });
+
+ describe('removeMintRateLimitConfig()', () => {
+ it('should fail: call from address without token manager role', async () => {
+ const { owner, tokenContract, regularAccounts } = await loadFixture(
+ defaultDeploy,
+ );
+
+ const caller = regularAccounts[0];
+ const window = days(1);
+
+ await increaseMintRateLimitTest({ tokenContract, owner }, window, 100);
+ await removeMintRateLimitTest({ tokenContract, owner }, window, {
+ from: caller,
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ });
+ });
+
+ it('should fail: when window does not exist', async () => {
+ const { owner, tokenContract } = await loadFixture(defaultDeploy);
+
+ await removeMintRateLimitTest({ tokenContract, owner }, days(99), {
+ revertCustomError: {
+ customErrorName: 'UnknownWindowLimit',
+ },
+ });
+ });
+
+ it('call from address with token manager role', async () => {
+ const { owner, tokenContract } = await loadFixture(defaultDeploy);
+ const window = days(1);
+
+ await increaseMintRateLimitTest({ tokenContract, owner }, window, 100);
+ await removeMintRateLimitTest({ tokenContract, owner }, window);
+ });
+
+ it('call when globally paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract } = await loadFixture(
+ defaultDeploy,
+ );
+ const window = days(1);
+
+ await increaseMintRateLimitTest({ tokenContract, owner }, window, 100);
+ await pauseGlobalTest({ pauseManager, owner });
+ await removeMintRateLimitTest({ tokenContract, owner }, window);
+ });
+
+ it('call when contract is paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract } = await loadFixture(
+ defaultDeploy,
+ );
+ const window = days(1);
+
+ await increaseMintRateLimitTest({ tokenContract, owner }, window, 100);
+ await pauseVault({ pauseManager, owner }, tokenContract);
+ await removeMintRateLimitTest({ tokenContract, owner }, window);
+ });
+
+ it('call when removeMintRateLimitConfig is paused by pause manager', async () => {
+ const { pauseManager, owner, tokenContract } = await loadFixture(
+ defaultDeploy,
+ );
+ const window = days(1);
+
+ await increaseMintRateLimitTest({ tokenContract, owner }, window, 100);
+ await pauseVaultFn(
+ { pauseManager, owner },
+ tokenContract,
+ REMOVE_MINT_RATE_LIMIT_SEL,
+ );
+ await removeMintRateLimitTest({ tokenContract, owner }, window);
+ });
+ });
+
+ describe('_beforeTokenTransfer()', () => {
+ it('should fail: mint(...) when address is blacklisted', async () => {
+ const { owner, regularAccounts, accessControl, tokenContract } =
+ await loadFixture(defaultDeploy);
+ const blacklisted = regularAccounts[0];
+
+ await blackList(
+ { blacklistable: tokenContract, accessControl, owner },
+ blacklisted,
+ );
+ await mint({ tokenContract, owner }, blacklisted, 1, {
+ revertCustomError: acErrors.WMAC_BLACKLISTED,
+ });
+ });
+
+ it('should fail: transfer(...) when from address is blacklisted', async () => {
+ const { owner, regularAccounts, accessControl, tokenContract } =
+ await loadFixture(defaultDeploy);
+
+ const blacklisted = regularAccounts[0];
+ const to = regularAccounts[1];
+
+ await mint({ tokenContract, owner }, blacklisted, 1);
+ await blackList(
+ { blacklistable: tokenContract, accessControl, owner },
+ blacklisted,
+ );
+
+ await expect(
+ tokenContract.connect(blacklisted).transfer(to.address, 1),
+ ).revertedWithCustomError(
+ tokenContract,
+ acErrors.WMAC_BLACKLISTED().customErrorName,
+ );
+ });
+
+ it('should fail: transfer(...) when to address is blacklisted', async () => {
+ const { owner, regularAccounts, accessControl, tokenContract } =
+ await loadFixture(defaultDeploy);
+
+ const blacklisted = regularAccounts[0];
+ const from = regularAccounts[1];
+
+ await mint({ tokenContract, owner }, from, 1);
+ await blackList(
+ { blacklistable: tokenContract, accessControl, owner },
+ blacklisted,
+ );
+
+ await expect(
+ tokenContract.connect(from).transfer(blacklisted.address, 1),
+ ).revertedWithCustomError(
+ tokenContract,
+ acErrors.WMAC_BLACKLISTED().customErrorName,
+ );
+ });
+
+ it('should fail: transferFrom(...) when from address is blacklisted', async () => {
+ const { owner, regularAccounts, accessControl, tokenContract } =
+ await loadFixture(defaultDeploy);
+
+ const blacklisted = regularAccounts[0];
+ const to = regularAccounts[1];
+
+ await mint({ tokenContract, owner }, blacklisted, 1);
+ await blackList(
+ { blacklistable: tokenContract, accessControl, owner },
+ blacklisted,
+ );
+
+ await tokenContract.connect(blacklisted).approve(to.address, 1);
+
+ await expect(
+ tokenContract
+ .connect(to)
+ .transferFrom(blacklisted.address, to.address, 1),
+ ).revertedWithCustomError(
+ tokenContract,
+ acErrors.WMAC_BLACKLISTED().customErrorName,
+ );
+ });
+
+ it('should fail: transferFrom(...) when to address is blacklisted', async () => {
+ const { owner, regularAccounts, accessControl, tokenContract } =
+ await loadFixture(defaultDeploy);
+
+ const blacklisted = regularAccounts[0];
+ const from = regularAccounts[1];
+ const caller = regularAccounts[2];
+
+ await mint({ tokenContract, owner }, from, 1);
+
+ await blackList(
+ { blacklistable: tokenContract, accessControl, owner },
+ blacklisted,
+ );
+ await tokenContract.connect(from).approve(caller.address, 1);
+
+ await expect(
+ tokenContract
+ .connect(caller)
+ .transferFrom(from.address, blacklisted.address, 1),
+ ).revertedWithCustomError(
+ tokenContract,
+ acErrors.WMAC_BLACKLISTED().customErrorName,
+ );
+ });
+
+ it('should fail: burn(...) when address is blacklisted', async () => {
+ const { owner, regularAccounts, accessControl, tokenContract } =
+ await loadFixture(defaultDeploy);
+
+ const blacklisted = regularAccounts[0];
+
+ await mint({ tokenContract, owner }, blacklisted, 1);
+ await blackList(
+ { blacklistable: tokenContract, accessControl, owner },
+ blacklisted,
+ );
+ await burn({ tokenContract, owner }, blacklisted, 1, {
+ revertCustomError: acErrors.WMAC_BLACKLISTED,
+ });
+ });
+
+ it('transferFrom(...) when caller address is blacklisted', async () => {
+ const { owner, regularAccounts, accessControl, tokenContract } =
+ await loadFixture(defaultDeploy);
+
+ const blacklisted = regularAccounts[0];
+ const from = regularAccounts[1];
+ const to = regularAccounts[2];
+
+ await mint({ tokenContract, owner }, from, 1);
+ await blackList(
+ { blacklistable: tokenContract, accessControl, owner },
+ blacklisted,
+ );
+
+ await tokenContract.connect(from).approve(blacklisted.address, 1);
+
+ await expect(
+ tokenContract
+ .connect(blacklisted)
+ .transferFrom(from.address, to.address, 1),
+ ).not.reverted;
+ });
+
+ it('transfer(...) when caller address was blacklisted and then un-blacklisted', async () => {
+ const { owner, regularAccounts, accessControl, tokenContract } =
+ await loadFixture(defaultDeploy);
+
+ const blacklisted = regularAccounts[0];
+ const to = regularAccounts[2];
+
+ await mint({ tokenContract, owner }, blacklisted, 1);
+ await blackList(
+ { blacklistable: tokenContract, accessControl, owner },
+ blacklisted,
+ );
+
+ await expect(
+ tokenContract.connect(blacklisted).transfer(to.address, 1),
+ ).revertedWithCustomError(
+ tokenContract,
+ acErrors.WMAC_BLACKLISTED().customErrorName,
+ );
+
+ await unBlackList(
+ { blacklistable: tokenContract, accessControl, owner },
+ blacklisted,
+ );
+
+ await expect(tokenContract.connect(blacklisted).transfer(to.address, 1))
+ .not.reverted;
+ });
+
+ it('transfer(...) is not affected by mint rate limits set to 0', async () => {
+ const { owner, regularAccounts, tokenContract } = await loadFixture(
+ defaultDeploy,
+ );
+ const from = regularAccounts[0];
+ const to = regularAccounts[1];
+ const dayWindow = days(1);
+ const minuteWindow = 60;
+
+ await mint({ tokenContract, owner }, from, 1);
+
+ await increaseMintRateLimitTest({ tokenContract, owner }, dayWindow, 1);
+ await decreaseMintRateLimitTest({ tokenContract, owner }, dayWindow, 0);
+ await increaseMintRateLimitTest(
+ { tokenContract, owner },
+ minuteWindow,
+ 1,
+ );
+ await decreaseMintRateLimitTest(
+ { tokenContract, owner },
+ minuteWindow,
+ 0,
+ );
+
+ await increaseMintRateLimitTest({ tokenContract, owner }, dayWindow, 1);
+ await decreaseMintRateLimitTest({ tokenContract, owner }, dayWindow, 0);
+ await increaseMintRateLimitTest(
+ { tokenContract, owner },
+ minuteWindow,
+ 1,
+ );
+ await decreaseMintRateLimitTest(
+ { tokenContract, owner },
+ minuteWindow,
+ 0,
+ );
+
+ await expect(tokenContract.connect(from).transfer(to.address, 1)).not
+ .reverted;
+ expect(await tokenContract.balanceOf(to.address)).eq(1);
+ });
+ });
+});
+
+describe('mTokenPermissioned', () => {
+ describe('transfer()', () => {
+ it('should fail: transfer when sender is not greenlisted', async () => {
+ const baseFixture = await loadFixture(defaultDeploy);
+ const {
+ owner,
+ accessControl,
+ regularAccounts,
+ mTokenPermissioned,
+ mTokenPermissionedRoles,
+ } = await loadFixture(mTokenPermissionedFixture.bind(this, baseFixture));
+
+ const from = regularAccounts[0];
+ const to = regularAccounts[1];
+
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenPermissionedRoles.greenlisted,
+ from.address,
+ );
+ await mint({ tokenContract: mTokenPermissioned, owner }, from, 1);
+ await accessControl.revokeRole(
+ mTokenPermissionedRoles.greenlisted,
+ from.address,
+ );
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenPermissionedRoles.greenlisted,
+ to.address,
+ );
+
+ await expect(
+ mTokenPermissioned.connect(from).transfer(to.address, 1),
+ ).revertedWithCustomError(mTokenPermissioned, 'NotGreenlisted');
+ });
+
+ it('should fail: transfer when recipient is not greenlisted', async () => {
+ const baseFixture = await loadFixture(defaultDeploy);
+ const {
+ owner,
+ accessControl,
+ regularAccounts,
+ mTokenPermissioned,
+ mTokenPermissionedRoles,
+ } = await loadFixture(mTokenPermissionedFixture.bind(this, baseFixture));
+
+ const from = regularAccounts[0];
+ const to = regularAccounts[1];
+
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenPermissionedRoles.greenlisted,
+ from.address,
+ );
+ await mint({ tokenContract: mTokenPermissioned, owner }, from, 1);
+
+ await expect(
+ mTokenPermissioned.connect(from).transfer(to.address, 1),
+ ).revertedWithCustomError(mTokenPermissioned, 'NotGreenlisted');
+ });
+
+ it('should fail: transfer when from is blacklisted', async () => {
+ const baseFixture = await loadFixture(defaultDeploy);
+ const {
+ owner,
+ accessControl,
+ regularAccounts,
+ mTokenPermissioned,
+ mTokenPermissionedRoles,
+ } = await loadFixture(mTokenPermissionedFixture.bind(this, baseFixture));
+
+ const from = regularAccounts[0];
+ const to = regularAccounts[1];
+
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenPermissionedRoles.greenlisted,
+ from.address,
+ );
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenPermissionedRoles.greenlisted,
+ to.address,
+ );
+ await mint({ tokenContract: mTokenPermissioned, owner }, from, 1);
+ await blackList(
+ {
+ blacklistable: mTokenPermissioned,
+ accessControl,
+ owner,
+ },
+ from,
+ );
+
+ await expect(
+ mTokenPermissioned.connect(from).transfer(to.address, 1),
+ ).revertedWithCustomError(
+ mTokenPermissioned,
+ acErrors.WMAC_BLACKLISTED().customErrorName,
+ );
+ });
+
+ it('should fail: transfer when ERC20Pausable is paused', async () => {
+ const baseFixture = await loadFixture(defaultDeploy);
+ const {
+ owner,
+ accessControl,
+ regularAccounts,
+ mTokenPermissioned,
+ mTokenPermissionedRoles,
+ } = await loadFixture(mTokenPermissionedFixture.bind(this, baseFixture));
+
+ const from = regularAccounts[0];
+ const to = regularAccounts[1];
+
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenPermissionedRoles.greenlisted,
+ from.address,
+ );
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenPermissionedRoles.greenlisted,
+ to.address,
+ );
+ await mint({ tokenContract: mTokenPermissioned, owner }, from, 1);
+
+ await setErc20PausablePaused(mTokenPermissioned);
+
+ await expect(
+ mTokenPermissioned.connect(from).transfer(to.address, 1),
+ ).revertedWith(ERC20_PAUSED_MSG);
+ });
+
+ it('should fail: mint when ERC20Pausable is paused', async () => {
+ const baseFixture = await loadFixture(defaultDeploy);
+ const {
+ owner,
+ accessControl,
+ regularAccounts,
+ mTokenPermissioned,
+ mTokenPermissionedRoles,
+ } = await loadFixture(mTokenPermissionedFixture.bind(this, baseFixture));
+
+ const to = regularAccounts[0];
+
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenPermissionedRoles.greenlisted,
+ to.address,
+ );
+ await setErc20PausablePaused(mTokenPermissioned);
+
+ await mint({ tokenContract: mTokenPermissioned, owner }, to, 1, {
+ revertMessage: ERC20_PAUSED_MSG,
+ });
+ });
+
+ it('should fail: burn when ERC20Pausable is paused', async () => {
+ const baseFixture = await loadFixture(defaultDeploy);
+ const {
+ owner,
+ accessControl,
+ regularAccounts,
+ mTokenPermissioned,
+ mTokenPermissionedRoles,
+ } = await loadFixture(mTokenPermissionedFixture.bind(this, baseFixture));
+
+ const holder = regularAccounts[0];
+
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenPermissionedRoles.greenlisted,
+ holder.address,
+ );
+ await mint({ tokenContract: mTokenPermissioned, owner }, holder, 1);
+ await setErc20PausablePaused(mTokenPermissioned);
+
+ await burn({ tokenContract: mTokenPermissioned, owner }, holder, 1, {
+ revertMessage: ERC20_PAUSED_MSG,
+ });
+ });
+
+ it('should fail: mint when receiver is not greenlisted', async () => {
+ const baseFixture = await loadFixture(defaultDeploy);
+ const { owner, regularAccounts, mTokenPermissioned } = await loadFixture(
+ mTokenPermissionedFixture.bind(this, baseFixture),
+ );
+
+ await mint(
+ { tokenContract: mTokenPermissioned, owner },
+ regularAccounts[0],
+ 1,
+ { revertCustomError: { customErrorName: 'NotGreenlisted' } },
+ );
+ });
+
+ it('transfer when both parties are greenlisted', async () => {
+ const baseFixture = await loadFixture(defaultDeploy);
+ const {
+ owner,
+ accessControl,
+ regularAccounts,
+ mTokenPermissioned,
+ mTokenPermissionedRoles,
+ } = await loadFixture(mTokenPermissionedFixture.bind(this, baseFixture));
+
+ const from = regularAccounts[0];
+ const to = regularAccounts[1];
+
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenPermissionedRoles.greenlisted,
+ from.address,
+ );
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenPermissionedRoles.greenlisted,
+ to.address,
+ );
+ await mint({ tokenContract: mTokenPermissioned, owner }, from, 1);
+
+ await expect(mTokenPermissioned.connect(from).transfer(to.address, 1)).not
+ .reverted;
+ expect(await mTokenPermissioned.balanceOf(to.address)).eq(1);
+ });
+
+ it('mint when receiver is greenlisted', async () => {
+ const baseFixture = await loadFixture(defaultDeploy);
+ const {
+ owner,
+ accessControl,
+ regularAccounts,
+ mTokenPermissioned,
+ mTokenPermissionedRoles,
+ } = await loadFixture(mTokenPermissionedFixture.bind(this, baseFixture));
+
+ const to = regularAccounts[0];
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenPermissionedRoles.greenlisted,
+ to.address,
+ );
+
+ await mint(
+ { tokenContract: mTokenPermissioned, owner },
+ to,
+ parseUnits('1'),
+ );
+ });
+
+ it('burnGoverned without greenlist on holder', async () => {
+ const baseFixture = await loadFixture(defaultDeploy);
+ const {
+ owner,
+ accessControl,
+ regularAccounts,
+ mTokenPermissioned,
+ mTokenPermissionedRoles,
+ } = await loadFixture(mTokenPermissionedFixture.bind(this, baseFixture));
+
+ const holder = regularAccounts[0];
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenPermissionedRoles.greenlisted,
+ holder.address,
+ );
+ await mint({ tokenContract: mTokenPermissioned, owner }, holder, 1);
+ await accessControl.revokeRole(
+ mTokenPermissionedRoles.greenlisted,
+ holder.address,
+ );
+
+ await burn(
+ { tokenContract: mTokenPermissioned, owner, isGoverned: true },
+ holder,
+ 1,
+ );
+ });
+
+ it('should fail: burn without greenlist on holder', async () => {
+ const baseFixture = await loadFixture(defaultDeploy);
+ const {
+ owner,
+ accessControl,
+ regularAccounts,
+ mTokenPermissioned,
+ mTokenPermissionedRoles,
+ } = await loadFixture(mTokenPermissionedFixture.bind(this, baseFixture));
+
+ const holder = regularAccounts[0];
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenPermissionedRoles.greenlisted,
+ holder.address,
+ );
+ await mint({ tokenContract: mTokenPermissioned, owner }, holder, 1);
+ await accessControl.revokeRole(
+ mTokenPermissionedRoles.greenlisted,
+ holder.address,
+ );
+
+ await burn({ tokenContract: mTokenPermissioned, owner }, holder, 1, {
+ revertCustomError: { customErrorName: 'NotGreenlisted' },
+ });
+ });
+ });
+
+ describe('transferFrom()', () => {
+ const greenlistComboCases: {
+ fromGreenlisted: boolean;
+ toGreenlisted: boolean;
+ callerGreenlisted: boolean;
+ expectSuccess: boolean;
+ }[] = [
+ {
+ fromGreenlisted: true,
+ toGreenlisted: true,
+ callerGreenlisted: true,
+ expectSuccess: true,
+ },
+ {
+ fromGreenlisted: true,
+ toGreenlisted: true,
+ callerGreenlisted: false,
+ expectSuccess: true,
+ },
+ {
+ fromGreenlisted: false,
+ toGreenlisted: true,
+ callerGreenlisted: true,
+ expectSuccess: false,
+ },
+ {
+ fromGreenlisted: false,
+ toGreenlisted: true,
+ callerGreenlisted: false,
+ expectSuccess: false,
+ },
+ {
+ fromGreenlisted: false,
+ toGreenlisted: false,
+ callerGreenlisted: true,
+ expectSuccess: false,
+ },
+ {
+ fromGreenlisted: false,
+ toGreenlisted: false,
+ callerGreenlisted: false,
+ expectSuccess: false,
+ },
+ {
+ fromGreenlisted: true,
+ toGreenlisted: false,
+ callerGreenlisted: true,
+ expectSuccess: false,
+ },
+ {
+ fromGreenlisted: true,
+ toGreenlisted: false,
+ callerGreenlisted: false,
+ expectSuccess: false,
+ },
+ ];
+
+ greenlistComboCases.forEach(
+ ({
+ fromGreenlisted,
+ toGreenlisted,
+ callerGreenlisted,
+ expectSuccess,
+ }) => {
+ const fromL = fromGreenlisted ? 'greenlisted' : 'not greenlisted';
+ const toL = toGreenlisted ? 'greenlisted' : 'not greenlisted';
+ const callerL = callerGreenlisted ? 'greenlisted' : 'not greenlisted';
+
+ it(
+ expectSuccess
+ ? `succeeds: from ${fromL}, to ${toL}, caller ${callerL}`
+ : `should fail: from ${fromL}, to ${toL}, caller ${callerL}`,
+ async () => {
+ const baseFixture = await loadFixture(defaultDeploy);
+ const {
+ owner,
+ accessControl,
+ regularAccounts,
+ mTokenPermissioned,
+ mTokenPermissionedRoles,
+ } = await loadFixture(
+ mTokenPermissionedFixture.bind(this, baseFixture),
+ );
+
+ const from = regularAccounts[0];
+ const caller = regularAccounts[1];
+ const to = regularAccounts[2];
+ const { greenlisted } = mTokenPermissionedRoles;
+
+ await accessControl['grantRole(bytes32,address)'](
+ greenlisted,
+ from.address,
+ );
+ await mint({ tokenContract: mTokenPermissioned, owner }, from, 1);
+ await mTokenPermissioned.connect(from).approve(caller.address, 1);
+
+ if (!fromGreenlisted) {
+ await accessControl.revokeRole(greenlisted, from.address);
+ }
+ if (toGreenlisted) {
+ await accessControl['grantRole(bytes32,address)'](
+ greenlisted,
+ to.address,
+ );
+ }
+ if (callerGreenlisted) {
+ await accessControl['grantRole(bytes32,address)'](
+ greenlisted,
+ caller.address,
+ );
+ }
+
+ const tx = mTokenPermissioned
+ .connect(caller)
+ .transferFrom(from.address, to.address, 1);
+
+ if (expectSuccess) {
+ await expect(tx).not.reverted;
+ expect(await mTokenPermissioned.balanceOf(to.address)).eq(1);
+ } else {
+ await expect(tx).revertedWithCustomError(
+ mTokenPermissioned,
+ 'NotGreenlisted',
+ );
+ }
+ },
+ );
+ },
+ );
+
+ it('should fail: transferFrom when from is blacklisted', async () => {
+ const baseFixture = await loadFixture(defaultDeploy);
+ const {
+ owner,
+ accessControl,
+ regularAccounts,
+ mTokenPermissioned,
+ mTokenPermissionedRoles,
+ } = await loadFixture(mTokenPermissionedFixture.bind(this, baseFixture));
+
+ const from = regularAccounts[0];
+ const spender = regularAccounts[1];
+ const to = regularAccounts[2];
+
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenPermissionedRoles.greenlisted,
+ from.address,
+ );
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenPermissionedRoles.greenlisted,
+ to.address,
+ );
+ await mint({ tokenContract: mTokenPermissioned, owner }, from, 1);
+ await blackList(
+ {
+ blacklistable: mTokenPermissioned,
+ accessControl,
+ owner,
+ },
+ from,
+ );
+ await mTokenPermissioned.connect(from).approve(spender.address, 1);
+
+ await expect(
+ mTokenPermissioned
+ .connect(spender)
+ .transferFrom(from.address, to.address, 1),
+ ).revertedWithCustomError(
+ mTokenPermissioned,
+ acErrors.WMAC_BLACKLISTED().customErrorName,
+ );
+ });
+
+ it('should fail: transferFrom when to is blacklisted', async () => {
+ const baseFixture = await loadFixture(defaultDeploy);
+ const {
+ owner,
+ accessControl,
+ regularAccounts,
+ mTokenPermissioned,
+ mTokenPermissionedRoles,
+ } = await loadFixture(mTokenPermissionedFixture.bind(this, baseFixture));
+
+ const from = regularAccounts[0];
+ const spender = regularAccounts[1];
+ const to = regularAccounts[2];
+
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenPermissionedRoles.greenlisted,
+ from.address,
+ );
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenPermissionedRoles.greenlisted,
+ to.address,
+ );
+ await mint({ tokenContract: mTokenPermissioned, owner }, from, 1);
+ await blackList(
+ {
+ blacklistable: mTokenPermissioned,
+ accessControl,
+ owner,
+ },
+ to,
+ );
+ await mTokenPermissioned.connect(from).approve(spender.address, 1);
+
+ await expect(
+ mTokenPermissioned
+ .connect(spender)
+ .transferFrom(from.address, to.address, 1),
+ ).revertedWithCustomError(
+ mTokenPermissioned,
+ acErrors.WMAC_BLACKLISTED().customErrorName,
+ );
+ });
+ });
+
+ describe('clawback()', () => {
+ it('should not fail when from address is not greenlisted', async () => {
+ const baseFixture = await loadFixture(defaultDeploy);
+ const {
+ owner,
+ accessControl,
+ regularAccounts,
+ clawbackReceiver,
+ mTokenPermissioned,
+ mTokenPermissionedRoles,
+ } = await loadFixture(mTokenPermissionedFixture.bind(this, baseFixture));
+
+ const holder = regularAccounts[0];
+ const amount = parseUnits('1');
+
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenPermissionedRoles.greenlisted,
+ holder.address,
+ );
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenPermissionedRoles.greenlisted,
+ clawbackReceiver.address,
+ );
+ await mint({ tokenContract: mTokenPermissioned, owner }, holder, amount);
+ await accessControl.revokeRole(
+ mTokenPermissionedRoles.greenlisted,
+ holder.address,
+ );
+
+ await clawbackTest(
+ { tokenContract: mTokenPermissioned, owner },
+ amount,
+ holder,
+ );
+ });
+
+ it('should fail: when clawbackReceiver is not greenlisted', async () => {
+ const baseFixture = await loadFixture(defaultDeploy);
+ const {
+ owner,
+ accessControl,
+ regularAccounts,
+ clawbackReceiver,
+ mTokenPermissioned,
+ mTokenPermissionedRoles,
+ } = await loadFixture(mTokenPermissionedFixture.bind(this, baseFixture));
+
+ const holder = regularAccounts[0];
+ const amount = parseUnits('1');
+
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenPermissionedRoles.greenlisted,
+ holder.address,
+ );
+ await mint({ tokenContract: mTokenPermissioned, owner }, holder, amount);
+ await accessControl.revokeRole(
+ mTokenPermissionedRoles.greenlisted,
+ holder.address,
+ );
+ await accessControl.revokeRole(
+ mTokenPermissionedRoles.greenlisted,
+ clawbackReceiver.address,
+ );
+
+ await clawbackTest(
+ { tokenContract: mTokenPermissioned, owner },
+ amount,
+ holder,
+ { revertCustomError: { customErrorName: 'NotGreenlisted' } },
+ );
+ });
+ });
+});
+
+describe('mTokenMinBalance', () => {
+ describe('transfer()', () => {
+ it('transfer when both parties hold above min balance after transfer', async () => {
+ const baseFixture = await loadFixture(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 loadFixture(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')),
+ ).revertedWithCustomError(mTokenMinBalance, 'MinBalanceNotMet');
+ });
+
+ it('transfer dust to empty recipient when recipient is free from min balance', async () => {
+ const baseFixture = await loadFixture(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(bytes32,address)'](
+ 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 loadFixture(defaultDeploy);
+ const {
+ owner,
+ accessControl,
+ regularAccounts,
+ mTokenMinBalance,
+ mTokenMinBalanceRoles,
+ } = await loadFixture(mTokenMinBalanceFixture.bind(this, baseFixture));
+
+ const from = regularAccounts[0];
+ const to = regularAccounts[1];
+
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenMinBalanceRoles.minBalanceExempt,
+ from.address,
+ );
+ await mint(
+ { tokenContract: mTokenMinBalance, owner },
+ from,
+ parseUnits('0.3'),
+ );
+
+ await expect(
+ mTokenMinBalance.connect(from).transfer(to.address, parseUnits('0.1')),
+ ).revertedWithCustomError(mTokenMinBalance, 'MinBalanceNotMet');
+ });
+
+ it('transfer entire balance leaving sender at zero', async () => {
+ const baseFixture = await loadFixture(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 loadFixture(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')),
+ ).revertedWithCustomError(mTokenMinBalance, 'MinBalanceNotMet');
+ });
+
+ it('transfer leaving sender below min balance when sender is free from min balance', async () => {
+ const baseFixture = await loadFixture(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(bytes32,address)'](
+ 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 loadFixture(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 loadFixture(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')),
+ ).revertedWithCustomError(
+ mTokenMinBalance,
+ acErrors.WMAC_BLACKLISTED().customErrorName,
+ );
+ });
+
+ it('should fail: transfer when to is blacklisted', async () => {
+ const baseFixture = await loadFixture(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')),
+ ).revertedWithCustomError(
+ mTokenMinBalance,
+ acErrors.WMAC_BLACKLISTED().customErrorName,
+ );
+ });
+
+ it('should fail: transfer when ERC20Pausable is paused', async () => {
+ const baseFixture = await loadFixture(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 setErc20PausablePaused(mTokenMinBalance);
+
+ await expect(
+ mTokenMinBalance.connect(from).transfer(to.address, parseUnits('0.1')),
+ ).revertedWith(ERC20_PAUSED_MSG);
+ });
+ });
+
+ describe('transferFrom()', () => {
+ it('transferFrom when both parties hold above min balance after transfer', async () => {
+ const baseFixture = await loadFixture(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 loadFixture(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),
+ ).revertedWithCustomError(mTokenMinBalance, 'MinBalanceNotMet');
+ });
+ });
+
+ describe('mint()', () => {
+ it('mint at least 1 token to empty recipient', async () => {
+ const baseFixture = await loadFixture(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 loadFixture(defaultDeploy);
+ const { owner, regularAccounts, mTokenMinBalance } = await loadFixture(
+ mTokenMinBalanceFixture.bind(this, baseFixture),
+ );
+
+ await mint(
+ { tokenContract: mTokenMinBalance, owner },
+ regularAccounts[0],
+ parseUnits('0.5'),
+ { revertCustomError: { customErrorName: 'MinBalanceNotMet' } },
+ );
+ });
+
+ it('mint less than 1 token to empty recipient when free from min balance', async () => {
+ const baseFixture = await loadFixture(defaultDeploy);
+ const {
+ owner,
+ accessControl,
+ regularAccounts,
+ mTokenMinBalance,
+ mTokenMinBalanceRoles,
+ } = await loadFixture(mTokenMinBalanceFixture.bind(this, baseFixture));
+
+ const to = regularAccounts[0];
+ await accessControl['grantRole(bytes32,address)'](
+ 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 loadFixture(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'),
+ );
+ });
+ });
+
+ describe('burn()', () => {
+ it('burn leaving holder at zero', async () => {
+ const baseFixture = await loadFixture(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 loadFixture(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 loadFixture(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'),
+ { revertCustomError: { customErrorName: 'MinBalanceNotMet' } },
+ );
+ });
+
+ it('burn leaving holder below min balance when free from min balance', async () => {
+ const baseFixture = await loadFixture(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(bytes32,address)'](
+ 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: burnGoverned leaving holder below min balance', async () => {
+ const baseFixture = await loadFixture(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')),
+ ).revertedWithCustomError(mTokenMinBalance, 'MinBalanceNotMet');
+ });
+ });
+});
+
+describe('mTokenPermissionedMinBalance', () => {
+ describe('transfer()', () => {
+ it('should fail: transfer when sender is not greenlisted', async () => {
+ const baseFixture = await loadFixture(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(bytes32,address)'](
+ greenlisted,
+ from.address,
+ );
+ await accessControl['grantRole(bytes32,address)'](
+ 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')),
+ ).revertedWithCustomError(mTokenPermissionedMinBalance, 'NotGreenlisted');
+ });
+
+ it('should fail: transfer when recipient is not greenlisted', async () => {
+ const baseFixture = await loadFixture(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(bytes32,address)'](
+ greenlisted,
+ from.address,
+ );
+ await mint(
+ { tokenContract: mTokenPermissionedMinBalance, owner },
+ from,
+ parseUnits('3'),
+ );
+
+ await expect(
+ mTokenPermissionedMinBalance
+ .connect(from)
+ .transfer(to.address, parseUnits('0.1')),
+ ).revertedWithCustomError(mTokenPermissionedMinBalance, 'NotGreenlisted');
+ });
+
+ it('should fail: transfer dust to empty recipient when both greenlisted', async () => {
+ const baseFixture = await loadFixture(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(bytes32,address)'](
+ greenlisted,
+ from.address,
+ );
+ await accessControl['grantRole(bytes32,address)'](
+ greenlisted,
+ to.address,
+ );
+ await mint(
+ { tokenContract: mTokenPermissionedMinBalance, owner },
+ from,
+ parseUnits('3'),
+ );
+
+ await expect(
+ mTokenPermissionedMinBalance
+ .connect(from)
+ .transfer(to.address, parseUnits('0.1')),
+ ).revertedWithCustomError(
+ mTokenPermissionedMinBalance,
+ 'MinBalanceNotMet',
+ );
+ });
+
+ it('transfer dust to empty recipient when recipient is free from min balance', async () => {
+ const baseFixture = await loadFixture(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(bytes32,address)'](
+ greenlisted,
+ from.address,
+ );
+ await accessControl['grantRole(bytes32,address)'](
+ greenlisted,
+ to.address,
+ );
+ await mint(
+ { tokenContract: mTokenPermissionedMinBalance, owner },
+ from,
+ parseUnits('3'),
+ );
+ await accessControl['grantRole(bytes32,address)'](
+ minBalanceExempt,
+ to.address,
+ );
+
+ await expect(
+ mTokenPermissionedMinBalance.connect(from).transfer(to.address, amount),
+ ).not.reverted;
+ expect(await mTokenPermissionedMinBalance.balanceOf(to.address)).eq(
+ amount,
+ );
+ });
+
+ it('transfer when both greenlisted and above min balance', async () => {
+ const baseFixture = await loadFixture(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(bytes32,address)'](
+ greenlisted,
+ from.address,
+ );
+ await accessControl['grantRole(bytes32,address)'](
+ 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;
+ });
+ });
+
+ describe('transferFrom()', () => {
+ it('should fail: transferFrom dust to empty recipient when both greenlisted', async () => {
+ const baseFixture = await loadFixture(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(bytes32,address)'](
+ greenlisted,
+ from.address,
+ );
+ await accessControl['grantRole(bytes32,address)'](
+ 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),
+ ).revertedWithCustomError(
+ mTokenPermissionedMinBalance,
+ 'MinBalanceNotMet',
+ );
+ });
+
+ it('transferFrom when both greenlisted and above min balance', async () => {
+ const baseFixture = await loadFixture(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(bytes32,address)'](
+ greenlisted,
+ from.address,
+ );
+ await accessControl['grantRole(bytes32,address)'](
+ 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;
+ });
+ });
+
+ describe('mint()', () => {
+ it('should fail: mint less than 1 token to greenlisted empty recipient', async () => {
+ const baseFixture = await loadFixture(defaultDeploy);
+ const {
+ owner,
+ accessControl,
+ regularAccounts,
+ mTokenPermissionedMinBalance,
+ mTokenPermissionedMinBalanceRoles,
+ } = await loadFixture(
+ mTokenPermissionedMinBalanceFixture.bind(this, baseFixture),
+ );
+
+ const to = regularAccounts[0];
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenPermissionedMinBalanceRoles.greenlisted,
+ to.address,
+ );
+
+ await mint(
+ { tokenContract: mTokenPermissionedMinBalance, owner },
+ to,
+ parseUnits('0.5'),
+ { revertCustomError: { customErrorName: 'MinBalanceNotMet' } },
+ );
+ });
+
+ it('mint at least 1 token to greenlisted recipient', async () => {
+ const baseFixture = await loadFixture(defaultDeploy);
+ const {
+ owner,
+ accessControl,
+ regularAccounts,
+ mTokenPermissionedMinBalance,
+ mTokenPermissionedMinBalanceRoles,
+ } = await loadFixture(
+ mTokenPermissionedMinBalanceFixture.bind(this, baseFixture),
+ );
+
+ const to = regularAccounts[0];
+ await accessControl['grantRole(bytes32,address)'](
+ mTokenPermissionedMinBalanceRoles.greenlisted,
+ to.address,
+ );
+
+ await mint(
+ { tokenContract: mTokenPermissionedMinBalance, owner },
+ to,
+ parseUnits('1'),
+ );
+ });
});
});
diff --git a/test/unit/suits/deposit-vault.suits.ts b/test/unit/suits/deposit-vault.suits.ts
new file mode 100644
index 00000000..5a48f11c
--- /dev/null
+++ b/test/unit/suits/deposit-vault.suits.ts
@@ -0,0 +1,11505 @@
+import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
+import { increase } from '@nomicfoundation/hardhat-network-helpers/dist/src/helpers/time';
+import {
+ days,
+ hours,
+} from '@nomicfoundation/hardhat-network-helpers/dist/src/helpers/time/duration';
+import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
+import { expect } from 'chai';
+import { constants, Contract } from 'ethers';
+import { parseUnits } from 'ethers/lib/utils';
+
+import {
+ baseInitParamsMv,
+ manageableVaultSuits,
+ mvInitializeParamCases,
+} from './manageable-vault.suits';
+
+import { encodeFnSelector } from '../../../helpers/utils';
+import {
+ DepositVaultTest,
+ DepositVaultTest__factory,
+ DepositVaultWithAaveTest,
+ DepositVaultWithMTokenTest,
+ DepositVaultWithUSTBTest,
+ DepositVaultWithMorphoTest,
+} from '../../../typechain-types';
+import {
+ acErrors,
+ blackList,
+ greenList,
+ setupPermissionRole,
+} from '../../common/ac.helpers';
+import {
+ approveBase18,
+ InitializeParamCase,
+ initializeParamsSuits,
+ InitializeParamsOpt,
+ mintToken,
+ pauseVault,
+ pauseVaultFn,
+ asyncForEach,
+} from '../../common/common.helpers';
+import {
+ setMinGrowthApr,
+ setRoundDataGrowth,
+} from '../../common/custom-feed-growth.helpers';
+import { setRoundData } from '../../common/data-feed.helpers';
+import {
+ setMaxAmountPerRequestTest,
+ approveRequestTest,
+ depositInstantTest,
+ depositRequestTest,
+ rejectRequestTest,
+ safeBulkApproveRequestTest,
+ expectedDepositHoldbackPartRateFromAvg,
+} from '../../common/deposit-vault.helpers';
+import { DefaultFixture } from '../../common/fixtures';
+import { greenListEnable } from '../../common/greenlist.helpers';
+import {
+ addPaymentTokenTest,
+ removePaymentTokenTest,
+ setVariabilityToleranceTest,
+ changeTokenAllowanceTest,
+ setInstantFeeTest,
+ setInstantLimitConfigTest,
+ setMinAmountTest,
+ setMinMaxInstantFeeTest,
+ setMinAmountToDepositTest,
+ setMaxInstantShareTest,
+ setSequentialRequestProcessingTest,
+ setWaivedFeeAccountTest,
+ setMaxApproveRequestIdTest,
+} from '../../common/manageable-vault.helpers';
+import { setMaxSupplyCapTest } from '../../common/mtoken.helpers';
+import { InitializerParamsDv } from '../../common/vault-initializer.helpers';
+import { sanctionUser } from '../../common/with-sanctions-list.helpers';
+
+const APPROVE_FN_SELECTORS = [
+ encodeFnSelector('approveRequest(uint256,uint256,bool)'),
+ encodeFnSelector('safeBulkApproveRequestAtSavedRate(uint256[])'),
+ encodeFnSelector('safeBulkApproveRequest(uint256[])'),
+ encodeFnSelector('safeBulkApproveRequest(uint256[],uint256)'),
+ encodeFnSelector('safeBulkApproveRequestAvgRate(uint256[])'),
+ encodeFnSelector('safeBulkApproveRequestAvgRate(uint256[],uint256)'),
+] as const;
+
+let pauseManager: DefaultFixture['pauseManager'];
+let owner: DefaultFixture['owner'];
+
+export const baseInitParamsDv = (
+ fixture: DefaultFixture,
+): InitializerParamsDv => ({
+ ...baseInitParamsMv(fixture),
+});
+
+export const tokensReceiverSelfParamCase = <
+ TParams extends InitializerParamsDv,
+>(
+ deployUninitialized: DepositVaultInitConfig['deployUninitialized'],
+): InitializeParamCase => ({
+ title: 'tokensReceiver is address(this)',
+ contract: deployUninitialized,
+ params: (_, contract) => ({ tokensReceiver: contract!.address }),
+ revertCustomError: {
+ customErrorName: 'InvalidAddress',
+ args: (_, contract) => [contract!.address],
+ },
+});
+
+export type DepositVaultInitConfig<
+ TParams extends InitializerParamsDv = InitializerParamsDv,
+> = {
+ deployUninitialized: (
+ fixture: DefaultFixture,
+ ) => Contract | Promise;
+ initialize: (
+ fixture: DefaultFixture,
+ params: Partial,
+ opt?: InitializeParamsOpt,
+ ) => Promise;
+ extraParamCases?: InitializeParamCase[];
+};
+
+const pauseOtherDepositApproveFns = async (
+ depositVault: Contract,
+ exceptSelector: (typeof APPROVE_FN_SELECTORS)[number],
+) => {
+ await asyncForEach(
+ APPROVE_FN_SELECTORS,
+ async (selector) => {
+ if (selector === exceptSelector) {
+ return;
+ }
+ await pauseVaultFn({ pauseManager, owner }, depositVault, selector);
+ },
+ true,
+ );
+};
+export const depositVaultSuits = (
+ dvName: string,
+ dvFixture: () => Promise,
+ dvConfig: {
+ createNew: (
+ owner: SignerWithAddress,
+ ) => Promise<
+ | DepositVaultTest
+ | DepositVaultWithAaveTest
+ | DepositVaultWithMTokenTest
+ | DepositVaultWithUSTBTest
+ | DepositVaultWithMorphoTest
+ >;
+ key:
+ | 'depositVault'
+ | 'depositVaultWithUSTB'
+ | 'depositVaultWithMToken'
+ | 'depositVaultWithAave'
+ | 'depositVaultWithMorpho';
+ },
+ deploymentAdditionalChecks: (fixtureRes: DefaultFixture) => Promise,
+ initConfig: DepositVaultInitConfig,
+ otherTests: (fixture: () => Promise) => void,
+) => {
+ const loadDvFixture = async () => {
+ const fixture = await loadFixture(dvFixture);
+ ({ pauseManager, owner } = fixture);
+
+ const { createNew, key } = dvConfig;
+ return {
+ ...fixture,
+ originalDepositVault: fixture.depositVault,
+ depositVault: DepositVaultTest__factory.connect(
+ fixture[key].address,
+ fixture.owner,
+ ),
+ createNew: async () => {
+ const dv = await createNew(fixture.owner);
+ return DepositVaultTest__factory.connect(dv.address, fixture.owner);
+ },
+ };
+ };
+
+ describe(dvName, function () {
+ manageableVaultSuits(loadDvFixture, dvConfig, async (fixture) => {
+ const { depositVault, roles } = fixture;
+ expect(await depositVault.contractAdminRole()).eq(
+ roles.tokenRoles.mTBILL.depositVaultAdmin,
+ );
+ });
+
+ it('deployment', async () => {
+ const fixture = await loadDvFixture();
+ const { depositVault, mTBILL } = fixture;
+
+ expect(await depositVault.minMTokenAmountForFirstDeposit()).eq('0');
+
+ expect(await mTBILL.maxSupplyCap()).eq(constants.MaxUint256);
+
+ await deploymentAdditionalChecks({
+ ...fixture,
+ depositVault: fixture.originalDepositVault as DepositVaultTest,
+ });
+ });
+
+ describe('initialization', () => {
+ initializeParamsSuits(
+ [
+ ...mvInitializeParamCases,
+ tokensReceiverSelfParamCase(initConfig.deployUninitialized),
+ ...(initConfig.extraParamCases ?? []),
+ ],
+ loadDvFixture,
+ initConfig.initialize,
+ );
+ });
+
+ describe('common', () => {
+ describe('getEffectiveMTokenSupply()', () => {
+ it('returns mToken totalSupply when upcomingSupply is zero', async () => {
+ const { depositVault, mTBILL } = await loadDvFixture();
+
+ expect(await depositVault.upcomingSupply()).eq(0);
+ expect(await depositVault.getEffectiveMTokenSupply()).eq(
+ await mTBILL.totalSupply(),
+ );
+ });
+
+ it('returns totalSupply plus upcomingSupply from pending deposit requests', async () => {
+ const {
+ depositVault,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 1000);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 1000,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ const supplyBefore = await mTBILL.totalSupply();
+ const upcomingBefore = await depositVault.upcomingSupply();
+ expect(await depositVault.getEffectiveMTokenSupply()).eq(
+ supplyBefore.add(upcomingBefore),
+ );
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ stableCoins.dai,
+ 100,
+ { from: regularAccounts[0] },
+ );
+
+ const upcomingAfter = await depositVault.upcomingSupply();
+ expect(upcomingAfter).gt(upcomingBefore);
+ expect(await depositVault.getEffectiveMTokenSupply()).eq(
+ supplyBefore.add(upcomingAfter),
+ );
+ });
+ });
+
+ describe('setMinMTokenAmountForFirstDeposit()', () => {
+ it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
+ const { owner, depositVault, regularAccounts } =
+ await loadDvFixture();
+
+ await setMinAmountToDepositTest({ depositVault, owner }, 1.1, {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ });
+ });
+
+ it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
+ const { owner, depositVault } = await loadDvFixture();
+ await setMinAmountToDepositTest({ depositVault, owner }, 1.1);
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { owner, depositVault } = await loadDvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ depositVault,
+ encodeFnSelector('setMinMTokenAmountForFirstDeposit(uint256)'),
+ );
+
+ await setMinAmountToDepositTest({ depositVault, owner }, 1.1, {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ });
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const { accessControl, owner, depositVault, regularAccounts } =
+ await loadDvFixture();
+
+ const contractAdminRole = await depositVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ contractAdminRole,
+ depositVault.address,
+ 'setMinMTokenAmountForFirstDeposit(uint256)',
+ regularAccounts[0].address,
+ );
+
+ expect(
+ await accessControl.hasRole(
+ contractAdminRole,
+ regularAccounts[0].address,
+ ),
+ ).eq(false);
+
+ await setMinAmountToDepositTest({ depositVault, owner }, 2.2, {
+ from: regularAccounts[0],
+ });
+ });
+
+ it('succeeds with scoped permission and vault admin role', async () => {
+ const { accessControl, owner, depositVault, regularAccounts, roles } =
+ await loadDvFixture();
+
+ const contractAdminRole = await depositVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ contractAdminRole,
+ depositVault.address,
+ 'setMinMTokenAmountForFirstDeposit(uint256)',
+ regularAccounts[0].address,
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ roles.tokenRoles.mTBILL.depositVaultAdmin,
+ regularAccounts[0].address,
+ );
+
+ await setMinAmountToDepositTest({ depositVault, owner }, 2.2, {
+ from: regularAccounts[0],
+ });
+ });
+ });
+
+ describe('setMaxAmountPerRequest()', () => {
+ it('should fail: call from address without DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
+ const { owner, depositVault, regularAccounts } =
+ await loadDvFixture();
+
+ await setMaxAmountPerRequestTest({ depositVault, owner }, 100, {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ });
+ });
+
+ it('call from address with DEPOSIT_VAULT_ADMIN_ROLE role', async () => {
+ const { owner, depositVault } = await loadDvFixture();
+ await setMaxAmountPerRequestTest({ depositVault, owner }, 100);
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { owner, depositVault } = await loadDvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ depositVault,
+ encodeFnSelector('setMaxAmountPerRequest(uint256)'),
+ );
+
+ await setMaxAmountPerRequestTest({ depositVault, owner }, 100, {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ });
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const { accessControl, owner, depositVault, regularAccounts } =
+ await loadDvFixture();
+
+ const contractAdminRole = await depositVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ contractAdminRole,
+ depositVault.address,
+ 'setMaxAmountPerRequest(uint256)',
+ regularAccounts[0].address,
+ );
+
+ expect(
+ await accessControl.hasRole(
+ contractAdminRole,
+ regularAccounts[0].address,
+ ),
+ ).eq(false);
+
+ await setMaxAmountPerRequestTest({ depositVault, owner }, 200, {
+ from: regularAccounts[0],
+ });
+ });
+
+ it('succeeds with scoped permission and vault admin role', async () => {
+ const { accessControl, owner, depositVault, regularAccounts, roles } =
+ await loadDvFixture();
+
+ const contractAdminRole = await depositVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ contractAdminRole,
+ depositVault.address,
+ 'setMaxAmountPerRequest(uint256)',
+ regularAccounts[0].address,
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ roles.tokenRoles.mTBILL.depositVaultAdmin,
+ regularAccounts[0].address,
+ );
+
+ await setMaxAmountPerRequestTest({ depositVault, owner }, 200, {
+ from: regularAccounts[0],
+ });
+ });
+ });
+
+ describe('depositInstant()', async () => {
+ it('should fail: if mint exceed allowance', async () => {
+ const {
+ depositVault,
+ stableCoins,
+ owner,
+ dataFeed,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await mintToken(stableCoins.dai, owner, 100000);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100000);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await changeTokenAllowanceTest(
+ { vault: depositVault, owner },
+ stableCoins.dai.address,
+ 100,
+ );
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 99_999,
+ {
+ revertCustomError: {
+ customErrorName: 'AllowanceExceeded',
+ },
+ },
+ );
+ });
+
+ it('should decrease allowance if allowance < UINT_MAX', async () => {
+ const {
+ depositVault,
+ stableCoins,
+ owner,
+ dataFeed,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await mintToken(stableCoins.dai, owner, 100000);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100000);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await changeTokenAllowanceTest(
+ { vault: depositVault, owner },
+ stableCoins.dai.address,
+ parseUnits('1000'),
+ );
+
+ const tokenConfigBefore = await depositVault.tokensConfig(
+ stableCoins.dai.address,
+ );
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 999,
+ );
+
+ const tokenConfigAfter = await depositVault.tokensConfig(
+ stableCoins.dai.address,
+ );
+
+ expect(
+ tokenConfigBefore.allowance.sub(tokenConfigAfter.allowance),
+ ).eq(parseUnits('999'));
+ });
+
+ it('should not decrease allowance if allowance = UINT_MAX', async () => {
+ const {
+ depositVault,
+ stableCoins,
+ owner,
+ dataFeed,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await mintToken(stableCoins.dai, owner, 100000);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100000);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await changeTokenAllowanceTest(
+ { vault: depositVault, owner },
+ stableCoins.dai.address,
+ constants.MaxUint256,
+ );
+
+ const tokenConfigBefore = await depositVault.tokensConfig(
+ stableCoins.dai.address,
+ );
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 999,
+ );
+
+ const tokenConfigAfter = await depositVault.tokensConfig(
+ stableCoins.dai.address,
+ );
+
+ expect(tokenConfigBefore.allowance).eq(tokenConfigAfter.allowance);
+ });
+
+ it('should fail: when there is no token in vault', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 1,
+ {
+ revertCustomError: {
+ customErrorName: 'UnknownPaymentToken',
+ },
+ },
+ );
+ });
+
+ it('should fail: when trying to deposit 0 amount', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 0,
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidAmount',
+ },
+ },
+ );
+ });
+
+ it('should fail: when function paused', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ } = await loadDvFixture();
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ const selector = encodeFnSelector(
+ 'depositInstant(address,uint256,uint256,bytes32)',
+ );
+ await pauseVaultFn({ pauseManager, owner }, depositVault, selector);
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('should fail: when rounding is invalid', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100.0000000001,
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidRounding',
+ },
+ },
+ );
+ });
+
+ it('should fail: call with insufficient allowance', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertMessage: 'ERC20: insufficient allowance',
+ },
+ );
+ });
+
+ it('should fail: call with insufficient balance', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertMessage: 'ERC20: transfer amount exceeds balance',
+ },
+ );
+ });
+
+ it('should fail: dataFeed rate 0 ', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mockedAggregator,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await approveBase18(owner, stableCoins.dai, depositVault, 10);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await mintToken(stableCoins.dai, owner, 100_000);
+ await setRoundData({ mockedAggregator }, 0);
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 1,
+ {
+ revertMessage: 'DF: feed is deprecated',
+ },
+ );
+ });
+
+ it('should fail: call for amount < minAmountToDepositTest', async () => {
+ const {
+ depositVault,
+ mockedAggregator,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+
+ await mintToken(stableCoins.dai, owner, 100_000);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100_000);
+
+ await setMinAmountToDepositTest({ depositVault, owner }, 100_000);
+ await setInstantLimitConfigTest(
+ { vault: depositVault, owner },
+ 150_000,
+ );
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 99_999,
+ {
+ revertCustomError: {
+ customErrorName: 'LessThanMinAmountFirstDeposit',
+ },
+ },
+ );
+ });
+
+ it('should fail: call for amount < minAmount', async () => {
+ const {
+ depositVault,
+ mockedAggregator,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+
+ await setMinAmountTest({ vault: depositVault, owner }, 100);
+
+ await mintToken(stableCoins.dai, owner, 100_000);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100_000);
+
+ await setMinAmountToDepositTest({ depositVault, owner }, 100_000);
+ await setInstantLimitConfigTest(
+ { vault: depositVault, owner },
+ 150_000,
+ );
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 99,
+ {
+ revertCustomError: {
+ customErrorName: 'AmountLessThanMin',
+ },
+ },
+ );
+ });
+
+ it('should fail: if exceed allowance of deposit for token', async () => {
+ const {
+ depositVault,
+ mockedAggregator,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 4);
+
+ await mintToken(stableCoins.dai, owner, 100_000);
+ await changeTokenAllowanceTest(
+ { vault: depositVault, owner },
+ stableCoins.dai.address,
+ 100,
+ );
+ await approveBase18(owner, stableCoins.dai, depositVault, 100_000);
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 99_999,
+ {
+ revertCustomError: {
+ customErrorName: 'AllowanceExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: if mint limit exceeded', async () => {
+ const {
+ depositVault,
+ mockedAggregator,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 4);
+
+ await mintToken(stableCoins.dai, owner, 100_000);
+ await setInstantLimitConfigTest({ vault: depositVault, owner }, 1000);
+
+ await approveBase18(owner, stableCoins.dai, depositVault, 100_000);
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 99_999,
+ {
+ revertCustomError: {
+ customErrorName: 'WindowLimitExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: MV: invalid instant fee when instant fee below min', async () => {
+ const {
+ depositVault,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 4);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await mintToken(stableCoins.dai, owner, 1_000_000);
+
+ await setInstantFeeTest({ vault: depositVault, owner }, 100);
+ await setMinMaxInstantFeeTest(
+ { vault: depositVault, owner },
+ 200,
+ 10_000,
+ );
+
+ await approveBase18(owner, stableCoins.dai, depositVault, 1_000_000);
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertCustomError: {
+ customErrorName: 'InstantFeeOutOfBounds',
+ },
+ },
+ );
+ });
+
+ it('should fail: MV: invalid instant fee when instant fee above max', async () => {
+ const {
+ depositVault,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 4);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await mintToken(stableCoins.dai, owner, 1_000_000);
+
+ await setInstantFeeTest({ vault: depositVault, owner }, 5000);
+ await setMinMaxInstantFeeTest(
+ { vault: depositVault, owner },
+ 0,
+ 2000,
+ );
+
+ await approveBase18(owner, stableCoins.dai, depositVault, 1_000_000);
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertCustomError: {
+ customErrorName: 'InstantFeeOutOfBounds',
+ },
+ },
+ );
+ });
+
+ describe('depositInstant() sliding rate limit (RateLimitLibrary)', () => {
+ const setupDepositInstantRateLimitFixture = async () => {
+ const fixture = await loadDvFixture();
+ const {
+ depositVault,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ } = fixture;
+
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 4);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await mintToken(stableCoins.dai, owner, 1_000_000);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+ await approveBase18(
+ owner,
+ stableCoins.dai,
+ depositVault,
+ 1_000_000,
+ );
+
+ return fixture;
+ };
+
+ it('10h window: full consume, after 1h restores ~10% and two deposits use it', async () => {
+ const {
+ depositVault,
+ owner,
+ mTBILL,
+ stableCoins,
+ mTokenToUsdDataFeed,
+ } = await setupDepositInstantRateLimitFixture();
+
+ await setInstantLimitConfigTest(
+ { vault: depositVault, owner },
+ { window: hours(10), limit: parseUnits('1000') },
+ );
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 1000,
+ );
+
+ await increase(hours(1));
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ it('1d window: after 80% consumed and limit halved, deposit fails', async () => {
+ const {
+ depositVault,
+ owner,
+ mTBILL,
+ stableCoins,
+ mTokenToUsdDataFeed,
+ } = await setupDepositInstantRateLimitFixture();
+
+ const initialLimit = parseUnits('1000');
+
+ await setInstantLimitConfigTest(
+ { vault: depositVault, owner },
+ { window: days(1), limit: initialLimit },
+ );
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 800,
+ );
+
+ await setInstantLimitConfigTest(
+ { vault: depositVault, owner },
+ { window: days(1), limit: initialLimit.div(2) },
+ );
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertCustomError: {
+ customErrorName: 'WindowLimitExceeded',
+ },
+ },
+ );
+ });
+
+ it('1d window: after limit halved, wait 12h and deposit small amount', async () => {
+ const {
+ depositVault,
+ owner,
+ mTBILL,
+ stableCoins,
+ mTokenToUsdDataFeed,
+ } = await setupDepositInstantRateLimitFixture();
+
+ const initialLimit = parseUnits('1000');
+
+ await setInstantLimitConfigTest(
+ { vault: depositVault, owner },
+ { window: days(1), limit: initialLimit },
+ );
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 800,
+ );
+
+ await setInstantLimitConfigTest(
+ { vault: depositVault, owner },
+ { window: days(1), limit: initialLimit.div(2) },
+ );
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertCustomError: {
+ customErrorName: 'WindowLimitExceeded',
+ },
+ },
+ );
+
+ await increase(hours(18));
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 1,
+ );
+ });
+
+ it('multiple windows active at the same time', async () => {
+ const {
+ depositVault,
+ owner,
+ mTBILL,
+ stableCoins,
+ mTokenToUsdDataFeed,
+ } = await setupDepositInstantRateLimitFixture();
+
+ await setInstantLimitConfigTest(
+ { vault: depositVault, owner },
+ { window: hours(1), limit: parseUnits('100') },
+ );
+ await setInstantLimitConfigTest(
+ { vault: depositVault, owner },
+ { window: hours(6), limit: parseUnits('500') },
+ );
+ await setInstantLimitConfigTest(
+ { vault: depositVault, owner },
+ { window: days(1), limit: parseUnits('10000') },
+ );
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ {
+ revertCustomError: {
+ customErrorName: 'WindowLimitExceeded',
+ },
+ },
+ );
+
+ await increase(hours(1));
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+ });
+ });
+
+ describe('depositInstant() multiple instant limits', () => {
+ it('two windows (12h and 1d): deposit succeeds when under both', async () => {
+ const {
+ depositVault,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 4);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await mintToken(stableCoins.dai, owner, 1_000_000);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await setInstantLimitConfigTest(
+ { vault: depositVault, owner },
+ { window: hours(12), limit: parseUnits('1000') },
+ );
+ await setInstantLimitConfigTest(
+ { vault: depositVault, owner },
+ { window: days(1), limit: parseUnits('2000') },
+ );
+
+ await approveBase18(
+ owner,
+ stableCoins.dai,
+ depositVault,
+ 1_000_000,
+ );
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 500,
+ );
+ });
+
+ it('two windows: deposit fails when one window (12h) is filled', async () => {
+ const {
+ depositVault,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 4);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await mintToken(stableCoins.dai, owner, 1_000_000);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await setInstantLimitConfigTest(
+ { vault: depositVault, owner },
+ { window: hours(12), limit: parseUnits('100') },
+ );
+ await setInstantLimitConfigTest(
+ { vault: depositVault, owner },
+ { window: days(1), limit: parseUnits('10000') },
+ );
+
+ await approveBase18(
+ owner,
+ stableCoins.dai,
+ depositVault,
+ 1_000_000,
+ );
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 60,
+ );
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ {
+ revertCustomError: {
+ customErrorName: 'WindowLimitExceeded',
+ },
+ },
+ );
+ });
+
+ it('two windows: 12h epoch resets after 12h, deposit succeeds again', async () => {
+ const {
+ depositVault,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 4);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await mintToken(stableCoins.dai, owner, 1_000_000);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await setInstantLimitConfigTest(
+ { vault: depositVault, owner },
+ { window: hours(12), limit: parseUnits('100') },
+ );
+ await setInstantLimitConfigTest(
+ { vault: depositVault, owner },
+ { window: days(1), limit: parseUnits('10000') },
+ );
+
+ await approveBase18(
+ owner,
+ stableCoins.dai,
+ depositVault,
+ 1_000_000,
+ );
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await increase(hours(12));
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 10,
+ );
+ });
+ });
+
+ it('should fail: if min receive amount greater then actual', async () => {
+ const {
+ depositVault,
+ mockedAggregator,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 4);
+
+ await mintToken(stableCoins.dai, owner, 100_000);
+
+ await approveBase18(owner, stableCoins.dai, depositVault, 100_000);
+
+ await depositInstantTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ minAmount: parseUnits('100000'),
+ },
+ stableCoins.dai,
+ 99_999,
+ {
+ revertCustomError: {
+ customErrorName: 'SlippageExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: if some fee = 100%', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 10000,
+ true,
+ );
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidAmount',
+ },
+ },
+ );
+
+ await removePaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setInstantFeeTest({ vault: depositVault, owner }, 10000);
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidAmount',
+ },
+ },
+ );
+ });
+
+ it('should fail: greenlist enabled and user not in greenlist ', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await depositVault.setGreenlistEnable(true);
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 1,
+ {
+ revertCustomError: {
+ customErrorName: 'NotGreenlisted',
+ },
+ },
+ );
+ });
+
+ it('should fail: user in blacklist ', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ blackListableTester,
+ accessControl,
+ regularAccounts,
+ } = await loadDvFixture();
+
+ await blackList(
+ { blacklistable: blackListableTester, accessControl, owner },
+ regularAccounts[0],
+ );
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 1,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_BLACKLISTED,
+ },
+ );
+ });
+
+ it('should fail: user in sanctions list', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ mockedSanctionsList,
+ } = await loadDvFixture();
+
+ await sanctionUser(
+ { sanctionsList: mockedSanctionsList },
+ regularAccounts[0],
+ );
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 1,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'Sanctioned',
+ },
+ },
+ );
+ });
+
+ it('should fail: greenlist enabled and recipient not in greenlist (custom recipient overload)', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ greenListableTester,
+ accessControl,
+ customRecipient,
+ } = await loadDvFixture();
+
+ await greenList(
+ { greenlistable: greenListableTester, accessControl, owner },
+ owner,
+ );
+
+ await depositVault.setGreenlistEnable(true);
+
+ await depositInstantTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 1,
+ {
+ revertCustomError: {
+ customErrorName: 'NotGreenlisted',
+ },
+ },
+ );
+ });
+
+ it('should fail: recipient in blacklist (custom recipient overload)', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ blackListableTester,
+ accessControl,
+ regularAccounts,
+ customRecipient,
+ } = await loadDvFixture();
+
+ await blackList(
+ { blacklistable: blackListableTester, accessControl, owner },
+ customRecipient,
+ );
+
+ await depositInstantTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 1,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_BLACKLISTED,
+ },
+ );
+ });
+
+ it('should fail: recipient in sanctions list (custom recipient overload)', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ mockedSanctionsList,
+ customRecipient,
+ } = await loadDvFixture();
+
+ await sanctionUser(
+ { sanctionsList: mockedSanctionsList },
+ customRecipient,
+ );
+
+ await depositInstantTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 1,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'Sanctioned',
+ },
+ },
+ );
+ });
+
+ it('should fail: when function paused (custom recipient overload)', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ customRecipient,
+ } = await loadDvFixture();
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ const selector = encodeFnSelector(
+ 'depositInstant(address,uint256,uint256,bytes32,address)',
+ );
+ await pauseVaultFn({ pauseManager, owner }, depositVault, selector);
+ await depositInstantTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('should fail: when 0 supply cap is left', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ customRecipient,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ } = await loadDvFixture();
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setMaxSupplyCapTest(
+ { tokenContract: mTBILL, owner },
+ parseUnits('99'),
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositInstantTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 99,
+ {
+ from: regularAccounts[0],
+ },
+ );
+
+ await depositInstantTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 1,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ contract: mTBILL,
+ customErrorName: 'MaxSupplyCapExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: when 10 supply cap is left and try to mint 11', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ customRecipient,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ } = await loadDvFixture();
+ await mintToken(stableCoins.dai, regularAccounts[0], 101);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 101,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setMaxSupplyCapTest(
+ { tokenContract: mTBILL, owner },
+ parseUnits('100'),
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositInstantTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 90,
+ {
+ from: regularAccounts[0],
+ },
+ );
+
+ await depositInstantTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 11,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ contract: mTBILL,
+ customErrorName: 'MaxSupplyCapExceeded',
+ },
+ },
+ );
+ });
+
+ it('when 10 supply cap is left and try to mint 10', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ customRecipient,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ } = await loadDvFixture();
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setMaxSupplyCapTest(
+ { tokenContract: mTBILL, owner },
+ parseUnits('100'),
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositInstantTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 90,
+ {
+ from: regularAccounts[0],
+ },
+ );
+
+ await depositInstantTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 10,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('deposit 100 DAI, greenlist enabled and user in greenlist ', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ greenListableTester,
+ mTokenToUsdDataFeed,
+ accessControl,
+ regularAccounts,
+ dataFeed,
+ } = await loadDvFixture();
+
+ await depositVault.setGreenlistEnable(true);
+
+ await greenList(
+ { greenlistable: greenListableTester, accessControl, owner },
+ regularAccounts[0],
+ );
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('deposit 100 DAI when 10% growth is applied', async () => {
+ const {
+ owner,
+ depositVault,
+ customFeedGrowth,
+ stableCoins,
+ mTBILL,
+ greenListableTester,
+ mTokenToUsdDataFeed,
+ accessControl,
+ regularAccounts,
+ dataFeed,
+ } = await loadDvFixture();
+
+ await depositVault.setGreenlistEnable(true);
+
+ await mTokenToUsdDataFeed.changeAggregator(customFeedGrowth.address);
+ await setRoundDataGrowth({ owner, customFeedGrowth }, 1, -1000, 10);
+
+ await greenList(
+ { greenlistable: greenListableTester, accessControl, owner },
+ regularAccounts[0],
+ );
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositInstantTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ expectedMintAmount: parseUnits('98.999684191007430686', 18),
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('deposit 100 DAI when -10% growth is applied', async () => {
+ const {
+ owner,
+ depositVault,
+ customFeedGrowth,
+ stableCoins,
+ mTBILL,
+ greenListableTester,
+ mTokenToUsdDataFeed,
+ accessControl,
+ regularAccounts,
+ dataFeed,
+ } = await loadDvFixture();
+
+ await depositVault.setGreenlistEnable(true);
+
+ await setMinGrowthApr({ owner, customFeedGrowth }, -10);
+ await mTokenToUsdDataFeed.changeAggregator(customFeedGrowth.address);
+ await setRoundDataGrowth({ owner, customFeedGrowth }, 1, -1000, -10);
+
+ await greenList(
+ { greenlistable: greenListableTester, accessControl, owner },
+ regularAccounts[0],
+ );
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositInstantTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ expectedMintAmount: parseUnits('99.000315811007437113', 18),
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('deposit 100 DAI, greenlist enabled and user in greenlist, tokenIn not stablecoin', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ greenListableTester,
+ mTokenToUsdDataFeed,
+ accessControl,
+ regularAccounts,
+ dataFeed,
+ } = await loadDvFixture();
+
+ await depositVault.setGreenlistEnable(true);
+
+ await greenList(
+ { greenlistable: greenListableTester, accessControl, owner },
+ regularAccounts[0],
+ );
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ false,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('deposit 100 DAI, when price of stable is 1.03$ and mToken price is 5$', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ it('deposit 100 DAI, when price of stable is 1.03$ and mToken price is 5$ and token fee 1%', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 100,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ it('deposit 100 DAI, when price of stable is 1.03$ and mToken price is 5$ without checking of minDepositAmount', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 100,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await depositVault.freeFromMinAmount(owner.address, true);
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ it('deposit 100 DAI, when price of stable is 1.03$ and mToken price is 5$ and user in waivedFeeRestriction', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 100,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setWaivedFeeAccountTest(
+ { vault: depositVault, owner },
+ owner.address,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+ await depositInstantTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ waivedFee: true,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ it('deposit 100 DAI (custom recipient overload)', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ greenListableTester,
+ mTokenToUsdDataFeed,
+ accessControl,
+ regularAccounts,
+ dataFeed,
+ customRecipient,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositInstantTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('deposit 100 DAI when recipient == msg.sender (custom recipient overload)', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositInstantTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient: regularAccounts[0],
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('deposit 100 DAI when other overload of depositInstant is paused (custom recipient overload)', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ customRecipient,
+ } = await loadDvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ depositVault,
+ encodeFnSelector('depositInstant(address,uint256,uint256,bytes32)'),
+ );
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositInstantTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('deposit 100 DAI when other overload of depositInstant is paused', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadDvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ depositVault,
+ encodeFnSelector(
+ 'depositInstant(address,uint256,uint256,bytes32,address)',
+ ),
+ );
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositInstantTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+ });
+
+ describe('depositRequest()', async () => {
+ describe('holdback', () => {
+ it('when 40% instant and 60% holdback', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 40_00,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('when 90% instant and 10% holdback', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 90_00,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('when 0% instant and 100% holdback', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 0,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('when 50% instant and 50% holdback and request recipient is different from msg.sender', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ customRecipient: regularAccounts[1],
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('when 50% instant and 50% holdback and instant recipient is different from msg.sender', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ customRecipientInstant: regularAccounts[2],
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('when 50% instant and 50% holdback and request and instant recipients are different from msg.sender and from each other', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ customRecipient: regularAccounts[1],
+ customRecipientInstant: regularAccounts[2],
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: when 100% instant and 0% holdback', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 100_00,
+ customRecipient: regularAccounts[1],
+ customRecipientInstant: regularAccounts[2],
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'InvalidAmount',
+ },
+ },
+ );
+ });
+
+ it('should fail: when instant share exceeds max instant share', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await setMaxInstantShareTest({ vault: depositVault, owner }, 90_00);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 95_00,
+ customRecipient: regularAccounts[1],
+ customRecipientInstant: regularAccounts[2],
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'InstantShareTooHigh',
+ },
+ },
+ );
+ });
+ });
+
+ it('should fail: when estimated mint amount exceeds max amount per request', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+ await setMaxAmountPerRequestTest({ depositVault, owner }, 1);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertCustomError: {
+ customErrorName: 'MaxAmountPerRequestExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: if mint exceed allowance', async () => {
+ const {
+ depositVault,
+ stableCoins,
+ owner,
+ dataFeed,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await mintToken(stableCoins.dai, owner, 100000);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100000);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await changeTokenAllowanceTest(
+ { vault: depositVault, owner },
+ stableCoins.dai.address,
+ 100,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 99_999,
+ {
+ revertCustomError: {
+ customErrorName: 'AllowanceExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: when there is no token in vault', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 1,
+ {
+ revertCustomError: {
+ customErrorName: 'UnknownPaymentToken',
+ },
+ },
+ );
+ });
+
+ it('should fail: when trying to deposit 0 amount', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 0,
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidAmount',
+ },
+ },
+ );
+ });
+
+ it('should fail: MV: invalid instant fee when instant fee below min', async () => {
+ const {
+ depositVault,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 4);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await mintToken(stableCoins.dai, owner, 1_000_000);
+
+ await setInstantFeeTest({ vault: depositVault, owner }, 100);
+ await setMinMaxInstantFeeTest(
+ { vault: depositVault, owner },
+ 200,
+ 10_000,
+ );
+
+ await approveBase18(owner, stableCoins.dai, depositVault, 1_000_000);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertCustomError: {
+ customErrorName: 'InstantFeeOutOfBounds',
+ },
+ },
+ );
+ });
+
+ it('should fail: MV: invalid instant fee when instant fee above max', async () => {
+ const {
+ depositVault,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 4);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await mintToken(stableCoins.dai, owner, 1_000_000);
+
+ await setInstantFeeTest({ vault: depositVault, owner }, 5000);
+ await setMinMaxInstantFeeTest(
+ { vault: depositVault, owner },
+ 0,
+ 2000,
+ );
+
+ await approveBase18(owner, stableCoins.dai, depositVault, 1_000_000);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertCustomError: {
+ customErrorName: 'InstantFeeOutOfBounds',
+ },
+ },
+ );
+ });
+
+ it('instant limit configs are not applied', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ mockedAggregator,
+ } = await loadDvFixture();
+
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 4);
+
+ await setInstantLimitConfigTest(
+ { vault: depositVault, owner },
+ { window: hours(12), limit: parseUnits('100') },
+ );
+ await setInstantLimitConfigTest(
+ { vault: depositVault, owner },
+ { window: days(1), limit: parseUnits('100') },
+ );
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 500);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 500,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 500,
+ { from: regularAccounts[0] },
+ );
+ });
+
+ it('should fail: when function paused', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ } = await loadDvFixture();
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ const selector = encodeFnSelector(
+ 'depositRequest(address,uint256,bytes32)',
+ );
+ await pauseVaultFn({ pauseManager, owner }, depositVault, selector);
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('should fail: when function paused (custom recipient overload)', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ customRecipient,
+ } = await loadDvFixture();
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ const selector = encodeFnSelector(
+ 'depositRequest(address,uint256,bytes32,address,uint256,uint256,address)',
+ );
+ await pauseVaultFn({ pauseManager, owner }, depositVault, selector);
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('should fail: when rounding is invalid', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100.0000000001,
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidRounding',
+ },
+ },
+ );
+ });
+
+ it('should fail: call with insufficient allowance', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertMessage: 'ERC20: insufficient allowance',
+ },
+ );
+ });
+
+ it('should fail: call with insufficient balance', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertMessage: 'ERC20: transfer amount exceeds balance',
+ },
+ );
+ });
+
+ it('should fail: dataFeed rate 0 ', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mockedAggregator,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await approveBase18(owner, stableCoins.dai, depositVault, 10);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await mintToken(stableCoins.dai, owner, 100_000);
+ await setRoundData({ mockedAggregator }, 0);
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 1,
+ {
+ revertMessage: 'DF: feed is deprecated',
+ },
+ );
+ });
+
+ it('should fail: call for amount < minAmountToDepositTest', async () => {
+ const {
+ depositVault,
+ mockedAggregator,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+
+ await mintToken(stableCoins.dai, owner, 100_000);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100_000);
+
+ await setMinAmountToDepositTest({ depositVault, owner }, 100_000);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 99_999,
+ {
+ revertCustomError: {
+ customErrorName: 'LessThanMinAmountFirstDeposit',
+ },
+ },
+ );
+ });
+
+ it('should fail: if exceed allowance of deposit for token', async () => {
+ const {
+ depositVault,
+ mockedAggregator,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 4);
+
+ await mintToken(stableCoins.dai, owner, 100_000);
+ await changeTokenAllowanceTest(
+ { vault: depositVault, owner },
+ stableCoins.dai.address,
+ 100,
+ );
+ await approveBase18(owner, stableCoins.dai, depositVault, 100_000);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 99_999,
+ {
+ revertCustomError: {
+ customErrorName: 'AllowanceExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: if token fee = 100%', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 10000,
+ true,
+ );
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidAmount',
+ },
+ },
+ );
+ });
+
+ it('should fail: greenlist enabled and user not in greenlist ', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await depositVault.setGreenlistEnable(true);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 1,
+ {
+ revertCustomError: {
+ customErrorName: 'NotGreenlisted',
+ },
+ },
+ );
+ });
+
+ it('should fail: user in blacklist ', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ blackListableTester,
+ accessControl,
+ regularAccounts,
+ } = await loadDvFixture();
+
+ await blackList(
+ { blacklistable: blackListableTester, accessControl, owner },
+ regularAccounts[0],
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 1,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_BLACKLISTED,
+ },
+ );
+ });
+
+ it('should fail: user in sanctionlist ', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ mockedSanctionsList,
+ } = await loadDvFixture();
+
+ await sanctionUser(
+ { sanctionsList: mockedSanctionsList },
+ regularAccounts[0],
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 1,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'Sanctioned',
+ },
+ },
+ );
+ });
+
+ it('should fail: greenlist enabled and recipient not in greenlist (custom recipient overload)', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ greenListableTester,
+ accessControl,
+ customRecipient,
+ } = await loadDvFixture();
+
+ await depositVault.setGreenlistEnable(true);
+
+ await greenList(
+ { greenlistable: greenListableTester, accessControl, owner },
+ owner,
+ );
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 1,
+ {
+ revertCustomError: {
+ customErrorName: 'NotGreenlisted',
+ },
+ },
+ );
+ });
+
+ it('should fail: recipient in blacklist (custom recipient overload)', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ blackListableTester,
+ accessControl,
+ regularAccounts,
+ customRecipient,
+ } = await loadDvFixture();
+
+ await blackList(
+ { blacklistable: blackListableTester, accessControl, owner },
+ customRecipient,
+ );
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 1,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_BLACKLISTED,
+ },
+ );
+ });
+
+ it('should fail: recipient in sanctionlist (custom recipient overload)', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ mockedSanctionsList,
+ customRecipient,
+ } = await loadDvFixture();
+
+ await sanctionUser(
+ { sanctionsList: mockedSanctionsList },
+ customRecipient,
+ );
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 1,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'Sanctioned',
+ },
+ },
+ );
+ });
+
+ it('deposit 100 DAI, greenlist enabled and user in greenlist ', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ greenListableTester,
+ mTokenToUsdDataFeed,
+ accessControl,
+ regularAccounts,
+ dataFeed,
+ } = await loadDvFixture();
+
+ await greenListEnable(
+ { greenlistable: greenListableTester, owner },
+ true,
+ );
+
+ await greenList(
+ { greenlistable: greenListableTester, accessControl, owner },
+ regularAccounts[0],
+ );
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('deposit 100 DAI when 10% growth is applied', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ greenListableTester,
+ mTokenToUsdDataFeed,
+ accessControl,
+ regularAccounts,
+ dataFeed,
+ customFeedGrowth,
+ } = await loadDvFixture();
+
+ await mTokenToUsdDataFeed.changeAggregator(customFeedGrowth.address);
+ await setRoundDataGrowth({ owner, customFeedGrowth }, 1, -1000, 10);
+
+ await greenListEnable(
+ { greenlistable: greenListableTester, owner },
+ true,
+ );
+
+ await greenList(
+ { greenlistable: greenListableTester, accessControl, owner },
+ regularAccounts[0],
+ );
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('deposit 100 DAI when -10% growth is applied', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ greenListableTester,
+ mTokenToUsdDataFeed,
+ accessControl,
+ regularAccounts,
+ dataFeed,
+ customFeedGrowth,
+ } = await loadDvFixture();
+
+ await mTokenToUsdDataFeed.changeAggregator(customFeedGrowth.address);
+ await setMinGrowthApr({ owner, customFeedGrowth }, -10);
+ await setRoundDataGrowth({ owner, customFeedGrowth }, 1, -1000, -10);
+
+ await greenListEnable(
+ { greenlistable: greenListableTester, owner },
+ true,
+ );
+
+ await greenList(
+ { greenlistable: greenListableTester, accessControl, owner },
+ regularAccounts[0],
+ );
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('deposit request with 100 DAI, when price of stable is 1.03$ and mToken price is 5$', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ it('deposit request with 100 DAI, when price of stable is 1.03$ and mToken price is 5$ and token fee 1%', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 100,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ it('deposit request with 100 DAI, when price of stable is 1.03$ and mToken price is 5$ without checking of minDepositAmount', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 100,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await depositVault.freeFromMinAmount(owner.address, true);
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ it('deposit request with 100 DAI, when price of stable is 1.03$ and mToken price is 5$ and user in waivedFeeRestriction', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 100,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setWaivedFeeAccountTest(
+ { vault: depositVault, owner },
+ owner.address,
+ true,
+ );
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ waivedFee: true,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ it('deposit 100 (custom recipient overload)', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ customRecipient,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('deposit 100 when recipient == msg.sender (custom recipient overload)', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient: regularAccounts[0],
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('deposit 100 DAI when other overload of depositRequest is paused (custom recipient overload)', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ customRecipient,
+ } = await loadDvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ depositVault,
+ encodeFnSelector('depositRequest(address,uint256,bytes32)'),
+ );
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('deposit 100 DAI when other overload of depositRequest is paused', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadDvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ depositVault,
+ encodeFnSelector(
+ 'depositRequest(address,uint256,bytes32,address,uint256,uint256,address)',
+ ),
+ );
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: cant deposit if effective supply is > max cap', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ customRecipient,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ } = await loadDvFixture();
+ await mintToken(stableCoins.dai, regularAccounts[0], 101);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 101,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setMaxSupplyCapTest(
+ { tokenContract: mTBILL, owner },
+ parseUnits('100'),
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositInstantTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 99,
+ {
+ from: regularAccounts[0],
+ },
+ );
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 2,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'SupplyCapExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: cant deposit if effective supply is > max cap when pending requests fill cap', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ } = await loadDvFixture();
+ await mintToken(stableCoins.dai, regularAccounts[0], 120);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 120,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setMaxSupplyCapTest(
+ { tokenContract: mTBILL, owner },
+ parseUnits('100'),
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 60,
+ {
+ from: regularAccounts[0],
+ },
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'SupplyCapExceeded',
+ },
+ },
+ );
+ });
+ });
+
+ describe('approveRequest()', async () => {
+ it('should fail: when request id exceeds maxApproveRequestId', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await setMaxApproveRequestIdTest({ vault: depositVault, owner }, 0);
+
+ await approveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ 1,
+ parseUnits('5'),
+ {
+ revertCustomError: {
+ customErrorName: 'RequestIdTooHigh',
+ args: [1, 0],
+ },
+ },
+ );
+ });
+
+ describe('isAvgRate=false', async () => {
+ it('should fail: call from address without vault admin role', async () => {
+ const {
+ depositVault,
+ regularAccounts,
+ mTokenToUsdDataFeed,
+ mTBILL,
+ } = await loadDvFixture();
+ await approveRequestTest(
+ {
+ depositVault,
+ owner: regularAccounts[1],
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ 1,
+ parseUnits('5'),
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('should fail: request by id not exist', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 1,
+ parseUnits('5'),
+ {
+ revertCustomError: {
+ customErrorName: 'RequestNotExists',
+ },
+ },
+ );
+ });
+
+ describe('request addresses access', () => {
+ const setupPendingDepositRequest = async (
+ fixture: Awaited>,
+ opts?: {
+ customRecipient?: SignerWithAddress;
+ customClaimer?: SignerWithAddress;
+ },
+ ) => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ } = fixture;
+
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData(
+ { mockedAggregator: mockedAggregatorMToken },
+ 5,
+ );
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient: opts?.customRecipient ?? regularAccounts[0],
+ ...(opts?.customClaimer
+ ? {
+ customClaimer: opts.customClaimer,
+ instantShare: 0,
+ }
+ : {}),
+ },
+ stableCoins.dai,
+ 100,
+ { from: regularAccounts[0] },
+ );
+ };
+
+ it('should fail: approve request when recipient got blacklisted', async () => {
+ const fixture = await loadDvFixture();
+ const {
+ owner,
+ depositVault,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ blackListableTester,
+ accessControl,
+ regularAccounts,
+ } = fixture;
+ await setupPendingDepositRequest(fixture);
+
+ await blackList(
+ { blacklistable: blackListableTester, accessControl, owner },
+ regularAccounts[0],
+ );
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 0,
+ parseUnits('5'),
+ { revertCustomError: acErrors.WMAC_BLACKLISTED },
+ );
+ });
+
+ it('should fail: approve request when recipient got ungreenlisted when greenlist enable flag is true', async () => {
+ const fixture = await loadDvFixture();
+ const { owner, depositVault, mTBILL, mTokenToUsdDataFeed } =
+ fixture;
+ await setupPendingDepositRequest(fixture);
+
+ await depositVault.setGreenlistEnable(true);
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 0,
+ parseUnits('5'),
+ {
+ revertCustomError: {
+ customErrorName: 'NotGreenlisted',
+ },
+ },
+ );
+ });
+
+ it('should fail: approve request when recipient got sanction listed', async () => {
+ const fixture = await loadDvFixture();
+ const {
+ owner,
+ depositVault,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ mockedSanctionsList,
+ } = fixture;
+ await setupPendingDepositRequest(fixture);
+
+ await sanctionUser(
+ { sanctionsList: mockedSanctionsList },
+ regularAccounts[0],
+ );
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 0,
+ parseUnits('5'),
+ {
+ revertCustomError: {
+ customErrorName: 'Sanctioned',
+ },
+ },
+ );
+ });
+ });
+
+ it('should fail: request already precessed', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ requestId,
+ parseUnits('5'),
+ );
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ requestId,
+ parseUnits('5'),
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('approve request from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ requestId,
+ parseUnits('5'),
+ );
+ });
+
+ it('should succeed when other approve entrypoints are paused', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await pauseOtherDepositApproveFns(
+ depositVault,
+ encodeFnSelector('approveRequest(uint256,uint256,bool)'),
+ );
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ requestId,
+ parseUnits('5'),
+ );
+ });
+
+ it('approve request should decrease pending supply', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMaxSupplyCapTest(
+ { tokenContract: mTBILL, owner },
+ parseUnits('100'),
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 0,
+ parseUnits('1'),
+ );
+ });
+
+ it('should fail: when after approval supply exceeds max cap', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMaxSupplyCapTest(
+ { tokenContract: mTBILL, owner },
+ parseUnits('100'),
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 90,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 10,
+ );
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 0,
+ parseUnits('0.99'),
+ {
+ revertCustomError: {
+ customErrorName: 'SupplyCapExceeded',
+ },
+ },
+ );
+ });
+
+ it('should approve request in non sequential order when sequentialRequestProcessing is disabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 1,
+ parseUnits('1'),
+ );
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 0,
+ parseUnits('1'),
+ );
+ });
+
+ it('should fail: approve request in non sequential order when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: depositVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 0,
+ parseUnits('1'),
+ );
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 2,
+ parseUnits('1'),
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidRequestSequence',
+ args: [2, 1],
+ },
+ },
+ );
+ });
+
+ it('should approve request id 0 and then id 1 when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: depositVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 0,
+ parseUnits('1'),
+ );
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 1,
+ parseUnits('1'),
+ );
+ });
+
+ it('should enforce fifo across separate transactions when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: depositVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, owner, 500);
+ await approveBase18(owner, stableCoins.dai, depositVault, 500);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await asyncForEach(
+ Array.from({ length: 9 }, (_, i) => i),
+ async (i) => {
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+ },
+ true,
+ );
+
+ await asyncForEach(
+ [0, 1, 2],
+ async (requestId) => {
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ requestId,
+ parseUnits('1'),
+ );
+ },
+ true,
+ );
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 3,
+ parseUnits('1'),
+ );
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 5,
+ parseUnits('1'),
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidRequestSequence',
+ args: [5, 4],
+ },
+ },
+ );
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 4,
+ parseUnits('1'),
+ );
+
+ await asyncForEach(
+ [6, 7, 8],
+ async (requestId) => {
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ requestId,
+ parseUnits('1'),
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidRequestSequence',
+ args: [requestId, 5],
+ },
+ },
+ );
+ },
+ true,
+ );
+
+ await asyncForEach(
+ [5, 6, 7],
+ async (requestId) => {
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ requestId,
+ parseUnits('1'),
+ );
+ },
+ true,
+ );
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 8,
+ parseUnits('1'),
+ );
+ });
+ });
+
+ describe('isAvgRate=true', async () => {
+ it('should fail: call from address without vault admin role', async () => {
+ const {
+ depositVault,
+ regularAccounts,
+ mTokenToUsdDataFeed,
+ mTBILL,
+ } = await loadDvFixture();
+ await approveRequestTest(
+ {
+ depositVault,
+ owner: regularAccounts[1],
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ 1,
+ parseUnits('1'),
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('should fail: request by id not exist', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await approveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ 1,
+ parseUnits('1'),
+ {
+ revertCustomError: {
+ customErrorName: 'RequestNotExists',
+ },
+ },
+ );
+ });
+
+ it('when instant part is 0', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await approveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ 0,
+ parseUnits('5'),
+ );
+ });
+
+ it('if new rate greater then variabilityTolerance', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ } = await loadDvFixture();
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+ await approveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ requestId,
+ parseUnits('6'),
+ );
+ });
+
+ it('if new rate lower then variabilityTolerance', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ } = await loadDvFixture();
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+ await approveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ requestId,
+ parseUnits('4'),
+ );
+ });
+
+ it('should fail: request already precessed', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await approveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ requestId,
+ parseUnits('5.000001'),
+ );
+ await approveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ requestId,
+ parseUnits('5.000001'),
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('approve request should decrease pending supply', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setVariabilityToleranceTest(
+ { vault: depositVault, owner },
+ 1000,
+ );
+ await setMaxSupplyCapTest(
+ { tokenContract: mTBILL, owner },
+ parseUnits('100'),
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await approveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ 0,
+ parseUnits('1'),
+ );
+ });
+
+ it('should fail: when after approval supply exceeds max cap', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setVariabilityToleranceTest(
+ { vault: depositVault, owner },
+ 1000,
+ );
+ await setMaxSupplyCapTest(
+ { tokenContract: mTBILL, owner },
+ parseUnits('100'),
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 90,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 10,
+ );
+
+ await approveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ 0,
+ parseUnits('0.99'),
+ {
+ revertCustomError: {
+ customErrorName: 'SupplyCapExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: when 0 supply cap is left', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ customRecipient,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ } = await loadDvFixture();
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setVariabilityToleranceTest(
+ { vault: depositVault, owner },
+ 1000,
+ );
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMaxSupplyCapTest(
+ { tokenContract: mTBILL, owner },
+ parseUnits('100'),
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositInstantTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 99,
+ {
+ from: regularAccounts[0],
+ },
+ );
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 1,
+ {
+ from: regularAccounts[0],
+ },
+ );
+
+ await approveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ 0,
+ parseUnits('0.99'),
+ {
+ revertCustomError: {
+ customErrorName: 'SupplyCapExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: when 10 supply cap is left and try to mint more', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ customRecipient,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ } = await loadDvFixture();
+ await mintToken(stableCoins.dai, regularAccounts[0], 101);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 101,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setVariabilityToleranceTest(
+ { vault: depositVault, owner },
+ 1000,
+ );
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMaxSupplyCapTest(
+ { tokenContract: mTBILL, owner },
+ parseUnits('100'),
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositInstantTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 90,
+ {
+ from: regularAccounts[0],
+ },
+ );
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 10,
+ {
+ from: regularAccounts[0],
+ },
+ );
+
+ await approveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ 0,
+ parseUnits('0.99'),
+ {
+ revertCustomError: {
+ customErrorName: 'SupplyCapExceeded',
+ },
+ },
+ );
+ });
+
+ it('when 10 supply cap is left and try to mint 10', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ customRecipient,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ } = await loadDvFixture();
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setMaxSupplyCapTest(
+ { tokenContract: mTBILL, owner },
+ parseUnits('100'),
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositInstantTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 90,
+ {
+ from: regularAccounts[0],
+ },
+ );
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 10,
+ {
+ from: regularAccounts[0],
+ },
+ );
+
+ await approveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ 0,
+ parseUnits('1'),
+ );
+ });
+
+ it('approve request from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await approveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ requestId,
+ parseUnits('5.000000001'),
+ );
+ });
+
+ it('should succeed when other approve entrypoints are paused', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await pauseOtherDepositApproveFns(
+ depositVault,
+ encodeFnSelector('approveRequest(uint256,uint256,bool)'),
+ );
+
+ await approveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ requestId,
+ parseUnits('5'),
+ );
+ });
+
+ it('should approve request in non sequential order when sequentialRequestProcessing is disabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setVariabilityToleranceTest(
+ { vault: depositVault, owner },
+ 1000,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await approveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ 1,
+ parseUnits('1'),
+ );
+
+ await approveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ 0,
+ parseUnits('1'),
+ );
+ });
+
+ it('should fail: approve request in non sequential order when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: depositVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setVariabilityToleranceTest(
+ { vault: depositVault, owner },
+ 1000,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await asyncForEach(
+ Array.from({ length: 3 }, (_, i) => i),
+ async (i) => {
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+ },
+ true,
+ );
+
+ await approveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ 0,
+ parseUnits('1'),
+ );
+
+ await approveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ 2,
+ parseUnits('1'),
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidRequestSequence',
+ args: [2, 1],
+ },
+ },
+ );
+ });
+
+ it('should approve request id 0 and then id 1 when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: depositVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setVariabilityToleranceTest(
+ { vault: depositVault, owner },
+ 1000,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await approveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ 0,
+ parseUnits('1'),
+ );
+
+ await approveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ 1,
+ parseUnits('1'),
+ );
+ });
+ });
+ });
+
+ describe('safeBulkApproveRequestAtSavedRate()', async () => {
+ it('should fail: call from address without vault admin role', async () => {
+ const { depositVault, regularAccounts, mTokenToUsdDataFeed, mTBILL } =
+ await loadDvFixture();
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner: regularAccounts[1],
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ [{ id: 1 }],
+ 'request-rate',
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('should fail: request by id not exist', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ [{ id: 1 }],
+ 'request-rate',
+ {
+ revertCustomError: {
+ customErrorName: 'RequestNotExists',
+ },
+ },
+ );
+ });
+
+ it('should skip requests above maxApproveRequestId without reverting', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await setMaxApproveRequestIdTest({ vault: depositVault, owner }, 0);
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 0 }, { id: 1, expectedToExecute: false }],
+ 'request-rate',
+ );
+ });
+
+ it('should fail: request already precessed', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ 'request-rate',
+ );
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ 'request-rate',
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('should fail: process multiple requests, when one of them already precessed', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 0,
+ parseUnits('5.000001'),
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }, { id: 0 }],
+ 'request-rate',
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('should fail: process multiple requests, when couple of them have equal id', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 0,
+ parseUnits('5.000001'),
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }, { id: 1 }],
+ 'request-rate',
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('approve 2 requests it should decrese the upcoming supply value fully', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+ await setMaxSupplyCapTest(
+ { tokenContract: mTBILL, owner },
+ parseUnits('100'),
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 0 }, { id: 1 }],
+ 'request-rate',
+ );
+ });
+
+ it('approve 1 request from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ 'request-rate',
+ );
+ });
+
+ it('approve 2 requests from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 0 }, { id: 1 }],
+ 'request-rate',
+ );
+ });
+
+ it('approve 10 requests from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 1000);
+ await approveBase18(owner, stableCoins.dai, depositVault, 1000);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await asyncForEach(
+ Array.from({ length: 10 }, (_, i) => i),
+ async (i) => {
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ },
+ true,
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ Array.from({ length: 10 }, (_, i) => ({ id: i })),
+ 'request-rate',
+ );
+ });
+
+ it('should succeed when other approve entrypoints are paused', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await pauseOtherDepositApproveFns(
+ depositVault,
+ encodeFnSelector('safeBulkApproveRequestAtSavedRate(uint256[])'),
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ 'request-rate',
+ );
+ });
+
+ it('should approve requests in non sequential order when sequentialRequestProcessing is disabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }, { id: 0 }],
+ 'request-rate',
+ );
+ });
+
+ it('should skip out-of-order bulk approvals without reverting when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: depositVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [
+ { id: 1, expectedToExecute: false },
+ { id: 0, expectedToExecute: true },
+ ],
+ 'request-rate',
+ );
+ });
+
+ it('should approve requests in sequential order when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: depositVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await asyncForEach(
+ Array.from({ length: 3 }, (_, i) => i),
+ async (i) => {
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+ },
+ true,
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 0 }, { id: 1 }, { id: 2 }],
+ 'request-rate',
+ );
+ });
+
+ it('should not approve requests after max supply cap when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: depositVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setVariabilityToleranceTest(
+ { vault: depositVault, owner },
+ 2000,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+ await setMaxSupplyCapTest(
+ { tokenContract: mTBILL, owner },
+ parseUnits('100'),
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 40,
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 0 }, { id: 1, expectedToExecute: false }],
+ parseUnits('0.899'),
+ );
+ });
+ });
+
+ describe('safeBulkApproveRequest() (custom price overload)', async () => {
+ it('should fail: call from address without vault admin role', async () => {
+ const { depositVault, regularAccounts, mTokenToUsdDataFeed, mTBILL } =
+ await loadDvFixture();
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner: regularAccounts[1],
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ [{ id: 1 }],
+ parseUnits('1'),
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('should fail: request by id not exist', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ [{ id: 1 }],
+ parseUnits('1'),
+ {
+ revertCustomError: {
+ customErrorName: 'RequestNotExists',
+ },
+ },
+ );
+ });
+
+ it('should skip requests above maxApproveRequestId without reverting', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await setMaxApproveRequestIdTest({ vault: depositVault, owner }, 0);
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 0 }, { id: 1, expectedToExecute: false }],
+ parseUnits('5'),
+ );
+ });
+
+ it('should fail: if new rate greater then variabilityTolerance', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ } = await loadDvFixture();
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ [{ id: requestId }],
+ parseUnits('6'),
+ {
+ revertCustomError: {
+ customErrorName: 'PriceVariationExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: if new rate lower then variabilityTolerance', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ } = await loadDvFixture();
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ [{ id: requestId }],
+ parseUnits('4'),
+ {
+ revertCustomError: {
+ customErrorName: 'PriceVariationExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: request already precessed', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ parseUnits('5.000001'),
+ );
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ parseUnits('5.000001'),
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('should fail: process multiple requests, when one of them already precessed', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 0,
+ parseUnits('5.000001'),
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }, { id: 0 }],
+ parseUnits('5.000001'),
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('should fail: process multiple requests, when couple of them have equal id', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 0,
+ parseUnits('5.000001'),
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }, { id: 1 }],
+ parseUnits('5.000001'),
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('approve 2 requests when second one exceeds supply cap', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setVariabilityToleranceTest(
+ { vault: depositVault, owner },
+ 2000,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+ await setMaxSupplyCapTest(
+ { tokenContract: mTBILL, owner },
+ parseUnits('100'),
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 40,
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 0 }, { id: 1, expectedToExecute: false }],
+ parseUnits('0.899'),
+ );
+ });
+
+ it('approve 1 request from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ parseUnits('5.000000001'),
+ );
+ });
+
+ it('approve 2 requests from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 0 }, { id: 1 }],
+ parseUnits('5.000000001'),
+ );
+ });
+
+ it('approve 10 requests from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 1000);
+ await approveBase18(owner, stableCoins.dai, depositVault, 1000);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await asyncForEach(
+ Array.from({ length: 10 }, (_, i) => i),
+ async (i) => {
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ },
+ true,
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ Array.from({ length: 10 }, (_, i) => ({ id: i })),
+ parseUnits('5.000000001'),
+ );
+ });
+
+ it('should succeed when other approve entrypoints are paused', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await pauseOtherDepositApproveFns(
+ depositVault,
+ encodeFnSelector('safeBulkApproveRequest(uint256[],uint256)'),
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ parseUnits('5.000000001'),
+ );
+ });
+
+ it('should approve requests in non sequential order when sequentialRequestProcessing is disabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setVariabilityToleranceTest(
+ { vault: depositVault, owner },
+ 2000,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }, { id: 0 }],
+ parseUnits('1'),
+ );
+ });
+
+ it('should skip out-of-order bulk approvals without reverting when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: depositVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setVariabilityToleranceTest(
+ { vault: depositVault, owner },
+ 2000,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [
+ { id: 1, expectedToExecute: false },
+ { id: 0, expectedToExecute: true },
+ ],
+ parseUnits('1'),
+ );
+ });
+
+ it('should approve requests in sequential order when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: depositVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setVariabilityToleranceTest(
+ { vault: depositVault, owner },
+ 2000,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await asyncForEach(
+ Array.from({ length: 3 }, (_, i) => i),
+ async (i) => {
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+ },
+ true,
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 0 }, { id: 1 }, { id: 2 }],
+ parseUnits('1'),
+ );
+ });
+
+ it('should not approve requests after max supply cap when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: depositVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setVariabilityToleranceTest(
+ { vault: depositVault, owner },
+ 2000,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+ await setMaxSupplyCapTest(
+ { tokenContract: mTBILL, owner },
+ parseUnits('100'),
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 40,
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 0 }, { id: 1, expectedToExecute: false }],
+ parseUnits('0.899'),
+ );
+ });
+ });
+
+ describe('safeBulkApproveRequestAvgRate() (custom price overload)', async () => {
+ it('should fail: call from address without vault admin role', async () => {
+ const { depositVault, regularAccounts, mTokenToUsdDataFeed, mTBILL } =
+ await loadDvFixture();
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner: regularAccounts[1],
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }],
+ parseUnits('1'),
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('should fail: request by id not exist', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }],
+ parseUnits('1'),
+ {
+ revertCustomError: {
+ customErrorName: 'RequestNotExists',
+ },
+ },
+ );
+ });
+
+ it('should skip requests above maxApproveRequestId without reverting', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await setMaxApproveRequestIdTest({ vault: depositVault, owner }, 0);
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 0 }, { id: 1, expectedToExecute: false }],
+ parseUnits('5'),
+ );
+ });
+
+ it('should fail: if new rate greater then variabilityTolerance', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ } = await loadDvFixture();
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: requestId }],
+ parseUnits('6'),
+ {
+ revertCustomError: {
+ customErrorName: 'PriceVariationExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: if new rate lower then variabilityTolerance', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ } = await loadDvFixture();
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 95_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: requestId }],
+ parseUnits('4'),
+ {
+ revertCustomError: {
+ customErrorName: 'PriceVariationExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: request already precessed', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ parseUnits('5.000001'),
+ );
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: requestId }],
+ parseUnits('5.000001'),
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('should fail: process multiple requests, when one of them already precessed', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 0,
+ parseUnits('5.000001'),
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }, { id: 0 }],
+ parseUnits('5.000001'),
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('should fail: process multiple requests, when couple of them have equal id', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 0,
+ parseUnits('5.000001'),
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }, { id: 1 }],
+ parseUnits('5.000001'),
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('process multiple requests, when one of them does not have instant part', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.001);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }, { id: 0 }],
+ parseUnits('5.00001'),
+ );
+ });
+
+ it('approve 2 requests when second one exceeds supply cap', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setVariabilityToleranceTest(
+ { vault: depositVault, owner },
+ 2000,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+ await setMaxSupplyCapTest(
+ { tokenContract: mTBILL, owner },
+ parseUnits('100'),
+ );
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 50,
+ );
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 40,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 0 }, { id: 1, expectedToExecute: false }],
+ parseUnits('0.899'),
+ );
+ });
+
+ it('approve 1 request from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: requestId }],
+ parseUnits('5.000000001'),
+ );
+ });
+
+ it('approve 2 requests from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 0 }, { id: 1 }],
+ parseUnits('5.000000001'),
+ );
+ });
+
+ it('approve 10 requests from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 1000);
+ await approveBase18(owner, stableCoins.dai, depositVault, 1000);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await asyncForEach(
+ Array.from({ length: 10 }, (_, i) => i),
+ async (i) => {
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ },
+ true,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ Array.from({ length: 10 }, (_, i) => ({ id: i })),
+ parseUnits('5.000000001'),
+ );
+ });
+
+ it('should succeed when other approve entrypoints are paused', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await pauseOtherDepositApproveFns(
+ depositVault,
+ encodeFnSelector(
+ 'safeBulkApproveRequestAvgRate(uint256[],uint256)',
+ ),
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: requestId }],
+ parseUnits('5.000000001'),
+ );
+ });
+
+ it('should approve requests in non sequential order when sequentialRequestProcessing is disabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setVariabilityToleranceTest(
+ { vault: depositVault, owner },
+ 2000,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }, { id: 0 }],
+ parseUnits('5'),
+ );
+ });
+
+ it('should skip out-of-order bulk approvals without reverting when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: depositVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setVariabilityToleranceTest(
+ { vault: depositVault, owner },
+ 2000,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [
+ { id: 1, expectedToExecute: false },
+ { id: 0, expectedToExecute: true },
+ ],
+ parseUnits('5'),
+ );
+ });
+
+ it('should approve requests in sequential order when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: depositVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, owner, 300);
+ await approveBase18(owner, stableCoins.dai, depositVault, 300);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setVariabilityToleranceTest(
+ { vault: depositVault, owner },
+ 2000,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await asyncForEach(
+ Array.from({ length: 3 }, (_, i) => i),
+ async (i) => {
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ },
+ true,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 0 }, { id: 1 }, { id: 2 }],
+ parseUnits('5'),
+ );
+ });
+
+ it('should not approve requests after max supply cap when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: depositVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setVariabilityToleranceTest(
+ { vault: depositVault, owner },
+ 2000,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+ await setMaxSupplyCapTest(
+ { tokenContract: mTBILL, owner },
+ parseUnits('100'),
+ );
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 50,
+ );
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 40,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 0 }, { id: 1, expectedToExecute: false }],
+ parseUnits('0.899'),
+ );
+ });
+ });
+
+ describe('safeBulkApproveRequest() (current price overload)', async () => {
+ it('should fail: call from address without vault admin role', async () => {
+ const { depositVault, regularAccounts, mTokenToUsdDataFeed, mTBILL } =
+ await loadDvFixture();
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner: regularAccounts[1],
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ [{ id: 1 }],
+ undefined,
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('should fail: request by id not exist', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ [{ id: 1 }],
+ undefined,
+ {
+ revertCustomError: {
+ customErrorName: 'RequestNotExists',
+ },
+ },
+ );
+ });
+
+ it('should skip requests above maxApproveRequestId without reverting', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await setMaxApproveRequestIdTest({ vault: depositVault, owner }, 0);
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 0 }, { id: 1, expectedToExecute: false }],
+ undefined,
+ );
+ });
+
+ it('should fail: if new rate greater then variabilityTolerance', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ } = await loadDvFixture();
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 10);
+
+ const requestId = 0;
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ [{ id: requestId }],
+ undefined,
+ {
+ revertCustomError: {
+ customErrorName: 'PriceVariationExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: if new rate lower then variabilityTolerance', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ } = await loadDvFixture();
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 3);
+
+ const requestId = 0;
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ [{ id: requestId }],
+ undefined,
+ {
+ revertCustomError: {
+ customErrorName: 'PriceVariationExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: request already precessed', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ undefined,
+ );
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ undefined,
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('should fail: process multiple requests, when one of them already precessed', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 0 }],
+ undefined,
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }, { id: 0 }],
+ undefined,
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('should fail: process multiple requests, when couple of the have equal id', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 0 }],
+ undefined,
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }, { id: 1 }],
+ undefined,
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('approve 1 request from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ undefined,
+ );
+ });
+
+ it('approve 1 request from vaut admin account when growth is applied', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ customFeedGrowth,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await setRoundDataGrowth({ owner, customFeedGrowth }, 1, -1000, 5);
+ await mTokenToUsdDataFeed.changeAggregator(customFeedGrowth.address);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ [{ id: requestId }],
+ undefined,
+ );
+ });
+
+ it('approve 2 requests from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 0 }, { id: 1 }],
+ undefined,
+ );
+ });
+
+ it('approve 10 requests from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 1000);
+ await approveBase18(owner, stableCoins.dai, depositVault, 1000);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await asyncForEach(
+ Array.from({ length: 10 }, (_, i) => i),
+ async (i) => {
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ },
+ true,
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ Array.from({ length: 10 }, (_, i) => ({ id: i })),
+ undefined,
+ );
+ });
+
+ it('approve 10 requests from vaut admin account when different users are recievers', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 1000);
+ await approveBase18(owner, stableCoins.dai, depositVault, 1000);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await asyncForEach(
+ Array.from({ length: 10 }, (_, i) => i),
+ async (i) => {
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient: regularAccounts[i],
+ },
+ stableCoins.dai,
+ 100,
+ );
+ },
+ true,
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ Array.from({ length: 10 }, (_, i) => ({ id: i })),
+ undefined,
+ );
+ });
+
+ it('approve 2 requests from vaut admin account when each request has different token', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await mintToken(stableCoins.usdc, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await approveBase18(owner, stableCoins.usdc, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.usdc,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 0 }, { id: 1 }],
+ undefined,
+ );
+ });
+
+ it('should succeed when other approve entrypoints are paused', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await pauseOtherDepositApproveFns(
+ depositVault,
+ encodeFnSelector('safeBulkApproveRequest(uint256[])'),
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ undefined,
+ );
+ });
+
+ it('should approve requests in non sequential order when sequentialRequestProcessing is disabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setVariabilityToleranceTest(
+ { vault: depositVault, owner },
+ 2000,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }, { id: 0 }],
+ undefined,
+ );
+ });
+
+ it('should skip out-of-order bulk approvals without reverting when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: depositVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setVariabilityToleranceTest(
+ { vault: depositVault, owner },
+ 2000,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [
+ { id: 1, expectedToExecute: false },
+ { id: 0, expectedToExecute: true },
+ ],
+ undefined,
+ );
+ });
+
+ it('should approve requests in sequential order when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: depositVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setVariabilityToleranceTest(
+ { vault: depositVault, owner },
+ 2000,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await asyncForEach(
+ Array.from({ length: 3 }, (_, i) => i),
+ async (i) => {
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+ },
+ true,
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 0 }, { id: 1 }, { id: 2 }],
+ undefined,
+ );
+ });
+
+ it('should not approve requests after max supply cap when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: depositVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setVariabilityToleranceTest(
+ { vault: depositVault, owner },
+ 2000,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+ await setMaxSupplyCapTest(
+ { tokenContract: mTBILL, owner },
+ parseUnits('100'),
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 40,
+ );
+
+ await safeBulkApproveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 0 }, { id: 1, expectedToExecute: false }],
+ parseUnits('0.899'),
+ );
+ });
+ });
+
+ describe('safeBulkApproveRequestAvgRate() (current price overload)', async () => {
+ it('should fail: call from address without vault admin role', async () => {
+ const { depositVault, regularAccounts, mTokenToUsdDataFeed, mTBILL } =
+ await loadDvFixture();
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner: regularAccounts[1],
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }],
+ undefined,
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('should fail: request by id not exist', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }],
+ undefined,
+ {
+ revertCustomError: {
+ customErrorName: 'RequestNotExists',
+ },
+ },
+ );
+ });
+
+ it('should skip requests above maxApproveRequestId without reverting', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await setMaxApproveRequestIdTest({ vault: depositVault, owner }, 0);
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 0 }, { id: 1, expectedToExecute: false }],
+ undefined,
+ );
+ });
+
+ it('should fail: if new rate greater then variabilityTolerance', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ } = await loadDvFixture();
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 1);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 10);
+
+ const requestId = 0;
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: requestId }],
+ undefined,
+ {
+ revertCustomError: {
+ customErrorName: 'PriceVariationExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: if new rate lower then variabilityTolerance', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ } = await loadDvFixture();
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 3);
+
+ const requestId = 0;
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: requestId }],
+ undefined,
+ {
+ revertCustomError: {
+ customErrorName: 'PriceVariationExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: request already precessed', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: requestId }],
+ undefined,
+ );
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: requestId }],
+ undefined,
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('should fail: process multiple requests, when one of them already precessed', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 0 }],
+ undefined,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }, { id: 0 }],
+ undefined,
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('should fail: process multiple requests, when couple of the have equal id', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 0 }],
+ undefined,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }, { id: 1 }],
+ undefined,
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('when one of the requests instant part is 0', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.001);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }, { id: 0 }],
+ undefined,
+ );
+ });
+
+ it('approve 1 request from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: requestId }],
+ undefined,
+ );
+ });
+
+ it('approve 1 request from vaut admin account when growth is applied', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ customFeedGrowth,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await setRoundDataGrowth({ owner, customFeedGrowth }, 1, -1000, 5);
+ await mTokenToUsdDataFeed.changeAggregator(customFeedGrowth.address);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: requestId }],
+ undefined,
+ );
+ });
+
+ it('approve 2 requests from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 0 }, { id: 1 }],
+ undefined,
+ );
+ });
+
+ it('approve 10 requests from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 1000);
+ await approveBase18(owner, stableCoins.dai, depositVault, 1000);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await asyncForEach(
+ Array.from({ length: 10 }, (_, i) => i),
+ async (i) => {
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ },
+ true,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ Array.from({ length: 10 }, (_, i) => ({ id: i })),
+ undefined,
+ );
+ });
+
+ it('approve 10 requests from vaut admin account when different users are recievers', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 1000);
+ await approveBase18(owner, stableCoins.dai, depositVault, 1000);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await asyncForEach(
+ Array.from({ length: 10 }, (_, i) => i),
+ async (i) => {
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient: regularAccounts[i],
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ },
+ true,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ Array.from({ length: 10 }, (_, i) => ({ id: i })),
+ undefined,
+ );
+ });
+
+ it('approve 2 requests from vaut admin account when each request has different token', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await mintToken(stableCoins.usdc, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await approveBase18(owner, stableCoins.usdc, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.usdc,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 0 }, { id: 1 }],
+ undefined,
+ );
+ });
+
+ it('should succeed when other approve entrypoints are paused', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await pauseOtherDepositApproveFns(
+ depositVault,
+ encodeFnSelector('safeBulkApproveRequestAvgRate(uint256[])'),
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: requestId }],
+ undefined,
+ );
+ });
+
+ it('should approve requests in non sequential order when sequentialRequestProcessing is disabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setVariabilityToleranceTest(
+ { vault: depositVault, owner },
+ 2000,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }, { id: 0 }],
+ undefined,
+ );
+ });
+
+ it('should skip out-of-order bulk approvals without reverting when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: depositVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setVariabilityToleranceTest(
+ { vault: depositVault, owner },
+ 2000,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [
+ { id: 1, expectedToExecute: false },
+ { id: 0, expectedToExecute: true },
+ ],
+ undefined,
+ );
+ });
+
+ it('should approve requests in sequential order when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: depositVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, owner, 300);
+ await approveBase18(owner, stableCoins.dai, depositVault, 300);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setVariabilityToleranceTest(
+ { vault: depositVault, owner },
+ 2000,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await asyncForEach(
+ Array.from({ length: 3 }, (_, i) => i),
+ async (i) => {
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ },
+ true,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 0 }, { id: 1 }, { id: 2 }],
+ undefined,
+ );
+ });
+ });
+
+ describe('rejectRequest()', async () => {
+ it('should fail: call from address without vault admin role', async () => {
+ const { depositVault, regularAccounts, mTokenToUsdDataFeed, mTBILL } =
+ await loadDvFixture();
+ await rejectRequestTest(
+ {
+ depositVault,
+ owner: regularAccounts[1],
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ 1,
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('should fail: request by id not exist', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await rejectRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 1,
+ {
+ revertCustomError: {
+ customErrorName: 'RequestNotExists',
+ },
+ },
+ );
+ });
+
+ it('should fail: request is already rejected', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await rejectRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ requestId,
+ );
+
+ await rejectRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ requestId,
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('reject request from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await rejectRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ requestId,
+ );
+ });
+
+ it('rejecting request should decrease pending supply', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMaxSupplyCapTest(
+ { tokenContract: mTBILL, owner },
+ parseUnits('100'),
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setInstantFeeTest({ vault: depositVault, owner }, 0);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+
+ await rejectRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 0,
+ );
+ });
+
+ it('should reject request id 0 and then id 1 when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: depositVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, owner, 200);
+ await approveBase18(owner, stableCoins.dai, depositVault, 200);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await rejectRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 0,
+ );
+
+ await rejectRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 1,
+ );
+ });
+
+ it('should fail: reject request id in non sequential order when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: depositVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, owner, 300);
+ await approveBase18(owner, stableCoins.dai, depositVault, 300);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await asyncForEach(
+ Array.from({ length: 3 }, (_, i) => i),
+ async (i) => {
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ },
+ true,
+ );
+
+ await rejectRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 2,
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidRequestSequence',
+ args: [2, 0],
+ },
+ },
+ );
+ });
+ });
+
+ describe('depositInstant() complex', () => {
+ it('should fail: when is paused', async () => {
+ const {
+ depositVault,
+ owner,
+ mTBILL,
+ stableCoins,
+ regularAccounts,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await pauseVault({ pauseManager, owner }, depositVault);
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('is on pause, but admin can use everything', async () => {
+ const {
+ depositVault,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await pauseVault({ pauseManager, owner }, depositVault);
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('call for amount == minAmountToDepositTest', async () => {
+ const {
+ depositVault,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+
+ await mintToken(stableCoins.dai, owner, 102_000);
+ await approveBase18(owner, stableCoins.dai, depositVault, 102_000);
+
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setMinAmountToDepositTest({ depositVault, owner }, 100_000);
+ await setInstantLimitConfigTest(
+ { vault: depositVault, owner },
+ parseUnits('150000'),
+ );
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 102_000,
+ );
+ });
+
+ it('call for amount == minAmountToDepositTest+1, then deposit with amount 100', async () => {
+ const {
+ depositVault,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+
+ await mintToken(stableCoins.dai, owner, 103_101);
+ await approveBase18(owner, stableCoins.dai, depositVault, 103_101);
+
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setMinAmountToDepositTest({ depositVault, owner }, 100_000);
+ await setInstantLimitConfigTest(
+ { vault: depositVault, owner },
+ parseUnits('150000'),
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 103_001,
+ );
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ it('deposit 100 DAI, when price is 5$, 25 USDC when price is 5.1$, 14 USDT when price is 5.4$', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await mintToken(stableCoins.usdc, owner, 125);
+ await mintToken(stableCoins.usdt, owner, 114);
+
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await approveBase18(owner, stableCoins.usdc, depositVault, 125);
+ await approveBase18(owner, stableCoins.usdt, depositVault, 114);
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.usdt,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.04);
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await setRoundData({ mockedAggregator }, 1);
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.usdc,
+ 125,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.01);
+ await depositInstantTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.usdt,
+ 114,
+ );
+ });
+ });
+
+ describe('depositRequest() complex', () => {
+ it('should fail: when is paused', async () => {
+ const {
+ depositVault,
+ owner,
+ mTBILL,
+ stableCoins,
+ regularAccounts,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await pauseVault({ pauseManager, owner }, depositVault);
+ await mintToken(stableCoins.dai, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('is on pause, but admin can use everything', async () => {
+ const {
+ depositVault,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await pauseVault({ pauseManager, owner }, depositVault);
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('call for amount == minAmountToDepositTest', async () => {
+ const {
+ depositVault,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+
+ await mintToken(stableCoins.dai, owner, 105_000);
+ await approveBase18(owner, stableCoins.dai, depositVault, 105_000);
+
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setMinAmountToDepositTest({ depositVault, owner }, 100_000);
+ await setInstantLimitConfigTest(
+ { vault: depositVault, owner },
+ 150_000,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 102_000,
+ );
+ const requestId = 0;
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ requestId,
+ parseUnits('5'),
+ );
+ });
+
+ it('call for amount == minAmountToDepositTest+1, then deposit with amount 1', async () => {
+ const {
+ depositVault,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+
+ await mintToken(stableCoins.dai, owner, 105_101);
+ await approveBase18(owner, stableCoins.dai, depositVault, 105_101);
+
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setMinAmountToDepositTest({ depositVault, owner }, 100_000);
+ await setInstantLimitConfigTest(
+ { vault: depositVault, owner },
+ 150_000,
+ );
+
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 102_001,
+ );
+ let requestId = 0;
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ requestId,
+ parseUnits('5'),
+ );
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ requestId = 1;
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ requestId,
+ parseUnits('5'),
+ );
+ });
+
+ it('deposit 100 DAI, when price is 5$, 25 USDC when price is 5.1$, 14 USDT when price is 5.4$', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadDvFixture();
+
+ await mintToken(stableCoins.dai, owner, 100);
+ await mintToken(stableCoins.usdc, owner, 125);
+ await mintToken(stableCoins.usdt, owner, 114);
+
+ await approveBase18(owner, stableCoins.dai, depositVault, 100);
+ await approveBase18(owner, stableCoins.usdc, depositVault, 125);
+ await approveBase18(owner, stableCoins.usdt, depositVault, 114);
+
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.usdt,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setMinAmountTest({ vault: depositVault, owner }, 10);
+
+ await setRoundData({ mockedAggregator }, 1.04);
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ let requestId = 0;
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ requestId,
+ parseUnits('5'),
+ );
+
+ await setRoundData({ mockedAggregator }, 1);
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.usdc,
+ 125,
+ );
+ requestId = 1;
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ requestId,
+ parseUnits('5'),
+ );
+
+ await setRoundData({ mockedAggregator }, 1.01);
+ await depositRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.usdt,
+ 114,
+ );
+ requestId = 2;
+
+ await approveRequestTest(
+ { depositVault, owner, mTBILL, mTokenToUsdDataFeed },
+ requestId,
+ parseUnits('5'),
+ );
+ });
+
+ it('should fail: when 10 supply cap is left and try to mint request 100', async () => {
+ const {
+ owner,
+ depositVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ customRecipient,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ } = await loadDvFixture();
+ await mintToken(stableCoins.dai, regularAccounts[0], 190);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ depositVault,
+ 190,
+ );
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setMaxSupplyCapTest(
+ { tokenContract: mTBILL, owner },
+ parseUnits('100'),
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setMinAmountTest({ vault: depositVault, owner }, 0);
+
+ await depositInstantTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 90,
+ {
+ from: regularAccounts[0],
+ },
+ );
+
+ await depositRequestTest(
+ {
+ depositVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'SupplyCapExceeded',
+ },
+ },
+ );
+ });
+ });
+
+ describe('_convertUsdToToken', () => {
+ it('when amountUsd == 0', async () => {
+ const { depositVault, owner, stableCoins, dataFeed } =
+ await loadDvFixture();
+
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ expect(
+ (
+ await depositVault.convertTokenToUsdTest(
+ stableCoins.dai.address,
+ 0,
+ )
+ ).amountInUsd,
+ ).eq(0);
+ });
+
+ it('should fail: when unknown payment token', async () => {
+ const { depositVault } = await loadDvFixture();
+
+ await expect(
+ depositVault.convertTokenToUsdTest(constants.AddressZero, 0),
+ ).to.be.revertedWithoutReason();
+ });
+
+ it('should fail: when tokenRate == 0', async () => {
+ const { depositVault, owner, stableCoins, dataFeed } =
+ await loadDvFixture();
+
+ await depositVault.setOverrideGetTokenRate(true);
+ await depositVault.setGetTokenRateValue(0);
+
+ await addPaymentTokenTest(
+ { vault: depositVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await expect(
+ depositVault.convertTokenToUsdTest(stableCoins.dai.address, 1),
+ ).to.be.revertedWithCustomError(depositVault, 'InvalidTokenRate');
+ });
+ });
+
+ describe('_convertUsdToMToken', () => {
+ it('when amountUsd == 0', async () => {
+ const { depositVault } = await loadDvFixture();
+
+ expect(
+ (await depositVault.convertUsdToMTokenTest(0)).amountMToken,
+ ).eq(0);
+ });
+
+ it('should fail: when rate == 0', async () => {
+ const { depositVault } = await loadDvFixture();
+
+ await depositVault.setOverrideGetTokenRate(true);
+ await depositVault.setGetTokenRateValue(0);
+
+ await expect(
+ depositVault.convertUsdToMTokenTest(1),
+ ).to.be.revertedWithCustomError(depositVault, 'InvalidTokenRate');
+ });
+ });
+
+ describe('_calculateHoldbackPartRateFromAvg', () => {
+ it('returns 0 when depositedUsdAmount is 0', async () => {
+ const { depositVault } = await loadDvFixture();
+ const depositedUsdAmount = 0;
+ const amountTokenInstant = parseUnits('50');
+ const avgMTokenRate = parseUnits('1');
+ const tokenOutRate = parseUnits('1');
+ const expected = expectedDepositHoldbackPartRateFromAvg(
+ 0n,
+ BigInt(amountTokenInstant.toString()),
+ BigInt(tokenOutRate.toString()),
+ BigInt(avgMTokenRate.toString()),
+ );
+ expect(expected).eq(0n);
+ expect(
+ await depositVault.calculateHoldbackPartRateFromAvgTest(
+ depositedUsdAmount,
+ amountTokenInstant,
+ tokenOutRate,
+ avgMTokenRate,
+ ),
+ ).eq(expected.toString());
+ });
+
+ it('returns 0 when avg rate implies lower target value than instant leg', async () => {
+ const { depositVault } = await loadDvFixture();
+ const depositedUsdAmount = parseUnits('10');
+ const amountTokenInstant = parseUnits('100');
+ const avgMTokenRate = parseUnits('1');
+ const tokenOutRate = parseUnits('0.5');
+ const expected = expectedDepositHoldbackPartRateFromAvg(
+ BigInt(depositedUsdAmount.toString()),
+ BigInt(amountTokenInstant.toString()),
+ BigInt(tokenOutRate.toString()),
+ BigInt(avgMTokenRate.toString()),
+ );
+ expect(expected).eq(0n);
+ expect(
+ await depositVault.calculateHoldbackPartRateFromAvgTest(
+ depositedUsdAmount,
+ amountTokenInstant,
+ tokenOutRate,
+ avgMTokenRate,
+ ),
+ ).eq(expected.toString());
+ });
+
+ it('returns 0 when avgMTokenRate is 0', async () => {
+ const { depositVault } = await loadDvFixture();
+ const depositedUsdAmount = parseUnits('100');
+ const amountTokenInstant = parseUnits('0');
+ const one = parseUnits('1');
+ const expected = expectedDepositHoldbackPartRateFromAvg(
+ BigInt(depositedUsdAmount.toString()),
+ BigInt(amountTokenInstant.toString()),
+ BigInt(one.toString()),
+ 0n,
+ );
+ expect(
+ await depositVault.calculateHoldbackPartRateFromAvgTest(
+ depositedUsdAmount,
+ amountTokenInstant,
+ one,
+ 0,
+ ),
+ ).eq(expected.toString());
+ });
+
+ it('returns 0 when depositedInstantUsdAmount is 0', async () => {
+ const { depositVault } = await loadDvFixture();
+ const depositedUsdAmount = parseUnits('100');
+ const tokenOutRate = parseUnits('1');
+ const avgMTokenRate = parseUnits('1.25');
+ const expected = expectedDepositHoldbackPartRateFromAvg(
+ BigInt(depositedUsdAmount.toString()),
+ 0n,
+ BigInt(tokenOutRate.toString()),
+ BigInt(avgMTokenRate.toString()),
+ );
+ expect(expected).eq(0n);
+ expect(
+ await depositVault.calculateHoldbackPartRateFromAvgTest(
+ depositedUsdAmount,
+ 0,
+ tokenOutRate,
+ avgMTokenRate,
+ ),
+ ).eq(expected.toString());
+ });
+
+ it('applies integer rounding on the final rate', async () => {
+ const { depositVault } = await loadDvFixture();
+ const depositedUsdAmount = 3n;
+ const amountTokenInstant = 1n;
+ const tokenOutRate = 3n;
+ const avgMTokenRate = 2n;
+ const expected = expectedDepositHoldbackPartRateFromAvg(
+ depositedUsdAmount,
+ amountTokenInstant,
+ tokenOutRate,
+ avgMTokenRate,
+ );
+ expect(
+ await depositVault.calculateHoldbackPartRateFromAvgTest(
+ depositedUsdAmount,
+ amountTokenInstant,
+ tokenOutRate,
+ avgMTokenRate,
+ ),
+ ).eq(expected.toString());
+ });
+
+ it('succeeds with depositedUsdAmount == 0 when branch returns 0 before division', async () => {
+ const { depositVault } = await loadDvFixture();
+ const amountTokenInstant = parseUnits('100');
+ const tokenOutRate = parseUnits('1');
+ const avgMTokenRate = parseUnits('1');
+ const expected = expectedDepositHoldbackPartRateFromAvg(
+ 0n,
+ BigInt(amountTokenInstant.toString()),
+ BigInt(tokenOutRate.toString()),
+ BigInt(avgMTokenRate.toString()),
+ );
+ expect(expected).eq(0n);
+ expect(
+ await depositVault.calculateHoldbackPartRateFromAvgTest(
+ 0,
+ amountTokenInstant,
+ tokenOutRate,
+ avgMTokenRate,
+ ),
+ ).eq('0');
+ });
+
+ it('returns 0 when depositedUsdAmount == 0 and holdback part is positive', async () => {
+ const { depositVault } = await loadDvFixture();
+ const amountTokenInstant = parseUnits('100');
+ const avgMTokenRate = parseUnits('1');
+ const tokenOutRate = parseUnits('2');
+ const expected = expectedDepositHoldbackPartRateFromAvg(
+ 0n,
+ BigInt(amountTokenInstant.toString()),
+ BigInt(tokenOutRate.toString()),
+ BigInt(avgMTokenRate.toString()),
+ );
+ expect(expected).eq(0n);
+ expect(
+ await depositVault.calculateHoldbackPartRateFromAvgTest(
+ 0,
+ amountTokenInstant,
+ tokenOutRate,
+ avgMTokenRate,
+ ),
+ ).eq(expected.toString());
+ });
+
+ it('matches reference for mixed instant and holdback with realistic WAD rates', async () => {
+ const { depositVault } = await loadDvFixture();
+ const depositedUsdAmount = parseUnits('70');
+ const amountTokenInstant = parseUnits('30');
+ const avgMTokenRate = parseUnits('1');
+ const tokenOutRate = parseUnits('1');
+ const expected = expectedDepositHoldbackPartRateFromAvg(
+ BigInt(depositedUsdAmount.toString()),
+ BigInt(amountTokenInstant.toString()),
+ BigInt(tokenOutRate.toString()),
+ BigInt(avgMTokenRate.toString()),
+ );
+ expect(
+ await depositVault.calculateHoldbackPartRateFromAvgTest(
+ depositedUsdAmount,
+ amountTokenInstant,
+ tokenOutRate,
+ avgMTokenRate,
+ ),
+ ).eq(expected.toString());
+ });
+
+ it('handles large values without overflow when inputs are bounded', async () => {
+ const { depositVault } = await loadDvFixture();
+ const depositedUsdAmount = 10n ** 30n * 6n;
+ const amountTokenInstant = 10n ** 30n * 4n;
+ const avgMTokenRate = 10n ** 18n * 2n;
+ const tokenOutRate = 10n ** 18n * 5n;
+ const expected = expectedDepositHoldbackPartRateFromAvg(
+ depositedUsdAmount,
+ amountTokenInstant,
+ tokenOutRate,
+ avgMTokenRate,
+ );
+ expect(
+ await depositVault.calculateHoldbackPartRateFromAvgTest(
+ depositedUsdAmount,
+ amountTokenInstant,
+ tokenOutRate,
+ avgMTokenRate,
+ ),
+ ).eq(expected.toString());
+ });
+ });
+ });
+
+ otherTests(dvFixture);
+ });
+};
diff --git a/test/unit/suits/manageable-vault.suits.ts b/test/unit/suits/manageable-vault.suits.ts
new file mode 100644
index 00000000..f02f1e28
--- /dev/null
+++ b/test/unit/suits/manageable-vault.suits.ts
@@ -0,0 +1,2920 @@
+import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
+import { increase } from '@nomicfoundation/hardhat-network-helpers/dist/src/helpers/time';
+import { days } from '@nomicfoundation/hardhat-network-helpers/dist/src/helpers/time/duration';
+import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
+import { expect } from 'chai';
+import { constants } from 'ethers';
+import { parseUnits } from 'ethers/lib/utils';
+import { ethers } from 'hardhat';
+
+import { encodeFnSelector } from '../../../helpers/utils';
+import {
+ ManageableVaultTester,
+ ManageableVaultTester__factory,
+} from '../../../typechain-types';
+import {
+ acErrors,
+ setPermissionRoleTester,
+ setRoleTimelocksTester,
+ setupGrantOperatorRole,
+ setupPermissionRole,
+} from '../../common/ac.helpers';
+import {
+ mintToken,
+ pauseVaultFn,
+ approveBase18,
+ InitializeParamCase,
+} from '../../common/common.helpers';
+import {
+ DefaultFixture,
+ getInitializerParamsMv,
+ InitializerParamsMv,
+} from '../../common/fixtures';
+import { greenListEnable } from '../../common/greenlist.helpers';
+import {
+ addPaymentTokenTest,
+ removePaymentTokenTest,
+ setVariabilityToleranceTest,
+ withdrawTest,
+ changeTokenAllowanceTest,
+ changeTokenFeeTest,
+ setWaivedFeeAccountTest,
+ setInstantFeeTest,
+ setMinAmountTest,
+ setMinMaxInstantFeeTest,
+ setSequentialRequestProcessingTest,
+ setTokensReceiverTest,
+ setInstantLimitConfigTest,
+ removeInstantLimitConfigTest,
+ setMaxInstantShareTest,
+ setMaxApproveRequestIdTest,
+} from '../../common/manageable-vault.helpers';
+import {
+ bulkScheduleTimelockOperationTester,
+ executeTimelockOperationTester,
+} from '../../common/timelock-manager.helpers';
+
+const setTokensReceiverSelector = encodeFnSelector(
+ 'setTokensReceiver(address)',
+);
+const setInstantLimitConfigSelector = encodeFnSelector(
+ 'setInstantLimitConfig(uint256,uint256)',
+);
+const removeInstantLimitConfigSelector = encodeFnSelector(
+ 'removeInstantLimitConfig(uint256)',
+);
+const setMaxApproveRequestIdSelector = encodeFnSelector(
+ 'setMaxApproveRequestId(uint256)',
+);
+const setMaxInstantShareSelector = encodeFnSelector(
+ 'setMaxInstantShare(uint256)',
+);
+const setSequentialRequestProcessingSelector = encodeFnSelector(
+ 'setSequentialRequestProcessing(bool)',
+);
+let pauseManager: DefaultFixture['pauseManager'];
+let owner: DefaultFixture['owner'];
+
+export const baseInitParamsMv = (
+ fixture: DefaultFixture,
+): InitializerParamsMv => ({
+ accessControl: fixture.accessControl,
+ mockedSanctionsList: fixture.mockedSanctionsList,
+ mTBILL: fixture.mTBILL,
+ mTokenToUsdDataFeed: fixture.mTokenToUsdDataFeed,
+ tokensReceiver: fixture.tokensReceiver,
+});
+
+export const mvInitializeParamCases: InitializeParamCase[] =
+ [
+ {
+ title: 'accessControl is zero address',
+ params: { accessControl: constants.AddressZero },
+ revertCustomError: {
+ customErrorName: 'InvalidAddress',
+ args: [constants.AddressZero],
+ },
+ },
+ {
+ title: 'mTBILL is zero address',
+ params: { mTBILL: constants.AddressZero },
+ revertCustomError: {
+ customErrorName: 'InvalidAddress',
+ args: [constants.AddressZero],
+ },
+ },
+ {
+ title: 'mTokenToUsdDataFeed is zero address',
+ params: { mTokenToUsdDataFeed: constants.AddressZero },
+ revertCustomError: {
+ customErrorName: 'InvalidAddress',
+ args: [constants.AddressZero],
+ },
+ },
+ {
+ title: 'tokensReceiver is zero address',
+ params: { tokensReceiver: constants.AddressZero },
+ revertCustomError: {
+ customErrorName: 'InvalidAddress',
+ args: [constants.AddressZero],
+ },
+ },
+ {
+ title: 'variationTolerance is zero',
+ params: { variationTolerance: 0 },
+ revertCustomError: { customErrorName: 'InvalidFee', args: [0] },
+ },
+ {
+ title: 'variationTolerance is greater than 100%',
+ params: { variationTolerance: 10001 },
+ revertCustomError: { customErrorName: 'InvalidFee', args: [10001] },
+ },
+ {
+ title: 'instantFee is greater than 100%',
+ params: { instantFee: 10001 },
+ revertCustomError: { customErrorName: 'InvalidFee', args: [10001] },
+ },
+ {
+ title: 'maxInstantShare is greater than 100%',
+ params: { maxInstantShare: 10001 },
+ revertCustomError: { customErrorName: 'InvalidFee', args: [10001] },
+ },
+ {
+ title: 'minInstantFee is greater than 100%',
+ params: { minInstantFee: 10001 },
+ revertCustomError: { customErrorName: 'InvalidFee', args: [10001] },
+ },
+ {
+ title: 'maxInstantFee is greater than 100%',
+ params: { maxInstantFee: 10001 },
+ revertCustomError: { customErrorName: 'InvalidFee', args: [10001] },
+ },
+ {
+ title: 'minInstantFee is greater than maxInstantFee',
+ params: { minInstantFee: 500, maxInstantFee: 100 },
+ revertCustomError: {
+ customErrorName: 'InvalidMinMaxInstantFee',
+ args: [500, 100],
+ },
+ },
+ ];
+
+export const manageableVaultSuits = (
+ mvFixture: () => Promise,
+ mvConfig: {
+ createNew: (owner: SignerWithAddress) => Promise;
+ key:
+ | 'depositVault'
+ | 'depositVaultWithUSTB'
+ | 'depositVaultWithMToken'
+ | 'depositVaultWithAave'
+ | 'depositVaultWithMorpho'
+ | 'redemptionVault'
+ | 'redemptionVaultWithAave'
+ | 'redemptionVaultWithMToken'
+ | 'redemptionVaultWithUSTB'
+ | 'redemptionVaultWithMorpho';
+ },
+ deploymentAdditionalChecks: (
+ fixtureRes: DefaultFixture,
+ ) => Promise = async () => {},
+ otherTests: (fixture: () => Promise) => void = async () => {},
+) => {
+ const loadMvFixture = async () => {
+ const fixture = await loadFixture(mvFixture);
+ ({ pauseManager, owner } = fixture);
+
+ const { createNew, key } = mvConfig;
+ return {
+ ...fixture,
+ originalVault: fixture.manageableVault,
+ manageableVault: ManageableVaultTester__factory.connect(
+ fixture[key].address,
+ fixture.owner,
+ ),
+ createNew: async () => {
+ const mv = await createNew(fixture.owner);
+ return ManageableVaultTester__factory.connect(
+ mv.address,
+ fixture.owner,
+ );
+ },
+ };
+ };
+
+ describe('ManageableVault', function () {
+ it('deployment', async () => {
+ const fixture = await loadMvFixture();
+ const { manageableVault, mTBILL, tokensReceiver, mTokenToUsdDataFeed } =
+ fixture;
+
+ expect(await manageableVault.mToken()).eq(mTBILL.address);
+
+ expect(await manageableVault.ONE_HUNDRED_PERCENT()).eq('10000');
+
+ expect(await manageableVault.tokensReceiver()).eq(tokensReceiver.address);
+
+ expect(await manageableVault.minAmount()).eq(1000);
+
+ expect(await manageableVault.instantFee()).eq('100');
+
+ expect(await manageableVault.mTokenDataFeed()).eq(
+ mTokenToUsdDataFeed.address,
+ );
+ expect(await manageableVault.variationTolerance()).eq(1);
+
+ expect(await manageableVault.minInstantFee()).eq(0);
+ expect(await manageableVault.maxInstantFee()).eq(10000);
+ expect((await manageableVault.getInstantLimitStatuses()).length).eq(0);
+
+ await deploymentAdditionalChecks({
+ ...fixture,
+ manageableVault: fixture.originalVault as ManageableVaultTester,
+ });
+ });
+
+ describe('initialization', () => {
+ it('should fail: cal; initialize() when already initialized', async () => {
+ const { manageableVault } = await loadMvFixture();
+
+ await expect(
+ manageableVault.initializeExternal(
+ ...getInitializerParamsMv({
+ accessControl: constants.AddressZero,
+ mockedSanctionsList: constants.AddressZero,
+ mTBILL: constants.AddressZero,
+ mTokenToUsdDataFeed: constants.AddressZero,
+ tokensReceiver: constants.AddressZero,
+ }),
+ ),
+ ).revertedWith('Initializable: contract is already initialized');
+ });
+
+ it('should fail: call with initializing == false', async () => {
+ const { owner } = await loadMvFixture();
+
+ const vault = await new ManageableVaultTester__factory(owner).deploy();
+
+ await expect(
+ vault.initializeWithoutInitializer(
+ ...getInitializerParamsMv({
+ accessControl: constants.AddressZero,
+ mockedSanctionsList: constants.AddressZero,
+ mTBILL: constants.AddressZero,
+ mTokenToUsdDataFeed: constants.AddressZero,
+ tokensReceiver: constants.AddressZero,
+ }),
+ ),
+ ).revertedWith('Initializable: contract is not initializing');
+ });
+ });
+
+ describe('common', () => {
+ describe('setInstantLimitConfig()', () => {
+ const instantLimitWindow = days(2);
+
+ it('should fail: call from address without VAULT_ADMIN_ROLE role', async () => {
+ const { owner, manageableVault, regularAccounts } =
+ await loadMvFixture();
+
+ await setInstantLimitConfigTest(
+ { vault: manageableVault, owner },
+ { window: instantLimitWindow, limit: parseUnits('1000') },
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('call from address with VAULT_ADMIN_ROLE role', async () => {
+ const { owner, manageableVault } = await loadMvFixture();
+ await setInstantLimitConfigTest(
+ { vault: manageableVault, owner },
+ { window: instantLimitWindow, limit: parseUnits('1000') },
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { owner, manageableVault } = await loadMvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ manageableVault,
+ setInstantLimitConfigSelector,
+ );
+
+ await setInstantLimitConfigTest(
+ { vault: manageableVault, owner },
+ { window: instantLimitWindow, limit: parseUnits('1000') },
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const { accessControl, owner, manageableVault, regularAccounts } =
+ await loadMvFixture();
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'setInstantLimitConfig(uint256,uint256)',
+ regularAccounts[0].address,
+ );
+
+ expect(
+ await accessControl.hasRole(vaultRole, regularAccounts[0].address),
+ ).eq(false);
+
+ await setInstantLimitConfigTest(
+ { vault: manageableVault, owner },
+ { window: instantLimitWindow, limit: parseUnits('1000') },
+ { from: regularAccounts[0] },
+ );
+ });
+
+ it('succeeds with scoped permission and vault admin role', async () => {
+ const {
+ accessControl,
+ owner,
+ manageableVault,
+ regularAccounts,
+ roles,
+ } = await loadMvFixture();
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'setInstantLimitConfig(uint256,uint256)',
+ regularAccounts[0].address,
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ roles.tokenRoles.mTBILL.depositVaultAdmin,
+ regularAccounts[0].address,
+ );
+
+ await setInstantLimitConfigTest(
+ { vault: manageableVault, owner },
+ { window: instantLimitWindow, limit: parseUnits('1000') },
+ { from: regularAccounts[0] },
+ );
+ });
+
+ it('when updating existing window limit config', async () => {
+ const { manageableVault, owner } = await loadMvFixture();
+
+ await setInstantLimitConfigTest(
+ { vault: manageableVault, owner },
+ { window: days(1), limit: parseUnits('500') },
+ );
+ await setInstantLimitConfigTest(
+ { vault: manageableVault, owner },
+ { window: days(1), limit: parseUnits('1500') },
+ );
+ });
+ });
+
+ describe('removeInstantLimitConfig()', () => {
+ const removeWindow = days(3);
+
+ it('should fail: call from address without VAULT_ADMIN_ROLE role', async () => {
+ const { owner, manageableVault, regularAccounts } =
+ await loadMvFixture();
+
+ await removeInstantLimitConfigTest(
+ { vault: manageableVault, owner },
+ removeWindow,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('call from address with VAULT_ADMIN_ROLE role', async () => {
+ const { owner, manageableVault } = await loadMvFixture();
+
+ await setInstantLimitConfigTest(
+ { vault: manageableVault, owner },
+ { window: removeWindow, limit: parseUnits('1000') },
+ );
+ await removeInstantLimitConfigTest(
+ { vault: manageableVault, owner },
+ removeWindow,
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { owner, manageableVault } = await loadMvFixture();
+
+ await setInstantLimitConfigTest(
+ { vault: manageableVault, owner },
+ { window: removeWindow, limit: parseUnits('1000') },
+ );
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ manageableVault,
+ removeInstantLimitConfigSelector,
+ );
+
+ await removeInstantLimitConfigTest(
+ { vault: manageableVault, owner },
+ removeWindow,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const { accessControl, owner, manageableVault, regularAccounts } =
+ await loadMvFixture();
+
+ await setInstantLimitConfigTest(
+ { vault: manageableVault, owner },
+ { window: removeWindow, limit: parseUnits('1000') },
+ );
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'removeInstantLimitConfig(uint256)',
+ regularAccounts[0].address,
+ );
+
+ expect(
+ await accessControl.hasRole(vaultRole, regularAccounts[0].address),
+ ).eq(false);
+
+ await removeInstantLimitConfigTest(
+ { vault: manageableVault, owner },
+ removeWindow,
+ { from: regularAccounts[0] },
+ );
+ });
+
+ it('succeeds with scoped permission and vault admin role', async () => {
+ const {
+ accessControl,
+ owner,
+ manageableVault,
+ regularAccounts,
+ roles,
+ } = await loadMvFixture();
+
+ await setInstantLimitConfigTest(
+ { vault: manageableVault, owner },
+ { window: removeWindow, limit: parseUnits('1000') },
+ );
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'removeInstantLimitConfig(uint256)',
+ regularAccounts[0].address,
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ roles.tokenRoles.mTBILL.depositVaultAdmin,
+ regularAccounts[0].address,
+ );
+
+ await removeInstantLimitConfigTest(
+ { vault: manageableVault, owner },
+ removeWindow,
+ { from: regularAccounts[0] },
+ );
+ });
+
+ it('should fail: when window does not exist', async () => {
+ const { manageableVault, owner } = await loadMvFixture();
+ await removeInstantLimitConfigTest(
+ { vault: manageableVault, owner },
+ days(99),
+ {
+ revertCustomError: {
+ customErrorName: 'UnknownWindowLimit',
+ },
+ },
+ );
+ });
+ });
+
+ describe('setMaxApproveRequestId()', () => {
+ it('should fail: call from address without VAULT_ADMIN_ROLE role', async () => {
+ const { owner, manageableVault, regularAccounts } =
+ await loadMvFixture();
+
+ await setMaxApproveRequestIdTest(
+ { vault: manageableVault, owner },
+ 250,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('call from address with VAULT_ADMIN_ROLE role', async () => {
+ const { owner, manageableVault } = await loadMvFixture();
+ await setMaxApproveRequestIdTest(
+ { vault: manageableVault, owner },
+ 250,
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { owner, manageableVault } = await loadMvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ manageableVault,
+ setMaxApproveRequestIdSelector,
+ );
+
+ await setMaxApproveRequestIdTest(
+ { vault: manageableVault, owner },
+ 250,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const { accessControl, owner, manageableVault, regularAccounts } =
+ await loadMvFixture();
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'setMaxApproveRequestId(uint256)',
+ regularAccounts[0].address,
+ );
+
+ expect(
+ await accessControl.hasRole(vaultRole, regularAccounts[0].address),
+ ).eq(false);
+
+ await setMaxApproveRequestIdTest(
+ { vault: manageableVault, owner },
+ 250,
+ { from: regularAccounts[0] },
+ );
+ });
+
+ it('succeeds with scoped permission and vault admin role', async () => {
+ const {
+ accessControl,
+ owner,
+ manageableVault,
+ regularAccounts,
+ roles,
+ } = await loadMvFixture();
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'setMaxApproveRequestId(uint256)',
+ regularAccounts[0].address,
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ roles.tokenRoles.mTBILL.depositVaultAdmin,
+ regularAccounts[0].address,
+ );
+
+ await setMaxApproveRequestIdTest(
+ { vault: manageableVault, owner },
+ 250,
+ { from: regularAccounts[0] },
+ );
+ });
+ });
+
+ describe('setMaxInstantShare()', () => {
+ it('should fail: call from address without VAULT_ADMIN_ROLE role', async () => {
+ const { owner, manageableVault, regularAccounts } =
+ await loadMvFixture();
+
+ await setMaxInstantShareTest(
+ { vault: manageableVault, owner },
+ 5000,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('call from address with VAULT_ADMIN_ROLE role', async () => {
+ const { owner, manageableVault } = await loadMvFixture();
+ await setMaxInstantShareTest({ vault: manageableVault, owner }, 5000);
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { owner, manageableVault } = await loadMvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ manageableVault,
+ setMaxInstantShareSelector,
+ );
+
+ await setMaxInstantShareTest(
+ { vault: manageableVault, owner },
+ 5000,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const { accessControl, owner, manageableVault, regularAccounts } =
+ await loadMvFixture();
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'setMaxInstantShare(uint256)',
+ regularAccounts[0].address,
+ );
+
+ expect(
+ await accessControl.hasRole(vaultRole, regularAccounts[0].address),
+ ).eq(false);
+
+ await setMaxInstantShareTest(
+ { vault: manageableVault, owner },
+ 5000,
+ { from: regularAccounts[0] },
+ );
+ });
+
+ it('succeeds with scoped permission and vault admin role', async () => {
+ const {
+ accessControl,
+ owner,
+ manageableVault,
+ regularAccounts,
+ roles,
+ } = await loadMvFixture();
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'setMaxInstantShare(uint256)',
+ regularAccounts[0].address,
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ roles.tokenRoles.mTBILL.depositVaultAdmin,
+ regularAccounts[0].address,
+ );
+
+ await setMaxInstantShareTest(
+ { vault: manageableVault, owner },
+ 5000,
+ { from: regularAccounts[0] },
+ );
+ });
+
+ it('should fail: if new value greater than 100%', async () => {
+ const { manageableVault, owner } = await loadMvFixture();
+ await setMaxInstantShareTest(
+ { vault: manageableVault, owner },
+ 10001,
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidFee',
+ },
+ },
+ );
+ });
+ });
+
+ describe('setSequentialRequestProcessing()', () => {
+ it('should fail: call from address without VAULT_ADMIN_ROLE role', async () => {
+ const { owner, manageableVault, regularAccounts } =
+ await loadMvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: manageableVault, owner },
+ true,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('call from address with VAULT_ADMIN_ROLE role', async () => {
+ const { owner, manageableVault } = await loadMvFixture();
+ await setSequentialRequestProcessingTest(
+ { vault: manageableVault, owner },
+ true,
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { owner, manageableVault } = await loadMvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ manageableVault,
+ setSequentialRequestProcessingSelector,
+ );
+
+ await setSequentialRequestProcessingTest(
+ { vault: manageableVault, owner },
+ true,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const { accessControl, owner, manageableVault, regularAccounts } =
+ await loadMvFixture();
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'setSequentialRequestProcessing(bool)',
+ regularAccounts[0].address,
+ );
+
+ expect(
+ await accessControl.hasRole(vaultRole, regularAccounts[0].address),
+ ).eq(false);
+
+ await setSequentialRequestProcessingTest(
+ { vault: manageableVault, owner },
+ true,
+ { from: regularAccounts[0] },
+ );
+ });
+
+ it('succeeds with scoped permission and vault admin role', async () => {
+ const {
+ accessControl,
+ owner,
+ manageableVault,
+ regularAccounts,
+ roles,
+ } = await loadMvFixture();
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'setSequentialRequestProcessing(bool)',
+ regularAccounts[0].address,
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ roles.tokenRoles.mTBILL.depositVaultAdmin,
+ regularAccounts[0].address,
+ );
+
+ await setSequentialRequestProcessingTest(
+ { vault: manageableVault, owner },
+ true,
+ { from: regularAccounts[0] },
+ );
+ });
+
+ it('should fail: if value is already set', async () => {
+ const { manageableVault, owner } = await loadMvFixture();
+ await setSequentialRequestProcessingTest(
+ { vault: manageableVault, owner },
+ true,
+ );
+ await setSequentialRequestProcessingTest(
+ { vault: manageableVault, owner },
+ true,
+ {
+ revertCustomError: {
+ customErrorName: 'SameBoolValue',
+ args: [true],
+ },
+ },
+ );
+ });
+ });
+
+ describe('setMinAmount()', () => {
+ it('should fail: call from address without VAULT_ADMIN_ROLE role', async () => {
+ const { owner, manageableVault, regularAccounts } =
+ await loadMvFixture();
+
+ await setMinAmountTest({ vault: manageableVault, owner }, 1.1, {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ });
+ });
+
+ it('call from address with VAULT_ADMIN_ROLE role', async () => {
+ const { owner, manageableVault } = await loadMvFixture();
+ await setMinAmountTest({ vault: manageableVault, owner }, 1.1);
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { owner, manageableVault } = await loadMvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ manageableVault,
+ encodeFnSelector('setMinAmount(uint256)'),
+ );
+
+ await setMinAmountTest({ vault: manageableVault, owner }, 1.1, {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ });
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const { accessControl, owner, manageableVault, regularAccounts } =
+ await loadMvFixture();
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'setMinAmount(uint256)',
+ regularAccounts[0].address,
+ );
+
+ expect(
+ await accessControl.hasRole(vaultRole, regularAccounts[0].address),
+ ).eq(false);
+
+ await setMinAmountTest({ vault: manageableVault, owner }, 200, {
+ from: regularAccounts[0],
+ });
+ });
+
+ it('succeeds with scoped permission and vault admin role', async () => {
+ const {
+ accessControl,
+ owner,
+ manageableVault,
+ regularAccounts,
+ roles,
+ } = await loadMvFixture();
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'setMinAmount(uint256)',
+ regularAccounts[0].address,
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ roles.tokenRoles.mTBILL.depositVaultAdmin,
+ regularAccounts[0].address,
+ );
+
+ await setMinAmountTest({ vault: manageableVault, owner }, 200, {
+ from: regularAccounts[0],
+ });
+ });
+ });
+
+ describe('setTokensReceiver()', () => {
+ it('should fail: call from address without VAULT_ADMIN_ROLE role', async () => {
+ const { owner, manageableVault, regularAccounts, tokensReceiver } =
+ await loadMvFixture();
+
+ await setTokensReceiverTest(
+ { vault: manageableVault, owner },
+ tokensReceiver.address,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('should fail: if receiver is zero address', async () => {
+ const { owner, manageableVault } = await loadMvFixture();
+
+ await setTokensReceiverTest(
+ { vault: manageableVault, owner },
+ constants.AddressZero,
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidAddress',
+ args: [constants.AddressZero],
+ },
+ },
+ );
+ });
+
+ it('should fail: if receiver is vault address', async () => {
+ const { owner, manageableVault } = await loadMvFixture();
+
+ await setTokensReceiverTest(
+ { vault: manageableVault, owner },
+ manageableVault.address,
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidAddress',
+ args: [manageableVault.address],
+ },
+ },
+ );
+ });
+
+ it('call from address with VAULT_ADMIN_ROLE role', async () => {
+ const { owner, manageableVault, regularAccounts } =
+ await loadMvFixture();
+
+ await setTokensReceiverTest(
+ { vault: manageableVault, owner },
+ regularAccounts[1].address,
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { owner, manageableVault, regularAccounts } =
+ await loadMvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ manageableVault,
+ setTokensReceiverSelector,
+ );
+
+ await setTokensReceiverTest(
+ { vault: manageableVault, owner },
+ regularAccounts[1].address,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const { accessControl, owner, manageableVault, regularAccounts } =
+ await loadMvFixture();
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'setTokensReceiver(address)',
+ regularAccounts[0].address,
+ );
+
+ expect(
+ await accessControl.hasRole(vaultRole, regularAccounts[0].address),
+ ).eq(false);
+
+ await setTokensReceiverTest(
+ { vault: manageableVault, owner },
+ regularAccounts[1].address,
+ { from: regularAccounts[0] },
+ );
+ });
+
+ it('succeeds with scoped permission and vault admin role', async () => {
+ const {
+ accessControl,
+ owner,
+ manageableVault,
+ regularAccounts,
+ roles,
+ } = await loadMvFixture();
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'setTokensReceiver(address)',
+ regularAccounts[0].address,
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ roles.tokenRoles.mTBILL.depositVaultAdmin,
+ regularAccounts[0].address,
+ );
+
+ await setTokensReceiverTest(
+ { vault: manageableVault, owner },
+ regularAccounts[1].address,
+ { from: regularAccounts[0] },
+ );
+ });
+
+ it('when called through timelock with contract admin role', async () => {
+ const {
+ accessControl,
+ manageableVault,
+ owner,
+ regularAccounts,
+ timelock,
+ timelockManager,
+ } = await loadMvFixture();
+
+ const proposer = regularAccounts[0];
+ const newReceiver = regularAccounts[1].address;
+ const vaultRole = await manageableVault.contractAdminRole();
+
+ await accessControl['grantRole(bytes32,address)'](
+ vaultRole,
+ proposer.address,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [vaultRole],
+ [3600],
+ );
+
+ const calldata = manageableVault.interface.encodeFunctionData(
+ 'setTokensReceiver',
+ [newReceiver],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [manageableVault.address],
+ [calldata],
+ {},
+ { from: proposer },
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ manageableVault.address,
+ calldata,
+ proposer.address,
+ { from: owner },
+ );
+
+ expect(await manageableVault.tokensReceiver()).eq(newReceiver);
+ });
+
+ it('when called through timelock with function admin role', async () => {
+ const {
+ accessControl,
+ manageableVault,
+ owner,
+ regularAccounts,
+ timelock,
+ timelockManager,
+ } = await loadMvFixture();
+
+ const proposer = regularAccounts[0];
+ const newReceiver = regularAccounts[1].address;
+ const vaultRole = await manageableVault.contractAdminRole();
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: vaultRole,
+ targetContract: manageableVault.address,
+ functionSelector: setTokensReceiverSelector,
+ grantOperator: owner,
+ });
+
+ await setupGrantOperatorRole({
+ accessControl,
+ owner,
+ masterRole: vaultRole,
+ targetContract: timelockManager.address,
+ functionSelector: setTokensReceiverSelector,
+ grantOperator: owner,
+ });
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ setTokensReceiverSelector,
+ [{ account: proposer.address, enabled: true }],
+ );
+
+ await setPermissionRoleTester(
+ { accessControl, owner },
+ vaultRole,
+ timelockManager.address,
+ setTokensReceiverSelector,
+ [{ account: proposer.address, enabled: true }],
+ );
+
+ expect(await accessControl.hasRole(vaultRole, proposer.address)).eq(
+ false,
+ );
+
+ const vaultPermissionKey = await accessControl.permissionRoleKey(
+ vaultRole,
+ manageableVault.address,
+ setTokensReceiverSelector,
+ );
+ const timelockPermissionKey = await accessControl.permissionRoleKey(
+ vaultRole,
+ timelockManager.address,
+ setTokensReceiverSelector,
+ );
+
+ await setRoleTimelocksTester(
+ { timelockManager, timelock, owner, accessControl },
+ [vaultPermissionKey, timelockPermissionKey],
+ [3600, 3600],
+ );
+
+ const calldata = manageableVault.interface.encodeFunctionData(
+ 'setTokensReceiver',
+ [newReceiver],
+ );
+
+ await bulkScheduleTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ [manageableVault.address],
+ [calldata],
+ {},
+ { from: proposer },
+ );
+
+ await increase(3600);
+
+ await executeTimelockOperationTester(
+ { timelockManager, timelock, owner, accessControl },
+ manageableVault.address,
+ calldata,
+ proposer.address,
+ { from: owner },
+ );
+
+ expect(await manageableVault.tokensReceiver()).eq(newReceiver);
+ });
+ });
+
+ describe('setGreenlistEnable()', () => {
+ it('should fail: call from address without VAULT_ADMIN_ROLE role', async () => {
+ const { owner, manageableVault, regularAccounts } =
+ await loadMvFixture();
+
+ await greenListEnable(
+ { greenlistable: manageableVault, owner },
+ true,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('call from address with VAULT_ADMIN_ROLE role', async () => {
+ const { owner, manageableVault } = await loadMvFixture();
+
+ await greenListEnable(
+ { greenlistable: manageableVault, owner },
+ true,
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { owner, manageableVault } = await loadMvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ manageableVault,
+ encodeFnSelector('setGreenlistEnable(bool)'),
+ );
+
+ await greenListEnable(
+ { greenlistable: manageableVault, owner },
+ true,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const { accessControl, owner, manageableVault, regularAccounts } =
+ await loadMvFixture();
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'setGreenlistEnable(bool)',
+ regularAccounts[0].address,
+ );
+
+ expect(
+ await accessControl.hasRole(vaultRole, regularAccounts[0].address),
+ ).eq(false);
+
+ await greenListEnable(
+ { greenlistable: manageableVault, owner },
+ true,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('succeeds with scoped permission and vault admin role', async () => {
+ const {
+ accessControl,
+ owner,
+ manageableVault,
+ regularAccounts,
+ roles,
+ } = await loadMvFixture();
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'setGreenlistEnable(bool)',
+ regularAccounts[0].address,
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ roles.tokenRoles.mTBILL.depositVaultAdmin,
+ regularAccounts[0].address,
+ );
+
+ await greenListEnable(
+ { greenlistable: manageableVault, owner },
+ true,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+ });
+
+ describe('addPaymentToken()', () => {
+ it('should fail: call from address without VAULT_ADMIN_ROLE role', async () => {
+ const { manageableVault, regularAccounts, owner } =
+ await loadMvFixture();
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ ethers.constants.AddressZero,
+ ethers.constants.AddressZero,
+ 0,
+ false,
+ constants.MaxUint256,
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: when token is already added', async () => {
+ const { manageableVault, stableCoins, owner, dataFeed } =
+ await loadMvFixture();
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ false,
+ );
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ false,
+ constants.MaxUint256,
+ {
+ revertCustomError: {
+ customErrorName: 'PaymentTokenAlreadyAdded',
+ },
+ },
+ );
+ });
+
+ it('should fail: when token dataFeed address zero', async () => {
+ const { manageableVault, stableCoins, owner } = await loadMvFixture();
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai,
+ constants.AddressZero,
+ 0,
+ false,
+ constants.MaxUint256,
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidAddress',
+ },
+ },
+ );
+ });
+
+ it('call from address with VAULT_ADMIN_ROLE role', async () => {
+ const { manageableVault, stableCoins, owner, dataFeed } =
+ await loadMvFixture();
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ false,
+ );
+ });
+
+ it('call when allowance is zero', async () => {
+ const { manageableVault, stableCoins, owner, dataFeed } =
+ await loadMvFixture();
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ false,
+ constants.Zero,
+ );
+ });
+
+ it('call when allowance is not uint256 max', async () => {
+ const { manageableVault, stableCoins, owner, dataFeed } =
+ await loadMvFixture();
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ false,
+ parseUnits('100'),
+ );
+ });
+
+ it('call from address with VAULT_ADMIN_ROLE role and add 3 options on a row', async () => {
+ const { manageableVault, stableCoins, owner, dataFeed } =
+ await loadMvFixture();
+
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.usdt,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { manageableVault, stableCoins, owner, dataFeed } =
+ await loadMvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ manageableVault,
+ encodeFnSelector(
+ 'addPaymentToken(address,address,uint256,uint256,bool)',
+ ),
+ );
+
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ constants.MaxUint256,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const {
+ accessControl,
+ owner,
+ manageableVault,
+ regularAccounts,
+ stableCoins,
+ dataFeed,
+ } = await loadMvFixture();
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'addPaymentToken(address,address,uint256,uint256,bool)',
+ regularAccounts[0].address,
+ );
+
+ expect(
+ await accessControl.hasRole(vaultRole, regularAccounts[0].address),
+ ).eq(false);
+
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ constants.MaxUint256,
+ { from: regularAccounts[0] },
+ );
+ });
+
+ it('succeeds with scoped permission and vault admin role', async () => {
+ const {
+ accessControl,
+ owner,
+ manageableVault,
+ regularAccounts,
+ roles,
+ stableCoins,
+ dataFeed,
+ } = await loadMvFixture();
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'addPaymentToken(address,address,uint256,uint256,bool)',
+ regularAccounts[0].address,
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ roles.tokenRoles.mTBILL.depositVaultAdmin,
+ regularAccounts[0].address,
+ );
+
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.usdt,
+ dataFeed.address,
+ 0,
+ true,
+ constants.MaxUint256,
+ { from: regularAccounts[0] },
+ );
+ });
+ });
+
+ describe('setWaivedFeeAccount()', () => {
+ describe('enabled=true', () => {
+ it('should fail: call from address without VAULT_ADMIN_ROLE role', async () => {
+ const { manageableVault, regularAccounts, owner } =
+ await loadMvFixture();
+ await setWaivedFeeAccountTest(
+ { vault: manageableVault, owner },
+ ethers.constants.AddressZero,
+ true,
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ from: regularAccounts[0],
+ },
+ );
+ });
+ it('should fail: if account fee already waived', async () => {
+ const { manageableVault, owner } = await loadMvFixture();
+ await setWaivedFeeAccountTest(
+ { vault: manageableVault, owner },
+ owner.address,
+ true,
+ );
+ await setWaivedFeeAccountTest(
+ { vault: manageableVault, owner },
+ owner.address,
+ true,
+ {
+ revertCustomError: {
+ customErrorName: 'SameBoolValue',
+ },
+ },
+ );
+ });
+
+ it('call from address with VAULT_ADMIN_ROLE role', async () => {
+ const { manageableVault, owner } = await loadMvFixture();
+ await setWaivedFeeAccountTest(
+ { vault: manageableVault, owner },
+ owner.address,
+ true,
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { manageableVault, owner } = await loadMvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ manageableVault,
+ encodeFnSelector('setWaivedFeeAccount(address,bool)'),
+ );
+
+ await setWaivedFeeAccountTest(
+ { vault: manageableVault, owner },
+ owner.address,
+ true,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const { accessControl, owner, manageableVault, regularAccounts } =
+ await loadMvFixture();
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'setWaivedFeeAccount(address,bool)',
+ regularAccounts[0].address,
+ );
+
+ expect(
+ await accessControl.hasRole(
+ vaultRole,
+ regularAccounts[0].address,
+ ),
+ ).eq(false);
+
+ await setWaivedFeeAccountTest(
+ { vault: manageableVault, owner },
+ regularAccounts[1].address,
+ true,
+ { from: regularAccounts[0] },
+ );
+ });
+
+ it('succeeds with scoped permission and vault admin role', async () => {
+ const {
+ accessControl,
+ owner,
+ manageableVault,
+ regularAccounts,
+ roles,
+ } = await loadMvFixture();
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'setWaivedFeeAccount(address,bool)',
+ regularAccounts[0].address,
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ roles.tokenRoles.mTBILL.depositVaultAdmin,
+ regularAccounts[0].address,
+ );
+
+ await setWaivedFeeAccountTest(
+ { vault: manageableVault, owner },
+ regularAccounts[2].address,
+ true,
+ { from: regularAccounts[0] },
+ );
+ });
+ });
+
+ describe('enabled=false', () => {
+ it('should fail: call from address without VAULT_ADMIN_ROLE role', async () => {
+ const { manageableVault, regularAccounts, owner } =
+ await loadMvFixture();
+ await setWaivedFeeAccountTest(
+ { vault: manageableVault, owner },
+ ethers.constants.AddressZero,
+ false,
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ from: regularAccounts[0],
+ },
+ );
+ });
+ it('should fail: if account not found in restriction', async () => {
+ const { manageableVault, owner } = await loadMvFixture();
+ await setWaivedFeeAccountTest(
+ { vault: manageableVault, owner },
+ owner.address,
+ false,
+ {
+ revertCustomError: {
+ customErrorName: 'SameBoolValue',
+ },
+ },
+ );
+ });
+
+ it('call from address with VAULT_ADMIN_ROLE role', async () => {
+ const { manageableVault, owner } = await loadMvFixture();
+ await setWaivedFeeAccountTest(
+ { vault: manageableVault, owner },
+ owner.address,
+ true,
+ );
+ await setWaivedFeeAccountTest(
+ { vault: manageableVault, owner },
+ owner.address,
+ false,
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { manageableVault, owner } = await loadMvFixture();
+
+ await setWaivedFeeAccountTest(
+ { vault: manageableVault, owner },
+ owner.address,
+ true,
+ );
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ manageableVault,
+ encodeFnSelector('setWaivedFeeAccount(address,bool)'),
+ );
+
+ await setWaivedFeeAccountTest(
+ { vault: manageableVault, owner },
+ owner.address,
+ false,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const { accessControl, owner, manageableVault, regularAccounts } =
+ await loadMvFixture();
+
+ await setWaivedFeeAccountTest(
+ { vault: manageableVault, owner },
+ regularAccounts[1].address,
+ true,
+ );
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'setWaivedFeeAccount(address,bool)',
+ regularAccounts[0].address,
+ );
+
+ expect(
+ await accessControl.hasRole(
+ vaultRole,
+ regularAccounts[0].address,
+ ),
+ ).eq(false);
+
+ await setWaivedFeeAccountTest(
+ { vault: manageableVault, owner },
+ regularAccounts[1].address,
+ false,
+ { from: regularAccounts[0] },
+ );
+ });
+
+ it('succeeds with scoped permission and vault admin role', async () => {
+ const {
+ accessControl,
+ owner,
+ manageableVault,
+ regularAccounts,
+ roles,
+ } = await loadMvFixture();
+
+ await setWaivedFeeAccountTest(
+ { vault: manageableVault, owner },
+ regularAccounts[2].address,
+ true,
+ );
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'setWaivedFeeAccount(address,bool)',
+ regularAccounts[0].address,
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ roles.tokenRoles.mTBILL.depositVaultAdmin,
+ regularAccounts[0].address,
+ );
+
+ await setWaivedFeeAccountTest(
+ { vault: manageableVault, owner },
+ regularAccounts[2].address,
+ false,
+ { from: regularAccounts[0] },
+ );
+ });
+ });
+ });
+
+ describe('setFee()', () => {
+ it('should fail: call from address without VAULT_ADMIN_ROLE role', async () => {
+ const { manageableVault, regularAccounts, owner } =
+ await loadMvFixture();
+ await setInstantFeeTest(
+ { vault: manageableVault, owner },
+ ethers.constants.Zero,
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: if new value greater then 100%', async () => {
+ const { manageableVault, owner } = await loadMvFixture();
+ await setInstantFeeTest({ vault: manageableVault, owner }, 10001, {
+ revertCustomError: {
+ customErrorName: 'InvalidFee',
+ },
+ });
+ });
+
+ it('call from address with VAULT_ADMIN_ROLE role', async () => {
+ const { manageableVault, owner } = await loadMvFixture();
+ await setInstantFeeTest({ vault: manageableVault, owner }, 100);
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { manageableVault, owner } = await loadMvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ manageableVault,
+ encodeFnSelector('setInstantFee(uint256)'),
+ );
+
+ await setInstantFeeTest({ vault: manageableVault, owner }, 100, {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ });
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const { accessControl, owner, manageableVault, regularAccounts } =
+ await loadMvFixture();
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'setInstantFee(uint256)',
+ regularAccounts[0].address,
+ );
+
+ expect(
+ await accessControl.hasRole(vaultRole, regularAccounts[0].address),
+ ).eq(false);
+
+ await setInstantFeeTest({ vault: manageableVault, owner }, 100, {
+ from: regularAccounts[0],
+ });
+ });
+
+ it('succeeds with scoped permission and vault admin role', async () => {
+ const {
+ accessControl,
+ owner,
+ manageableVault,
+ regularAccounts,
+ roles,
+ } = await loadMvFixture();
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'setInstantFee(uint256)',
+ regularAccounts[0].address,
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ roles.tokenRoles.mTBILL.depositVaultAdmin,
+ regularAccounts[0].address,
+ );
+
+ await setInstantFeeTest({ vault: manageableVault, owner }, 100, {
+ from: regularAccounts[0],
+ });
+ });
+ });
+
+ describe('setMinMaxInstantFee()', () => {
+ it('should fail: call from address without VAULT_ADMIN_ROLE role', async () => {
+ const { owner, manageableVault, regularAccounts } =
+ await loadMvFixture();
+ await setMinMaxInstantFeeTest(
+ { vault: manageableVault, owner },
+ 0,
+ 1000,
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: if min greater than max', async () => {
+ const { manageableVault, owner } = await loadMvFixture();
+ await setMinMaxInstantFeeTest(
+ { vault: manageableVault, owner },
+ 500,
+ 100,
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidMinMaxInstantFee',
+ },
+ },
+ );
+ });
+
+ it('should fail: if fee greater than 100%', async () => {
+ const { manageableVault, owner } = await loadMvFixture();
+ await setMinMaxInstantFeeTest(
+ { vault: manageableVault, owner },
+ 10001,
+ 10001,
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidFee',
+ },
+ },
+ );
+ });
+
+ it('call from address with VAULT_ADMIN_ROLE role', async () => {
+ const { manageableVault, owner } = await loadMvFixture();
+ await setMinMaxInstantFeeTest(
+ { vault: manageableVault, owner },
+ 10,
+ 5000,
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { manageableVault, owner } = await loadMvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ manageableVault,
+ encodeFnSelector('setMinMaxInstantFee(uint256,uint256)'),
+ );
+
+ await setMinMaxInstantFeeTest(
+ { vault: manageableVault, owner },
+ 0,
+ 1000,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const { accessControl, owner, manageableVault, regularAccounts } =
+ await loadMvFixture();
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'setMinMaxInstantFee(uint256,uint256)',
+ regularAccounts[0].address,
+ );
+
+ expect(
+ await accessControl.hasRole(vaultRole, regularAccounts[0].address),
+ ).eq(false);
+
+ await setMinMaxInstantFeeTest(
+ { vault: manageableVault, owner },
+ 10,
+ 5000,
+ { from: regularAccounts[0] },
+ );
+ });
+
+ it('succeeds with scoped permission and vault admin role', async () => {
+ const {
+ accessControl,
+ owner,
+ manageableVault,
+ regularAccounts,
+ roles,
+ } = await loadMvFixture();
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'setMinMaxInstantFee(uint256,uint256)',
+ regularAccounts[0].address,
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ roles.tokenRoles.mTBILL.depositVaultAdmin,
+ regularAccounts[0].address,
+ );
+
+ await setMinMaxInstantFeeTest(
+ { vault: manageableVault, owner },
+ 10,
+ 5000,
+ { from: regularAccounts[0] },
+ );
+ });
+ });
+
+ describe('setVariabilityTolerance()', () => {
+ it('should fail: call from address without VAULT_ADMIN_ROLE role', async () => {
+ const { manageableVault, regularAccounts, owner } =
+ await loadMvFixture();
+ await setVariabilityToleranceTest(
+ { vault: manageableVault, owner },
+ ethers.constants.Zero,
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ from: regularAccounts[0],
+ },
+ );
+ });
+ it('if new value zero', async () => {
+ const { manageableVault, owner } = await loadMvFixture();
+ await setVariabilityToleranceTest(
+ { vault: manageableVault, owner },
+ ethers.constants.Zero,
+ );
+ });
+
+ it('call from address with VAULT_ADMIN_ROLE role', async () => {
+ const { manageableVault, owner } = await loadMvFixture();
+ await setVariabilityToleranceTest(
+ { vault: manageableVault, owner },
+ 100,
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { manageableVault, owner } = await loadMvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ manageableVault,
+ encodeFnSelector('setVariationTolerance(uint256)'),
+ );
+
+ await setVariabilityToleranceTest(
+ { vault: manageableVault, owner },
+ 100,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const { accessControl, owner, manageableVault, regularAccounts } =
+ await loadMvFixture();
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'setVariationTolerance(uint256)',
+ regularAccounts[0].address,
+ );
+
+ expect(
+ await accessControl.hasRole(vaultRole, regularAccounts[0].address),
+ ).eq(false);
+
+ await setVariabilityToleranceTest(
+ { vault: manageableVault, owner },
+ 100,
+ { from: regularAccounts[0] },
+ );
+ });
+
+ it('succeeds with scoped permission and vault admin role', async () => {
+ const {
+ accessControl,
+ owner,
+ manageableVault,
+ regularAccounts,
+ roles,
+ } = await loadMvFixture();
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'setVariationTolerance(uint256)',
+ regularAccounts[0].address,
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ roles.tokenRoles.mTBILL.depositVaultAdmin,
+ regularAccounts[0].address,
+ );
+
+ await setVariabilityToleranceTest(
+ { vault: manageableVault, owner },
+ 100,
+ { from: regularAccounts[0] },
+ );
+ });
+ });
+
+ describe('removePaymentToken()', () => {
+ it('should fail: call from address without VAULT_ADMIN_ROLE role', async () => {
+ const { manageableVault, regularAccounts, owner } =
+ await loadMvFixture();
+ await removePaymentTokenTest(
+ { vault: manageableVault, owner },
+ ethers.constants.AddressZero,
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: when token is not exists', async () => {
+ const { owner, manageableVault, stableCoins } = await loadMvFixture();
+ await removePaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai.address,
+ {
+ revertCustomError: {
+ customErrorName: 'PaymentTokenNotExists',
+ },
+ },
+ );
+ });
+
+ it('call from address with VAULT_ADMIN_ROLE role', async () => {
+ const { manageableVault, stableCoins, owner, dataFeed } =
+ await loadMvFixture();
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await removePaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai.address,
+ );
+ });
+
+ it('call from address with VAULT_ADMIN_ROLE role and add 3 options on a row', async () => {
+ const { manageableVault, owner, stableCoins, dataFeed } =
+ await loadMvFixture();
+
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.usdt,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await removePaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai.address,
+ );
+ await removePaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.usdc.address,
+ );
+ await removePaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.usdt.address,
+ );
+
+ await removePaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.usdt.address,
+ {
+ revertCustomError: {
+ customErrorName: 'PaymentTokenNotExists',
+ },
+ },
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { manageableVault, owner, stableCoins, dataFeed } =
+ await loadMvFixture();
+
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ manageableVault,
+ encodeFnSelector('removePaymentToken(address)'),
+ );
+
+ await removePaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai.address,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const {
+ accessControl,
+ owner,
+ manageableVault,
+ regularAccounts,
+ stableCoins,
+ dataFeed,
+ } = await loadMvFixture();
+
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'removePaymentToken(address)',
+ regularAccounts[0].address,
+ );
+
+ expect(
+ await accessControl.hasRole(vaultRole, regularAccounts[0].address),
+ ).eq(false);
+
+ await removePaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.usdc.address,
+ { from: regularAccounts[0] },
+ );
+ });
+
+ it('succeeds with scoped permission and vault admin role', async () => {
+ const {
+ accessControl,
+ owner,
+ manageableVault,
+ regularAccounts,
+ roles,
+ stableCoins,
+ dataFeed,
+ } = await loadMvFixture();
+
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.usdt,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'removePaymentToken(address)',
+ regularAccounts[0].address,
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ roles.tokenRoles.mTBILL.depositVaultAdmin,
+ regularAccounts[0].address,
+ );
+
+ await removePaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.usdt.address,
+ { from: regularAccounts[0] },
+ );
+ });
+ });
+
+ describe('withdrawToken()', () => {
+ it('should fail: call from address without VAULT_ADMIN_ROLE role', async () => {
+ const { manageableVault, regularAccounts, owner } =
+ await loadMvFixture();
+ await withdrawTest(
+ { vault: manageableVault, owner },
+ ethers.constants.AddressZero,
+ 0,
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: when there is no token in vault', async () => {
+ const { owner, manageableVault, stableCoins } = await loadMvFixture();
+ await withdrawTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai,
+ 1,
+ { revertMessage: 'ERC20: transfer amount exceeds balance' },
+ );
+ });
+
+ it('call from address with VAULT_ADMIN_ROLE role', async () => {
+ const { manageableVault, stableCoins, owner } = await loadMvFixture();
+ await mintToken(stableCoins.dai, manageableVault, 1);
+ await withdrawTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai,
+ 1,
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { manageableVault, stableCoins, owner } = await loadMvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ manageableVault,
+ encodeFnSelector('withdrawToken(address,uint256)'),
+ );
+
+ await withdrawTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai,
+ 1,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const {
+ accessControl,
+ owner,
+ manageableVault,
+ regularAccounts,
+ stableCoins,
+ } = await loadMvFixture();
+
+ await mintToken(stableCoins.dai, manageableVault, 1);
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'withdrawToken(address,uint256)',
+ regularAccounts[0].address,
+ );
+
+ expect(
+ await accessControl.hasRole(vaultRole, regularAccounts[0].address),
+ ).eq(false);
+
+ await withdrawTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai,
+ 1,
+ { from: regularAccounts[0] },
+ );
+ });
+
+ it('succeeds with scoped permission and vault admin role', async () => {
+ const {
+ accessControl,
+ owner,
+ manageableVault,
+ regularAccounts,
+ roles,
+ stableCoins,
+ } = await loadMvFixture();
+
+ await mintToken(stableCoins.dai, manageableVault, 1);
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'withdrawToken(address,uint256)',
+ regularAccounts[0].address,
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ roles.tokenRoles.mTBILL.depositVaultAdmin,
+ regularAccounts[0].address,
+ );
+
+ await withdrawTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai,
+ 1,
+ { from: regularAccounts[0] },
+ );
+ });
+ });
+
+ describe('freeFromMinAmount()', async () => {
+ it('should fail: call from address without vault admin role', async () => {
+ const { manageableVault, regularAccounts } = await loadMvFixture();
+ await expect(
+ manageableVault
+ .connect(regularAccounts[0])
+ .freeFromMinAmount(regularAccounts[1].address, true),
+ ).to.be.revertedWithCustomError(
+ manageableVault,
+ 'NoFunctionPermission',
+ );
+ });
+ it('should not fail', async () => {
+ const { manageableVault, regularAccounts } = await loadMvFixture();
+ await expect(
+ manageableVault.freeFromMinAmount(regularAccounts[0].address, true),
+ ).to.not.reverted;
+
+ expect(
+ await manageableVault.isFreeFromMinAmount(
+ regularAccounts[0].address,
+ ),
+ ).to.eq(true);
+ });
+ it('should fail: already in list', async () => {
+ const { manageableVault, regularAccounts } = await loadMvFixture();
+ await expect(
+ manageableVault.freeFromMinAmount(regularAccounts[0].address, true),
+ ).to.not.reverted;
+
+ expect(
+ await manageableVault.isFreeFromMinAmount(
+ regularAccounts[0].address,
+ ),
+ ).to.eq(true);
+
+ await expect(
+ manageableVault.freeFromMinAmount(regularAccounts[0].address, true),
+ ).to.be.revertedWithCustomError(manageableVault, 'SameAddressValue');
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { manageableVault, regularAccounts } = await loadMvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ manageableVault,
+ encodeFnSelector('freeFromMinAmount(address,bool)'),
+ );
+
+ await expect(
+ manageableVault.freeFromMinAmount(regularAccounts[0].address, true),
+ ).to.be.revertedWithCustomError(manageableVault, 'Paused');
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const { accessControl, owner, manageableVault, regularAccounts } =
+ await loadMvFixture();
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'freeFromMinAmount(address,bool)',
+ regularAccounts[0].address,
+ );
+
+ expect(
+ await accessControl.hasRole(vaultRole, regularAccounts[0].address),
+ ).eq(false);
+
+ await expect(
+ manageableVault
+ .connect(regularAccounts[0])
+ .freeFromMinAmount(regularAccounts[2].address, true),
+ ).to.not.reverted;
+
+ expect(
+ await manageableVault.isFreeFromMinAmount(
+ regularAccounts[2].address,
+ ),
+ ).to.eq(true);
+ });
+
+ it('succeeds with scoped permission and vault admin role', async () => {
+ const {
+ accessControl,
+ owner,
+ manageableVault,
+ regularAccounts,
+ roles,
+ } = await loadMvFixture();
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'freeFromMinAmount(address,bool)',
+ regularAccounts[0].address,
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ roles.tokenRoles.mTBILL.depositVaultAdmin,
+ regularAccounts[0].address,
+ );
+
+ await expect(
+ manageableVault
+ .connect(regularAccounts[0])
+ .freeFromMinAmount(regularAccounts[3].address, true),
+ ).to.not.reverted;
+
+ expect(
+ await manageableVault.isFreeFromMinAmount(
+ regularAccounts[3].address,
+ ),
+ ).to.eq(true);
+ });
+ });
+
+ describe('changeTokenAllowance()', () => {
+ it('should fail: call from address without VAULT_ADMIN_ROLE role', async () => {
+ const { manageableVault, regularAccounts, owner } =
+ await loadMvFixture();
+ await changeTokenAllowanceTest(
+ { vault: manageableVault, owner },
+ ethers.constants.AddressZero,
+ 0,
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ from: regularAccounts[0],
+ },
+ );
+ });
+ it('should fail: token not exist', async () => {
+ const { manageableVault, owner, stableCoins } = await loadMvFixture();
+ await changeTokenAllowanceTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai.address,
+ 0,
+ {
+ revertCustomError: {
+ customErrorName: 'UnknownPaymentToken',
+ },
+ },
+ );
+ });
+ it('allowance zero', async () => {
+ const { manageableVault, owner, stableCoins, dataFeed } =
+ await loadMvFixture();
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await changeTokenAllowanceTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai.address,
+ 0,
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { manageableVault, owner, stableCoins, dataFeed } =
+ await loadMvFixture();
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ manageableVault,
+ encodeFnSelector('changeTokenAllowance(address,uint256)'),
+ );
+
+ await changeTokenAllowanceTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai.address,
+ 100000000,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const {
+ accessControl,
+ owner,
+ manageableVault,
+ regularAccounts,
+ stableCoins,
+ dataFeed,
+ } = await loadMvFixture();
+
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'changeTokenAllowance(address,uint256)',
+ regularAccounts[0].address,
+ );
+
+ expect(
+ await accessControl.hasRole(vaultRole, regularAccounts[0].address),
+ ).eq(false);
+
+ await changeTokenAllowanceTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai.address,
+ 100000000,
+ { from: regularAccounts[0] },
+ );
+ });
+
+ it('succeeds with scoped permission and vault admin role', async () => {
+ const {
+ accessControl,
+ owner,
+ manageableVault,
+ regularAccounts,
+ roles,
+ stableCoins,
+ dataFeed,
+ } = await loadMvFixture();
+
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'changeTokenAllowance(address,uint256)',
+ regularAccounts[0].address,
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ roles.tokenRoles.mTBILL.depositVaultAdmin,
+ regularAccounts[0].address,
+ );
+
+ await changeTokenAllowanceTest(
+ { vault: manageableVault, owner },
+ stableCoins.usdc.address,
+ 100000000,
+ { from: regularAccounts[0] },
+ );
+ });
+ });
+
+ describe('changeTokenFee()', () => {
+ it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
+ const { manageableVault, regularAccounts, owner } =
+ await loadMvFixture();
+ await changeTokenFeeTest(
+ { vault: manageableVault, owner },
+ ethers.constants.AddressZero,
+ 0,
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ from: regularAccounts[0],
+ },
+ );
+ });
+ it('should fail: token not exist', async () => {
+ const { manageableVault, owner, stableCoins } = await loadMvFixture();
+ await changeTokenFeeTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai.address,
+ 0,
+ {
+ revertCustomError: {
+ customErrorName: 'UnknownPaymentToken',
+ },
+ },
+ );
+ });
+ it('should fail: fee > 100%', async () => {
+ const { manageableVault, owner, stableCoins, dataFeed } =
+ await loadMvFixture();
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await changeTokenFeeTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai.address,
+ 10001,
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidFee',
+ },
+ },
+ );
+ });
+ it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
+ const { manageableVault, owner, stableCoins, dataFeed } =
+ await loadMvFixture();
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await changeTokenFeeTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai.address,
+ 100,
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { manageableVault, owner, stableCoins, dataFeed } =
+ await loadMvFixture();
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ manageableVault,
+ encodeFnSelector('changeTokenFee(address,uint256)'),
+ );
+
+ await changeTokenFeeTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai.address,
+ 100,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const {
+ accessControl,
+ owner,
+ manageableVault,
+ regularAccounts,
+ stableCoins,
+ dataFeed,
+ } = await loadMvFixture();
+
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'changeTokenFee(address,uint256)',
+ regularAccounts[0].address,
+ );
+
+ expect(
+ await accessControl.hasRole(vaultRole, regularAccounts[0].address),
+ ).eq(false);
+
+ await changeTokenFeeTest(
+ { vault: manageableVault, owner },
+ stableCoins.dai.address,
+ 100,
+ { from: regularAccounts[0] },
+ );
+ });
+
+ it('succeeds with scoped permission and vault admin role', async () => {
+ const {
+ accessControl,
+ owner,
+ manageableVault,
+ regularAccounts,
+ roles,
+ stableCoins,
+ dataFeed,
+ } = await loadMvFixture();
+
+ await addPaymentTokenTest(
+ { vault: manageableVault, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ const vaultRole = await manageableVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ vaultRole,
+ manageableVault.address,
+ 'changeTokenFee(address,uint256)',
+ regularAccounts[0].address,
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ roles.tokenRoles.mTBILL.depositVaultAdmin,
+ regularAccounts[0].address,
+ );
+
+ await changeTokenFeeTest(
+ { vault: manageableVault, owner },
+ stableCoins.usdc.address,
+ 100,
+ { from: regularAccounts[0] },
+ );
+ });
+ });
+
+ describe('internal functions', () => {
+ it('should fail: invalid rounding tokenTransferFromToTester()', async () => {
+ const { manageableVault, stableCoins, owner } = await loadMvFixture();
+
+ await mintToken(stableCoins.usdc, owner, 1000);
+
+ await approveBase18(owner, stableCoins.usdc, manageableVault, 1000);
+
+ await expect(
+ manageableVault.tokenTransferFromToTester(
+ stableCoins.usdc.address,
+ owner.address,
+ manageableVault.address,
+ parseUnits('999.999999999'),
+ 8,
+ ),
+ ).to.be.revertedWithCustomError(manageableVault, 'InvalidRounding');
+ });
+ });
+ });
+
+ otherTests(mvFixture);
+ });
+};
diff --git a/test/unit/suits/redemption-vault.suits.ts b/test/unit/suits/redemption-vault.suits.ts
new file mode 100644
index 00000000..8ffc3373
--- /dev/null
+++ b/test/unit/suits/redemption-vault.suits.ts
@@ -0,0 +1,14772 @@
+import {
+ loadFixture,
+ setBalance,
+} from '@nomicfoundation/hardhat-network-helpers';
+import { increase } from '@nomicfoundation/hardhat-network-helpers/dist/src/helpers/time';
+import {
+ days,
+ hours,
+} from '@nomicfoundation/hardhat-network-helpers/dist/src/helpers/time/duration';
+import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
+import { expect } from 'chai';
+import { BigNumber, constants, Contract } from 'ethers';
+import { parseUnits } from 'ethers/lib/utils';
+import { ethers } from 'hardhat';
+
+import {
+ baseInitParamsMv,
+ manageableVaultSuits,
+ mvInitializeParamCases,
+} from './manageable-vault.suits';
+
+import { encodeFnSelector } from '../../../helpers/utils';
+import {
+ ERC20Mock,
+ RedemptionVaultTest,
+ RedemptionVaultTest__factory,
+ RedemptionVaultWithAaveTest,
+ RedemptionVaultWithMorphoTest,
+ RedemptionVaultWithMTokenTest,
+ RedemptionVaultWithUSTBTest,
+} from '../../../typechain-types';
+import {
+ acErrors,
+ blackList,
+ greenList,
+ setupPermissionRole,
+} from '../../common/ac.helpers';
+import {
+ approveBase18,
+ InitializeParamCase,
+ initializeParamsSuits,
+ InitializeParamsOpt,
+ mintToken,
+ pauseVault,
+ pauseVaultFn,
+ asyncForEach,
+ getCurrentBlockTimestamp,
+} from '../../common/common.helpers';
+import {
+ setMinGrowthApr,
+ setRoundDataGrowth,
+} from '../../common/custom-feed-growth.helpers';
+import { setRoundData } from '../../common/data-feed.helpers';
+import { DefaultFixture } from '../../common/fixtures';
+import {
+ addPaymentTokenTest,
+ changeTokenAllowanceTest,
+ removePaymentTokenTest,
+ removeInstantLimitConfigTest,
+ setWaivedFeeAccountTest,
+ setInstantFeeTest,
+ setInstantLimitConfigTest,
+ setMinMaxInstantFeeTest,
+ setMinAmountTest,
+ setVariabilityToleranceTest,
+ withdrawTest,
+ setMaxInstantShareTest,
+ setSequentialRequestProcessingTest,
+} from '../../common/manageable-vault.helpers';
+import {
+ approveRedeemRequestTest,
+ bulkRepayLpLoanRequestTest,
+ cancelLpLoanRequestTest,
+ redeemInstantTest,
+ redeemRequestTest,
+ rejectRedeemRequestTest,
+ safeBulkApproveRequestTest,
+ setLoanLpTest,
+ setLoanRepaymentAddressTest,
+ setLoanSwapperVaultTest,
+ expectedHoldbackPartRateFromAvg,
+ setPreferLoanLiquidityTest,
+ setLoanAprTest,
+ setRequestRedeemerTest,
+ setMaxApproveRequestIdTest,
+ truncateToTokenDecimals,
+} from '../../common/redemption-vault.helpers';
+import { InitializerParamsRv } from '../../common/vault-initializer.helpers';
+import { sanctionUser } from '../../common/with-sanctions-list.helpers';
+
+const REDEMPTION_APPROVE_FN_SELECTORS = [
+ encodeFnSelector('approveRequest(uint256,uint256,bool)'),
+ encodeFnSelector('safeApproveRequest(uint256,uint256)'),
+ encodeFnSelector('safeApproveRequestAvgRate(uint256,uint256)'),
+ encodeFnSelector('safeBulkApproveRequestAtSavedRate(uint256[])'),
+ encodeFnSelector('safeBulkApproveRequest(uint256[])'),
+ encodeFnSelector('safeBulkApproveRequest(uint256[],uint256)'),
+ encodeFnSelector('safeBulkApproveRequestAvgRate(uint256[])'),
+ encodeFnSelector('safeBulkApproveRequestAvgRate(uint256[],uint256)'),
+] as const;
+
+let pauseManager: DefaultFixture['pauseManager'];
+let owner: DefaultFixture['owner'];
+
+export const baseInitParamsRv = (
+ fixture: DefaultFixture,
+): InitializerParamsRv => ({
+ ...baseInitParamsMv(fixture),
+ requestRedeemer: fixture.requestRedeemer,
+ loanLp: fixture.loanLp,
+ loanRepaymentAddress: fixture.loanRepaymentAddress,
+ redemptionVaultLoanSwapper: fixture.redemptionVaultLoanSwapper,
+});
+
+export const rvInitializeParamCases: InitializeParamCase[] =
+ [
+ {
+ title: 'requestRedeemer is zero address',
+ params: { requestRedeemer: constants.AddressZero },
+ revertCustomError: {
+ customErrorName: 'InvalidAddress',
+ args: [constants.AddressZero],
+ },
+ },
+ ];
+
+export const tokensReceiverSelfParamCase = <
+ TParams extends InitializerParamsRv,
+>(
+ deployUninitialized: RedemptionVaultInitConfig['deployUninitialized'],
+): InitializeParamCase => ({
+ title: 'tokensReceiver is address(this)',
+ contract: deployUninitialized,
+ params: (_, contract) => ({ tokensReceiver: contract!.address }),
+ revertCustomError: {
+ customErrorName: 'InvalidAddress',
+ args: (_, contract) => [contract!.address],
+ },
+});
+
+export type RedemptionVaultInitConfig<
+ TParams extends InitializerParamsRv = InitializerParamsRv,
+> = {
+ deployUninitialized: (
+ fixture: DefaultFixture,
+ ) => Contract | Promise;
+ initialize: (
+ fixture: DefaultFixture,
+ params: Partial,
+ opt?: InitializeParamsOpt,
+ ) => Promise;
+ extraParamCases?: InitializeParamCase[];
+};
+
+const pauseOtherRedemptionApproveFns = async (
+ redemptionVault: Contract,
+ exceptSelector: (typeof REDEMPTION_APPROVE_FN_SELECTORS)[number],
+) => {
+ await asyncForEach(
+ REDEMPTION_APPROVE_FN_SELECTORS,
+ async (selector) => {
+ if (selector === exceptSelector) {
+ return;
+ }
+ await pauseVaultFn({ pauseManager, owner }, redemptionVault, selector);
+ },
+ true,
+ );
+};
+
+export const redemptionVaultSuits = (
+ rvName: string,
+ rvFixture: () => Promise,
+ rvConfig: {
+ createNew: (
+ owner: SignerWithAddress,
+ ) => Promise<
+ | RedemptionVaultTest
+ | RedemptionVaultWithAaveTest
+ | RedemptionVaultWithMTokenTest
+ | RedemptionVaultWithUSTBTest
+ | RedemptionVaultWithMorphoTest
+ >;
+ key:
+ | 'redemptionVault'
+ | 'redemptionVaultWithAave'
+ | 'redemptionVaultWithMToken'
+ | 'redemptionVaultWithUSTB'
+ | 'redemptionVaultWithMorpho';
+ },
+ deploymentAdditionalChecks: (fixtureRes: DefaultFixture) => Promise,
+ initConfig: RedemptionVaultInitConfig,
+ otherTests: (fixture: () => Promise) => void,
+) => {
+ const loadRvFixture = async () => {
+ const fixture = await loadFixture(rvFixture);
+ ({ pauseManager, owner } = fixture);
+
+ const { createNew, key } = rvConfig;
+ return {
+ ...fixture,
+ redemptionVault: RedemptionVaultTest__factory.connect(
+ fixture[key].address,
+ fixture.owner,
+ ),
+ createNew: async () => {
+ const rv = await createNew(fixture.owner);
+ return RedemptionVaultTest__factory.connect(rv.address, fixture.owner);
+ },
+ };
+ };
+
+ describe(rvName, function () {
+ manageableVaultSuits(loadRvFixture, rvConfig, async (fixture) => {
+ const { redemptionVault, roles } = fixture;
+ expect(await redemptionVault.contractAdminRole()).eq(
+ roles.tokenRoles.mTBILL.redemptionVaultAdmin,
+ );
+ });
+
+ it('deployment', async () => {
+ const fixture = await loadRvFixture();
+ const {
+ redemptionVault,
+ loanLp,
+ loanRepaymentAddress,
+ redemptionVaultLoanSwapper,
+ } = fixture;
+
+ expect(await redemptionVault.maxInstantShare()).eq(100_00);
+ expect(await redemptionVault.loanLp()).eq(loanLp.address);
+
+ expect(await redemptionVault.loanRepaymentAddress()).eq(
+ loanRepaymentAddress.address,
+ );
+ expect(await redemptionVault.loanSwapperVault()).eq(
+ redemptionVaultLoanSwapper.address,
+ );
+
+ await deploymentAdditionalChecks(fixture);
+ });
+
+ describe('initialization', () => {
+ initializeParamsSuits(
+ [
+ ...mvInitializeParamCases,
+ ...rvInitializeParamCases,
+ tokensReceiverSelfParamCase(initConfig.deployUninitialized),
+ ...(initConfig.extraParamCases ?? []),
+ ],
+ loadRvFixture,
+ initConfig.initialize,
+ );
+ });
+
+ describe('common', () => {
+ describe('redeemInstant() complex', () => {
+ it('should fail: when vault is paused', async () => {
+ const {
+ redemptionVault,
+ owner,
+ mTBILL,
+ stableCoins,
+ regularAccounts,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+
+ await pauseVault({ pauseManager, owner }, redemptionVault);
+ await mintToken(stableCoins.dai, redemptionVault, 100);
+ await mintToken(mTBILL, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ redemptionVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('is on pause, but admin can use everything', async () => {
+ const {
+ redemptionVault,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+
+ await pauseVault({ pauseManager, owner }, redemptionVault);
+
+ await mintToken(stableCoins.dai, redemptionVault, 100);
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, stableCoins.dai, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('call for amount == minAmount', async () => {
+ const {
+ redemptionVault,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+
+ await mintToken(mTBILL, owner, 100_000);
+ await mintToken(stableCoins.dai, redemptionVault, 100_000);
+ await approveBase18(owner, mTBILL, redemptionVault, 100_000);
+
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setMinAmountTest({ vault: redemptionVault, owner }, 100_000);
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100_000,
+ );
+ });
+
+ it('redeem 100 mtbill, when price is 5$, 125 mtbill when price is 5.1$, 114 mtbill when price is 5.4$', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ mockedAggregatorMToken,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, owner, 100);
+ await mintToken(mTBILL, owner, 125);
+ await mintToken(mTBILL, owner, 114);
+
+ await mintToken(stableCoins.dai, redemptionVault, 1000);
+ await mintToken(stableCoins.usdc, redemptionVault, 1250);
+ await mintToken(stableCoins.usdt, redemptionVault, 1140);
+
+ await approveBase18(owner, mTBILL, redemptionVault, 100 + 125 + 114);
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.usdt,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setRoundData({ mockedAggregator }, 1.04);
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await setRoundData({ mockedAggregator }, 1);
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.usdc,
+ 125,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.01);
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.usdt,
+ 114,
+ );
+ });
+ });
+
+ describe('redeemInstant()', () => {
+ it('should fail: when there is no token in vault', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 1,
+ {
+ revertCustomError: {
+ customErrorName: 'UnknownPaymentToken',
+ },
+ },
+ );
+ });
+
+ it('should fail: when trying to redeem 0 amount', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 0,
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidAmount',
+ },
+ },
+ );
+ });
+
+ it('should fail: when function paused', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ } = await loadRvFixture();
+ await mintToken(mTBILL, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ redemptionVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ const selector = encodeFnSelector(
+ 'redeemInstant(address,uint256,uint256)',
+ );
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVault,
+ selector,
+ );
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('should fail: call with insufficient balance', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertMessage: 'ERC20: burn amount exceeds balance',
+ },
+ );
+ });
+
+ it('should fail: dataFeed rate 0 ', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+
+ await approveBase18(owner, stableCoins.dai, redemptionVault, 10);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await mintToken(mTBILL, owner, 100_000);
+ await setRoundData({ mockedAggregator }, 0);
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 1,
+ {
+ revertMessage: 'DF: feed is deprecated',
+ },
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 0);
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 1,
+ {
+ revertMessage: 'DF: feed is deprecated',
+ },
+ );
+ });
+
+ it('should fail: call for amount < minAmount', async () => {
+ const {
+ redemptionVault,
+ mockedAggregator,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+
+ await mintToken(mTBILL, owner, 100_000);
+ await approveBase18(owner, mTBILL, redemptionVault, 100_000);
+
+ await setMinAmountTest({ vault: redemptionVault, owner }, 100_000);
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 99_999,
+ {
+ revertCustomError: {
+ customErrorName: 'AmountLessThanMin',
+ },
+ },
+ );
+ });
+
+ it('should fail: if exceed allowance of deposit by token', async () => {
+ const {
+ redemptionVault,
+ mockedAggregator,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 4);
+
+ await mintToken(mTBILL, owner, 100_000);
+ await changeTokenAllowanceTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai.address,
+ 100,
+ );
+ await approveBase18(owner, mTBILL, redemptionVault, 100_000);
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 99_999,
+ {
+ revertCustomError: {
+ customErrorName: 'AllowanceExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: if redeem daily limit exceeded', async () => {
+ const {
+ redemptionVault,
+ mockedAggregator,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 4);
+
+ await mintToken(mTBILL, owner, 100_000);
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ 1000,
+ );
+
+ await approveBase18(owner, mTBILL, redemptionVault, 100_000);
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 99_999,
+ {
+ revertCustomError: {
+ customErrorName: 'WindowLimitExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: MV: invalid instant fee when instant fee below min', async () => {
+ const {
+ redemptionVault,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 4);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await mintToken(stableCoins.dai, redemptionVault, 1_000_000);
+
+ await setInstantFeeTest({ vault: redemptionVault, owner }, 100);
+ await setMinMaxInstantFeeTest(
+ { vault: redemptionVault, owner },
+ 200,
+ 10_000,
+ );
+
+ await mintToken(mTBILL, owner, 100_000);
+ await approveBase18(owner, mTBILL, redemptionVault, 100_000);
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertCustomError: {
+ customErrorName: 'InstantFeeOutOfBounds',
+ },
+ },
+ );
+ });
+
+ it('should fail: MV: invalid instant fee when instant fee above max', async () => {
+ const {
+ redemptionVault,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 4);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await mintToken(stableCoins.dai, redemptionVault, 1_000_000);
+
+ await setInstantFeeTest({ vault: redemptionVault, owner }, 5000);
+ await setMinMaxInstantFeeTest(
+ { vault: redemptionVault, owner },
+ 0,
+ 2000,
+ );
+
+ await mintToken(mTBILL, owner, 100_000);
+ await approveBase18(owner, mTBILL, redemptionVault, 100_000);
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertCustomError: {
+ customErrorName: 'InstantFeeOutOfBounds',
+ },
+ },
+ );
+ });
+
+ describe('redeemInstant() multiple instant limits', () => {
+ it('two windows (12h and 1d): redeem succeeds when under both', async () => {
+ const {
+ redemptionVault,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 4);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await mintToken(stableCoins.dai, redemptionVault, 1_000_000);
+
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: hours(12), limit: parseUnits('1000') },
+ );
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: days(1), limit: parseUnits('2000') },
+ );
+
+ await mintToken(mTBILL, owner, 100_000);
+ await approveBase18(owner, mTBILL, redemptionVault, 100_000);
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 500,
+ );
+ });
+
+ it('two windows: redeem fails when one window (12h) is filled', async () => {
+ const {
+ redemptionVault,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 4);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await mintToken(stableCoins.dai, redemptionVault, 1_000_000);
+
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: hours(12), limit: parseUnits('100') },
+ );
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: days(1), limit: parseUnits('10000') },
+ );
+
+ await mintToken(mTBILL, owner, 100_000);
+ await approveBase18(owner, mTBILL, redemptionVault, 100_000);
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 60,
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ {
+ revertCustomError: {
+ customErrorName: 'WindowLimitExceeded',
+ },
+ },
+ );
+ });
+
+ it('two windows: 12h epoch resets after 12h, redeem succeeds again', async () => {
+ const {
+ redemptionVault,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 4);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await mintToken(stableCoins.dai, redemptionVault, 1_000_000);
+
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: hours(12), limit: parseUnits('100') },
+ );
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: days(1), limit: parseUnits('10000') },
+ );
+
+ await mintToken(mTBILL, owner, 100_000);
+ await approveBase18(owner, mTBILL, redemptionVault, 100_000);
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await increase(hours(12));
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 10,
+ );
+ });
+
+ it('two windows: 1d epoch resets after 1d, redeem succeeds again', async () => {
+ const {
+ redemptionVault,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 4);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await mintToken(stableCoins.dai, redemptionVault, 1_000_000);
+
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: hours(12), limit: parseUnits('100000') },
+ );
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: days(1), limit: parseUnits('100') },
+ );
+
+ await mintToken(mTBILL, owner, 100_000);
+ await approveBase18(owner, mTBILL, redemptionVault, 100_000);
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await increase(days(1));
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+ });
+
+ it('four windows: redeem fails when one limit is exceeded', async () => {
+ const {
+ redemptionVault,
+ mockedAggregator,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 4);
+
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: hours(1), limit: parseUnits('50') },
+ );
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: hours(6), limit: parseUnits('500') },
+ );
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: hours(12), limit: parseUnits('5000') },
+ );
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: days(1), limit: parseUnits('50000') },
+ );
+
+ await mintToken(mTBILL, owner, 100_000);
+ await approveBase18(owner, mTBILL, redemptionVault, 100_000);
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 60,
+ {
+ revertCustomError: {
+ customErrorName: 'WindowLimitExceeded',
+ },
+ },
+ );
+ });
+
+ it('four windows: redeem succeeds when under all limits', async () => {
+ const {
+ redemptionVault,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 4);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await mintToken(stableCoins.dai, redemptionVault, 1_000_000);
+
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: hours(1), limit: parseUnits('100') },
+ );
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: hours(6), limit: parseUnits('500') },
+ );
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: hours(12), limit: parseUnits('1000') },
+ );
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: days(1), limit: parseUnits('5000') },
+ );
+
+ await mintToken(mTBILL, owner, 100_000);
+ await approveBase18(owner, mTBILL, redemptionVault, 100_000);
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+ });
+
+ it('remove window and add same window again: usage resets', async () => {
+ const {
+ redemptionVault,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 4);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await mintToken(stableCoins.dai, redemptionVault, 1_000_000);
+
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: days(1), limit: parseUnits('1000') },
+ );
+
+ await mintToken(mTBILL, owner, 100_000);
+ await approveBase18(owner, mTBILL, redemptionVault, 100_000);
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 400,
+ );
+
+ await removeInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ days(1),
+ );
+
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: days(1), limit: parseUnits('1000') },
+ );
+
+ const statuses = await redemptionVault.getInstantLimitStatuses();
+
+ expect(statuses[0].remaining).eq(parseUnits('1000'));
+ expect(statuses[0].lastUpdated).not.eq(0);
+ expect(statuses[0].window).eq(days(1));
+ expect(statuses[0].limit).eq(parseUnits('1000'));
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 700,
+ );
+ });
+
+ it('remove window: later redeem is not capped by removed limit', async () => {
+ const {
+ redemptionVault,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 4);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await mintToken(stableCoins.dai, redemptionVault, 1_000_000);
+
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: days(1), limit: parseUnits('100') },
+ );
+
+ await mintToken(mTBILL, owner, 100_000);
+ await approveBase18(owner, mTBILL, redemptionVault, 100_000);
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 80,
+ );
+
+ await removeInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ days(1),
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 500,
+ );
+ });
+ });
+
+ describe('redeemInstant() sliding rate limit (RateLimitLibrary)', () => {
+ const setupRedeemInstantRateLimitFixture = async () => {
+ const fixture = await loadRvFixture();
+ const {
+ redemptionVault,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ } = fixture;
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 4);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await mintToken(stableCoins.dai, redemptionVault, 1_000_000);
+ await mintToken(mTBILL, owner, 100_000);
+ await approveBase18(owner, mTBILL, redemptionVault, 100_000);
+
+ return fixture;
+ };
+
+ it('10h window: full consume, after 1h restores ~10% and two redeems use it', async () => {
+ const {
+ redemptionVault,
+ owner,
+ mTBILL,
+ stableCoins,
+ mTokenToUsdDataFeed,
+ } = await setupRedeemInstantRateLimitFixture();
+
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: hours(10), limit: parseUnits('1000') },
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 1000,
+ );
+
+ await increase(hours(1));
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ it('1d window: after 80% consumed and limit halved, redeem fails', async () => {
+ const {
+ redemptionVault,
+ owner,
+ mTBILL,
+ stableCoins,
+ mTokenToUsdDataFeed,
+ } = await setupRedeemInstantRateLimitFixture();
+
+ const initialLimit = parseUnits('1000');
+
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: days(1), limit: initialLimit },
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 800,
+ );
+
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: days(1), limit: initialLimit.div(2) },
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertCustomError: {
+ customErrorName: 'WindowLimitExceeded',
+ },
+ },
+ );
+ });
+
+ it('1d window: after limit halved, wait 12h and redeem small amount', async () => {
+ const {
+ redemptionVault,
+ owner,
+ mTBILL,
+ stableCoins,
+ mTokenToUsdDataFeed,
+ } = await setupRedeemInstantRateLimitFixture();
+
+ const initialLimit = parseUnits('1000');
+
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: days(1), limit: initialLimit },
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 800,
+ );
+
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: days(1), limit: initialLimit.div(2) },
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertCustomError: {
+ customErrorName: 'WindowLimitExceeded',
+ },
+ },
+ );
+
+ await increase(hours(18));
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 1,
+ );
+ });
+
+ it('multiple windows active at the same time', async () => {
+ const {
+ redemptionVault,
+ owner,
+ mTBILL,
+ stableCoins,
+ mTokenToUsdDataFeed,
+ } = await setupRedeemInstantRateLimitFixture();
+
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: hours(1), limit: parseUnits('100') },
+ );
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: hours(6), limit: parseUnits('500') },
+ );
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: days(1), limit: parseUnits('10000') },
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ {
+ revertCustomError: {
+ customErrorName: 'WindowLimitExceeded',
+ },
+ },
+ );
+
+ await increase(hours(1));
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 50,
+ );
+ });
+ });
+
+ it('should fail: if min receive amount greater then actual', async () => {
+ const {
+ redemptionVault,
+ mockedAggregator,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 4);
+
+ await mintToken(mTBILL, owner, 100_000);
+
+ await approveBase18(owner, mTBILL, redemptionVault, 100_000);
+
+ await redeemInstantTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ minAmount: parseUnits('1000000'),
+ },
+ stableCoins.dai,
+ 99_999,
+ {
+ revertCustomError: {
+ customErrorName: 'SlippageExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: if some fee = 100%', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 10000,
+ true,
+ );
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertCustomError: {
+ customErrorName: 'FeeExceedsAmount',
+ },
+ },
+ );
+
+ await removePaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setInstantFeeTest({ vault: redemptionVault, owner }, 10000);
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertCustomError: {
+ customErrorName: 'FeeExceedsAmount',
+ },
+ },
+ );
+ });
+
+ it('should fail: greenlist enabled and user not in greenlist ', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+
+ await redemptionVault.setGreenlistEnable(true);
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 1,
+ {
+ revertCustomError: {
+ customErrorName: 'NotGreenlisted',
+ },
+ },
+ );
+ });
+
+ it('should fail: user in blacklist ', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ blackListableTester,
+ accessControl,
+ regularAccounts,
+ } = await loadRvFixture();
+
+ await blackList(
+ { blacklistable: blackListableTester, accessControl, owner },
+ regularAccounts[0],
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 1,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_BLACKLISTED,
+ },
+ );
+ });
+
+ it('should fail: user in sanctions list', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ mockedSanctionsList,
+ } = await loadRvFixture();
+
+ await sanctionUser(
+ { sanctionsList: mockedSanctionsList },
+ regularAccounts[0],
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 1,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'Sanctioned',
+ },
+ },
+ );
+ });
+
+ it('should fail: when function with custom recipient is paused', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ customRecipient,
+ } = await loadRvFixture();
+ await mintToken(mTBILL, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ redemptionVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ const selector = encodeFnSelector(
+ 'redeemInstant(address,uint256,uint256,address)',
+ );
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVault,
+ selector,
+ );
+ await redeemInstantTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('should fail: greenlist enabled and recipient not in greenlist (custom recipient overload)', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ greenListableTester,
+ accessControl,
+ customRecipient,
+ } = await loadRvFixture();
+
+ await redemptionVault.setGreenlistEnable(true);
+
+ await greenList(
+ { greenlistable: greenListableTester, accessControl, owner },
+ owner,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 1,
+ {
+ revertCustomError: {
+ customErrorName: 'NotGreenlisted',
+ },
+ },
+ );
+ });
+
+ it('should fail: recipient in blacklist (custom recipient overload)', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ blackListableTester,
+ accessControl,
+ regularAccounts,
+ customRecipient,
+ } = await loadRvFixture();
+
+ await blackList(
+ { blacklistable: blackListableTester, accessControl, owner },
+ customRecipient,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 1,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_BLACKLISTED,
+ },
+ );
+ });
+
+ it('should fail: recipient in sanctions list (custom recipient overload)', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ mockedSanctionsList,
+ customRecipient,
+ } = await loadRvFixture();
+
+ await sanctionUser(
+ { sanctionsList: mockedSanctionsList },
+ customRecipient,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 1,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'Sanctioned',
+ },
+ },
+ );
+ });
+
+ it('when enough liquidity on vault but not on loan lp', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, owner, 100);
+ await mintToken(stableCoins.dai, redemptionVault, 1000);
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ describe('loan lp', () => {
+ describe('preferLoanLiquidity=false', () => {
+ it('when enough liquidity on loan lp but not on vault', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ loanLp,
+ mTokenLoan,
+ redemptionVaultLoanSwapper,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, owner, 100);
+ await mintToken(mTokenLoan, loanLp, 1000);
+ await approveBase18(loanLp, mTokenLoan, redemptionVault, 1000);
+ await mintToken(
+ stableCoins.dai,
+ redemptionVaultLoanSwapper,
+ 1000,
+ );
+
+ await approveBase18(
+ loanLp,
+ stableCoins.dai,
+ redemptionVault,
+ 1000,
+ );
+ await withdrawTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ await stableCoins.dai.balanceOf(redemptionVault.address),
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ it('when enough liquidity on loan lp and on vault but lp liquidity should be untouched', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ loanLp,
+ mTokenLoan,
+ redemptionVaultLoanSwapper,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, owner, 100);
+ await mintToken(mTokenLoan, loanLp, 1000);
+ await mintToken(stableCoins.dai, redemptionVault, 1000);
+ await approveBase18(loanLp, mTokenLoan, redemptionVault, 1000);
+ await mintToken(
+ stableCoins.dai,
+ redemptionVaultLoanSwapper,
+ 1000,
+ );
+
+ await approveBase18(
+ loanLp,
+ stableCoins.dai,
+ redemptionVault,
+ 1000,
+ );
+ await withdrawTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ await stableCoins.dai.balanceOf(redemptionVault.address),
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ it('when 25% of liquidity on vault and 75% on loan lp', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ loanLp,
+ mockedAggregatorMToken,
+ mockedAggregator,
+ mTokenLoan,
+ redemptionVaultLoanSwapper,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, owner, 100);
+ await mintToken(stableCoins.dai, redemptionVault, 25);
+
+ await mintToken(mTokenLoan, loanLp, 75);
+ await approveBase18(loanLp, mTokenLoan, redemptionVault, 75);
+ await mintToken(stableCoins.dai, redemptionVaultLoanSwapper, 75);
+ await approveBase18(loanLp, mTokenLoan, redemptionVault, 75);
+
+ await setRoundData(
+ { mockedAggregator: mockedAggregatorMToken },
+ 1,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ it('when 25% of liquidity on vault and 75% on loan lp and all the fees are 0%', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ loanLp,
+ mockedAggregatorMToken,
+ mockedAggregator,
+ mTokenLoan,
+ redemptionVaultLoanSwapper,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, owner, 100);
+ await mintToken(stableCoins.dai, redemptionVault, 25);
+
+ await mintToken(mTokenLoan, loanLp, 75);
+ await approveBase18(loanLp, mTokenLoan, redemptionVault, 75);
+ await mintToken(stableCoins.dai, redemptionVaultLoanSwapper, 75);
+ await approveBase18(loanLp, mTokenLoan, redemptionVault, 75);
+
+ await setRoundData(
+ { mockedAggregator: mockedAggregatorMToken },
+ 1,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setInstantFeeTest({ vault: redemptionVault, owner }, 0);
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ it('should fail: when not enough liquidity on both vault and loan lp', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ loanLp,
+ mTokenLoan,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(loanLp, mTokenLoan, redemptionVault, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 100,
+ true,
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertMessage: 'ERC20: transfer amount exceeds balance',
+ },
+ );
+ });
+
+ it('should fail: when not enough liquidity on vault and loan lp is not set', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, owner, 100);
+ await setLoanLpTest(
+ { redemptionVault, owner },
+ ethers.constants.AddressZero,
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 100,
+ true,
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertMessage: 'ERC20: transfer amount exceeds balance',
+ },
+ );
+ });
+
+ it('should fail: when not enough liquidity on vault and loan swapper is not set', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, owner, 100);
+ await setLoanSwapperVaultTest(
+ { redemptionVault, owner },
+ ethers.constants.AddressZero,
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 100,
+ true,
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertMessage: 'ERC20: transfer amount exceeds balance',
+ },
+ );
+ });
+
+ it('should fail: when not enough liquidity on vault and loan swapper and loanLp are not set', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, owner, 100);
+ await setLoanSwapperVaultTest(
+ { redemptionVault, owner },
+ ethers.constants.AddressZero,
+ );
+ await setLoanLpTest(
+ { redemptionVault, owner },
+ ethers.constants.AddressZero,
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 100,
+ true,
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertMessage: 'ERC20: transfer amount exceeds balance',
+ },
+ );
+ });
+
+ it('should fail: when not fee waived and vault does not have enough liquidity', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ redemptionVaultLoanSwapper,
+ loanLp,
+ mTokenLoan,
+ mockedAggregatorMToken,
+ mockedAggregator,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, owner, 100);
+
+ await mintToken(mTokenLoan, loanLp, 100);
+ await approveBase18(loanLp, mTokenLoan, redemptionVault, 100);
+ await mintToken(stableCoins.dai, redemptionVaultLoanSwapper, 100);
+ await approveBase18(loanLp, mTokenLoan, redemptionVault, 100);
+
+ await setWaivedFeeAccountTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ redemptionVault.address,
+ false,
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 100,
+ true,
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 100,
+ true,
+ );
+
+ await setRoundData(
+ { mockedAggregator: mockedAggregatorMToken },
+ 1,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertMessage: 'ERC20: transfer amount exceeds balance',
+ },
+ );
+ });
+ });
+
+ describe('preferLoanLiquidity=true', () => {
+ it('when enough liquidity on loan lp but not on vault', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ loanLp,
+ mTokenLoan,
+ redemptionVaultLoanSwapper,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, owner, 100);
+ await mintToken(mTokenLoan, loanLp, 1000);
+ await approveBase18(loanLp, mTokenLoan, redemptionVault, 1000);
+ await mintToken(
+ stableCoins.dai,
+ redemptionVaultLoanSwapper,
+ 1000,
+ );
+
+ await approveBase18(
+ loanLp,
+ stableCoins.dai,
+ redemptionVault,
+ 1000,
+ );
+ await withdrawTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ await stableCoins.dai.balanceOf(redemptionVault.address),
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setPreferLoanLiquidityTest(
+ { redemptionVault, owner },
+ true,
+ );
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ it('when enough liquidity on loan lp and on vault but vault liquidity should be untouched', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ loanLp,
+ mTokenLoan,
+ redemptionVaultLoanSwapper,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, owner, 100);
+ await mintToken(mTokenLoan, loanLp, 1000);
+ await mintToken(stableCoins.dai, redemptionVault, 1000);
+ await approveBase18(loanLp, mTokenLoan, redemptionVault, 1000);
+ await mintToken(
+ stableCoins.dai,
+ redemptionVaultLoanSwapper,
+ 1000,
+ );
+
+ await approveBase18(
+ loanLp,
+ stableCoins.dai,
+ redemptionVault,
+ 1000,
+ );
+ await withdrawTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ await stableCoins.dai.balanceOf(redemptionVault.address),
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setPreferLoanLiquidityTest(
+ { redemptionVault, owner },
+ true,
+ );
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ it('when 25% of liquidity on vault and 75% on loan lp', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ loanLp,
+ mockedAggregatorMToken,
+ mockedAggregator,
+ mTokenLoan,
+ redemptionVaultLoanSwapper,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, owner, 100);
+ await mintToken(stableCoins.dai, redemptionVault, 25);
+
+ await mintToken(mTokenLoan, loanLp, 75);
+ await approveBase18(loanLp, mTokenLoan, redemptionVault, 75);
+ await mintToken(stableCoins.dai, redemptionVaultLoanSwapper, 75);
+ await approveBase18(loanLp, mTokenLoan, redemptionVault, 75);
+
+ await setRoundData(
+ { mockedAggregator: mockedAggregatorMToken },
+ 1,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setPreferLoanLiquidityTest(
+ { redemptionVault, owner },
+ true,
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ it('when 25% of liquidity on vault and 75% on loan lp and all the fees are 0%', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ loanLp,
+ mockedAggregatorMToken,
+ mockedAggregator,
+ mTokenLoan,
+ redemptionVaultLoanSwapper,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, owner, 100);
+ await mintToken(stableCoins.dai, redemptionVault, 25);
+
+ await mintToken(mTokenLoan, loanLp, 75);
+ await approveBase18(loanLp, mTokenLoan, redemptionVault, 75);
+ await mintToken(stableCoins.dai, redemptionVaultLoanSwapper, 75);
+ await approveBase18(loanLp, mTokenLoan, redemptionVault, 75);
+
+ await setRoundData(
+ { mockedAggregator: mockedAggregatorMToken },
+ 1,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setInstantFeeTest({ vault: redemptionVault, owner }, 0);
+ await setPreferLoanLiquidityTest(
+ { redemptionVault, owner },
+ true,
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ it('should fail: when not enough liquidity on both vault and loan lp', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ loanLp,
+ mTokenLoan,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(loanLp, mTokenLoan, redemptionVault, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 100,
+ true,
+ );
+ await setPreferLoanLiquidityTest(
+ { redemptionVault, owner },
+ true,
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertMessage: 'ERC20: transfer amount exceeds balance',
+ },
+ );
+ });
+
+ it('should fail: when not enough liquidity on vault and loan lp is not set', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, owner, 100);
+ await setLoanLpTest(
+ { redemptionVault, owner },
+ ethers.constants.AddressZero,
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 100,
+ true,
+ );
+ await setPreferLoanLiquidityTest(
+ { redemptionVault, owner },
+ true,
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertMessage: 'ERC20: transfer amount exceeds balance',
+ },
+ );
+ });
+
+ it('should fail: when not enough liquidity on vault and loan swapper is not set', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, owner, 100);
+ await setLoanSwapperVaultTest(
+ { redemptionVault, owner },
+ ethers.constants.AddressZero,
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 100,
+ true,
+ );
+ await setPreferLoanLiquidityTest(
+ { redemptionVault, owner },
+ true,
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertMessage: 'ERC20: transfer amount exceeds balance',
+ },
+ );
+ });
+
+ it('should fail: when not enough liquidity on vault and loan swapper and loanLp are not set', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, owner, 100);
+ await setLoanSwapperVaultTest(
+ { redemptionVault, owner },
+ ethers.constants.AddressZero,
+ );
+ await setLoanLpTest(
+ { redemptionVault, owner },
+ ethers.constants.AddressZero,
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 100,
+ true,
+ );
+ await setPreferLoanLiquidityTest(
+ { redemptionVault, owner },
+ true,
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertMessage: 'ERC20: transfer amount exceeds balance',
+ },
+ );
+ });
+
+ it('should skip loan liquidity and fallback to vault liquidity when not fee waived', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ loanLp,
+ mTokenLoan,
+ redemptionVaultLoanSwapper,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, owner, 100);
+ await mintToken(mTokenLoan, loanLp, 1000);
+ await mintToken(stableCoins.dai, redemptionVault, 1000);
+ await approveBase18(loanLp, mTokenLoan, redemptionVault, 1000);
+ await mintToken(
+ stableCoins.dai,
+ redemptionVaultLoanSwapper,
+ 1000,
+ );
+
+ await setWaivedFeeAccountTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ redemptionVault.address,
+ false,
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setPreferLoanLiquidityTest(
+ { redemptionVault, owner },
+ true,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ loanLiquidityExpectToFail: true,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ it('should fail: when not fee waived and vault does not have enough liquidity', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ redemptionVaultLoanSwapper,
+ loanLp,
+ mTokenLoan,
+ mockedAggregatorMToken,
+ mockedAggregator,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, owner, 100);
+
+ await mintToken(mTokenLoan, loanLp, 100);
+ await approveBase18(loanLp, mTokenLoan, redemptionVault, 100);
+ await mintToken(stableCoins.dai, redemptionVaultLoanSwapper, 100);
+ await approveBase18(loanLp, mTokenLoan, redemptionVault, 100);
+
+ await setWaivedFeeAccountTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ redemptionVault.address,
+ false,
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 100,
+ true,
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 100,
+ true,
+ );
+
+ await setRoundData(
+ { mockedAggregator: mockedAggregatorMToken },
+ 1,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setPreferLoanLiquidityTest(
+ { redemptionVault, owner },
+ true,
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertMessage: 'ERC20: transfer amount exceeds balance',
+ },
+ );
+ });
+
+ it('should fail: when swapper fails it should catch the error and fail later during the transfer', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ redemptionVaultLoanSwapper,
+ loanLp,
+ mTokenLoan,
+ mockedAggregatorMToken,
+ mockedAggregatorMTokenLoan,
+ mockedAggregator,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, owner, 100);
+
+ await mintToken(mTokenLoan, loanLp, 100);
+ await approveBase18(loanLp, mTokenLoan, redemptionVault, 100);
+ await mintToken(stableCoins.dai, redemptionVaultLoanSwapper, 100);
+ await approveBase18(loanLp, mTokenLoan, redemptionVault, 100);
+
+ await setWaivedFeeAccountTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ redemptionVault.address,
+ false,
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 100,
+ true,
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 100,
+ true,
+ );
+
+ await setRoundData(
+ { mockedAggregator: mockedAggregatorMToken },
+ 1,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData(
+ { mockedAggregator: mockedAggregatorMTokenLoan },
+ 0,
+ );
+
+ await setPreferLoanLiquidityTest(
+ { redemptionVault, owner },
+ true,
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertMessage: 'ERC20: transfer amount exceeds balance',
+ },
+ );
+ });
+ });
+ });
+
+ describe('holdback', () => {
+ it('when max instant share is 100%', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, owner, 100);
+ await mintToken(stableCoins.dai, redemptionVault, 1000);
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ it('should fail: when max instant share is not 100%', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, owner, 100);
+ await mintToken(stableCoins.dai, redemptionVault, 1000);
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setMaxInstantShareTest(
+ { vault: redemptionVault, owner },
+ 90_00,
+ );
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertCustomError: {
+ customErrorName: 'InstantShareTooHigh',
+ },
+ },
+ );
+ });
+ });
+
+ it('redeem 100 mTBILL when 10% growth is applied', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ customFeedGrowth,
+ } = await loadRvFixture();
+
+ await mTokenToUsdDataFeed.changeAggregator(customFeedGrowth.address);
+ await setRoundDataGrowth({ owner, customFeedGrowth }, 1, -1000, 10);
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, regularAccounts[0], 100);
+ await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ expectedAmountOut: parseUnits('99.000314820', 9),
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('redeem 100 mTBILL when -10% growth is applied', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ customFeedGrowth,
+ } = await loadRvFixture();
+
+ await setMinGrowthApr({ owner, customFeedGrowth }, -10);
+ await mTokenToUsdDataFeed.changeAggregator(customFeedGrowth.address);
+ await setRoundDataGrowth({ owner, customFeedGrowth }, 1, -1000, -10);
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, regularAccounts[0], 100);
+ await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ expectedAmountOut: parseUnits('98.999685180', 9),
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('redeem 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ it('redeem 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$ and token fee 1%', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 100,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ it('redeem 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$ without checking of minDepositAmount', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 100,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await redemptionVault.freeFromMinAmount(owner.address, true);
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ it('redeem 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$ and user in waivedFeeRestriction', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 100,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setWaivedFeeAccountTest(
+ { vault: redemptionVault, owner },
+ owner.address,
+ true,
+ );
+ await redeemInstantTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ waivedFee: true,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ it('redeem 100 mTBILL (custom recipient overload)', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ customRecipient,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, regularAccounts[0], 100);
+ await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('redeem 100 mTBILL when recipient == msg.sender (custom recipient overload)', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, regularAccounts[0], 100);
+ await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient: regularAccounts[0],
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('redeem 100 mTBILL when other fn overload is paused (custom recipient overload)', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ customRecipient,
+ } = await loadRvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVault,
+ encodeFnSelector('redeemInstant(address,uint256,uint256)'),
+ );
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, regularAccounts[0], 100);
+ await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('when user didnt approve mTokens to redeem', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, regularAccounts[0], 10);
+ await mintToken(stableCoins.dai, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 10,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('redeem 100 mTBILL when other fn overload is paused', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadRvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVault,
+ encodeFnSelector('redeemInstant(address,uint256,uint256,address)'),
+ );
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, regularAccounts[0], 100);
+ await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemInstantTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+ });
+
+ describe('setLoanLp()', () => {
+ it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
+ const { redemptionVault, regularAccounts, owner } = await loadFixture(
+ rvFixture,
+ );
+ await setLoanLpTest(
+ { redemptionVault, owner },
+ ethers.constants.AddressZero,
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ from: regularAccounts[0],
+ },
+ );
+ });
+ it('if new loanLp address zero', async () => {
+ const { redemptionVault, owner } = await loadRvFixture();
+ await setLoanLpTest(
+ { redemptionVault, owner },
+ ethers.constants.AddressZero,
+ );
+ });
+
+ it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
+ const { redemptionVault, owner } = await loadRvFixture();
+ await setLoanLpTest({ redemptionVault, owner }, owner.address);
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { redemptionVault, owner } = await loadRvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVault,
+ encodeFnSelector('setLoanLp(address)'),
+ );
+
+ await setLoanLpTest({ redemptionVault, owner }, owner.address, {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ });
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const { accessControl, owner, redemptionVault, regularAccounts } =
+ await loadRvFixture();
+
+ const contractAdminRole = await redemptionVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ contractAdminRole,
+ redemptionVault.address,
+ 'setLoanLp(address)',
+ regularAccounts[0].address,
+ );
+
+ expect(
+ await accessControl.hasRole(
+ contractAdminRole,
+ regularAccounts[0].address,
+ ),
+ ).eq(false);
+
+ await setLoanLpTest(
+ { redemptionVault, owner },
+ regularAccounts[1].address,
+ { from: regularAccounts[0] },
+ );
+ });
+
+ it('succeeds with scoped permission and vault admin role', async () => {
+ const {
+ accessControl,
+ owner,
+ redemptionVault,
+ regularAccounts,
+ roles,
+ } = await loadRvFixture();
+
+ const contractAdminRole = await redemptionVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ contractAdminRole,
+ redemptionVault.address,
+ 'setLoanLp(address)',
+ regularAccounts[0].address,
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ roles.tokenRoles.mTBILL.redemptionVaultAdmin,
+ regularAccounts[0].address,
+ );
+
+ await setLoanLpTest(
+ { redemptionVault, owner },
+ regularAccounts[2].address,
+ { from: regularAccounts[0] },
+ );
+ });
+ });
+
+ describe('setLoanRepaymentAddress()', () => {
+ it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
+ const { redemptionVault, regularAccounts, owner } = await loadFixture(
+ rvFixture,
+ );
+ await setLoanRepaymentAddressTest(
+ { redemptionVault, owner },
+ regularAccounts[0].address,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
+ const { redemptionVault, owner, regularAccounts } =
+ await loadRvFixture();
+ await setLoanRepaymentAddressTest(
+ { redemptionVault, owner },
+ regularAccounts[0].address,
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { redemptionVault, owner, regularAccounts } =
+ await loadRvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVault,
+ encodeFnSelector('setLoanRepaymentAddress(address)'),
+ );
+
+ await setLoanRepaymentAddressTest(
+ { redemptionVault, owner },
+ regularAccounts[0].address,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const { accessControl, owner, redemptionVault, regularAccounts } =
+ await loadRvFixture();
+
+ const contractAdminRole = await redemptionVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ contractAdminRole,
+ redemptionVault.address,
+ 'setLoanRepaymentAddress(address)',
+ regularAccounts[0].address,
+ );
+
+ expect(
+ await accessControl.hasRole(
+ contractAdminRole,
+ regularAccounts[0].address,
+ ),
+ ).eq(false);
+
+ await setLoanRepaymentAddressTest(
+ { redemptionVault, owner },
+ regularAccounts[1].address,
+ { from: regularAccounts[0] },
+ );
+ });
+
+ it('succeeds with scoped permission and vault admin role', async () => {
+ const {
+ accessControl,
+ owner,
+ redemptionVault,
+ regularAccounts,
+ roles,
+ } = await loadRvFixture();
+
+ const contractAdminRole = await redemptionVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ contractAdminRole,
+ redemptionVault.address,
+ 'setLoanRepaymentAddress(address)',
+ regularAccounts[0].address,
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ roles.tokenRoles.mTBILL.redemptionVaultAdmin,
+ regularAccounts[0].address,
+ );
+
+ await setLoanRepaymentAddressTest(
+ { redemptionVault, owner },
+ regularAccounts[2].address,
+ { from: regularAccounts[0] },
+ );
+ });
+ });
+
+ describe('setLoanSwapperVault()', () => {
+ it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
+ const { redemptionVault, regularAccounts, owner } = await loadFixture(
+ rvFixture,
+ );
+ await setLoanSwapperVaultTest(
+ { redemptionVault, owner },
+ regularAccounts[0].address,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
+ const { redemptionVault, owner, regularAccounts } =
+ await loadRvFixture();
+ await setLoanSwapperVaultTest(
+ { redemptionVault, owner },
+ regularAccounts[0].address,
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { redemptionVault, owner, regularAccounts } =
+ await loadRvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVault,
+ encodeFnSelector('setLoanSwapperVault(address)'),
+ );
+
+ await setLoanSwapperVaultTest(
+ { redemptionVault, owner },
+ regularAccounts[0].address,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const { accessControl, owner, redemptionVault, regularAccounts } =
+ await loadRvFixture();
+
+ const contractAdminRole = await redemptionVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ contractAdminRole,
+ redemptionVault.address,
+ 'setLoanSwapperVault(address)',
+ regularAccounts[0].address,
+ );
+
+ expect(
+ await accessControl.hasRole(
+ contractAdminRole,
+ regularAccounts[0].address,
+ ),
+ ).eq(false);
+
+ await setLoanSwapperVaultTest(
+ { redemptionVault, owner },
+ regularAccounts[1].address,
+ { from: regularAccounts[0] },
+ );
+ });
+
+ it('succeeds with scoped permission and vault admin role', async () => {
+ const {
+ accessControl,
+ owner,
+ redemptionVault,
+ regularAccounts,
+ roles,
+ } = await loadRvFixture();
+
+ const contractAdminRole = await redemptionVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ contractAdminRole,
+ redemptionVault.address,
+ 'setLoanSwapperVault(address)',
+ regularAccounts[0].address,
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ roles.tokenRoles.mTBILL.redemptionVaultAdmin,
+ regularAccounts[0].address,
+ );
+
+ await setLoanSwapperVaultTest(
+ { redemptionVault, owner },
+ regularAccounts[2].address,
+ { from: regularAccounts[0] },
+ );
+ });
+ });
+
+ describe('setPreferLoanLiquidity()', () => {
+ it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
+ const { redemptionVault, regularAccounts, owner } = await loadFixture(
+ rvFixture,
+ );
+ await setPreferLoanLiquidityTest({ redemptionVault, owner }, true, {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ });
+ });
+
+ it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
+ const { redemptionVault, owner } = await loadRvFixture();
+ await setPreferLoanLiquidityTest({ redemptionVault, owner }, true);
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { redemptionVault, owner } = await loadRvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVault,
+ encodeFnSelector('setPreferLoanLiquidity(bool)'),
+ );
+
+ await setPreferLoanLiquidityTest({ redemptionVault, owner }, true, {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ });
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const { accessControl, owner, redemptionVault, regularAccounts } =
+ await loadRvFixture();
+
+ const contractAdminRole = await redemptionVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ contractAdminRole,
+ redemptionVault.address,
+ 'setPreferLoanLiquidity(bool)',
+ regularAccounts[0].address,
+ );
+
+ expect(
+ await accessControl.hasRole(
+ contractAdminRole,
+ regularAccounts[0].address,
+ ),
+ ).eq(false);
+
+ await setPreferLoanLiquidityTest({ redemptionVault, owner }, true, {
+ from: regularAccounts[0],
+ });
+ });
+
+ it('succeeds with scoped permission and vault admin role', async () => {
+ const {
+ accessControl,
+ owner,
+ redemptionVault,
+ regularAccounts,
+ roles,
+ } = await loadRvFixture();
+
+ const contractAdminRole = await redemptionVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ contractAdminRole,
+ redemptionVault.address,
+ 'setPreferLoanLiquidity(bool)',
+ regularAccounts[0].address,
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ roles.tokenRoles.mTBILL.redemptionVaultAdmin,
+ regularAccounts[0].address,
+ );
+
+ await setPreferLoanLiquidityTest({ redemptionVault, owner }, true, {
+ from: regularAccounts[0],
+ });
+ });
+ });
+
+ describe('setRequestRedeemer()', () => {
+ it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
+ const { redemptionVault, regularAccounts, owner } =
+ await loadRvFixture();
+
+ await setRequestRedeemerTest(
+ { redemptionVault, owner },
+ regularAccounts[1].address,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('should fail: if redeemer is zero address', async () => {
+ const { redemptionVault, owner } = await loadRvFixture();
+
+ await setRequestRedeemerTest(
+ { redemptionVault, owner },
+ constants.AddressZero,
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidAddress',
+ args: [constants.AddressZero],
+ },
+ },
+ );
+ });
+
+ it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
+ const { redemptionVault, owner, regularAccounts } =
+ await loadRvFixture();
+
+ await setRequestRedeemerTest(
+ { redemptionVault, owner },
+ regularAccounts[1].address,
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { redemptionVault, owner, regularAccounts } =
+ await loadRvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVault,
+ encodeFnSelector('setRequestRedeemer(address)'),
+ );
+
+ await setRequestRedeemerTest(
+ { redemptionVault, owner },
+ regularAccounts[1].address,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const { accessControl, owner, redemptionVault, regularAccounts } =
+ await loadRvFixture();
+
+ const contractAdminRole = await redemptionVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ contractAdminRole,
+ redemptionVault.address,
+ 'setRequestRedeemer(address)',
+ regularAccounts[0].address,
+ );
+
+ expect(
+ await accessControl.hasRole(
+ contractAdminRole,
+ regularAccounts[0].address,
+ ),
+ ).eq(false);
+
+ await setRequestRedeemerTest(
+ { redemptionVault, owner },
+ regularAccounts[1].address,
+ { from: regularAccounts[0] },
+ );
+ });
+
+ it('succeeds with scoped permission and vault admin role', async () => {
+ const {
+ accessControl,
+ owner,
+ redemptionVault,
+ regularAccounts,
+ roles,
+ } = await loadRvFixture();
+
+ const contractAdminRole = await redemptionVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ contractAdminRole,
+ redemptionVault.address,
+ 'setRequestRedeemer(address)',
+ regularAccounts[0].address,
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ roles.tokenRoles.mTBILL.redemptionVaultAdmin,
+ regularAccounts[0].address,
+ );
+
+ await setRequestRedeemerTest(
+ { redemptionVault, owner },
+ regularAccounts[2].address,
+ { from: regularAccounts[0] },
+ );
+ });
+ });
+
+ describe('setLoanApr()', () => {
+ it('should fail: call from address without REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
+ const { redemptionVault, regularAccounts, owner } =
+ await loadRvFixture();
+
+ await setLoanAprTest({ redemptionVault, owner }, 5000, {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ });
+ });
+
+ it('call from address with REDEMPTION_VAULT_ADMIN_ROLE role', async () => {
+ const { redemptionVault, owner } = await loadRvFixture();
+ await setLoanAprTest({ redemptionVault, owner }, 5000);
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { redemptionVault, owner } = await loadRvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVault,
+ encodeFnSelector('setLoanApr(uint256)'),
+ );
+
+ await setLoanAprTest({ redemptionVault, owner }, 5000, {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ });
+ });
+
+ it('succeeds with only scoped function permission', async () => {
+ const { accessControl, owner, redemptionVault, regularAccounts } =
+ await loadRvFixture();
+
+ const contractAdminRole = await redemptionVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ contractAdminRole,
+ redemptionVault.address,
+ 'setLoanApr(uint256)',
+ regularAccounts[0].address,
+ );
+
+ expect(
+ await accessControl.hasRole(
+ contractAdminRole,
+ regularAccounts[0].address,
+ ),
+ ).eq(false);
+
+ await setLoanAprTest({ redemptionVault, owner }, 5000, {
+ from: regularAccounts[0],
+ });
+ });
+
+ it('succeeds with scoped permission and vault admin role', async () => {
+ const {
+ accessControl,
+ owner,
+ redemptionVault,
+ regularAccounts,
+ roles,
+ } = await loadRvFixture();
+
+ const contractAdminRole = await redemptionVault.contractAdminRole();
+ await setupPermissionRole(
+ { accessControl, owner },
+ contractAdminRole,
+ redemptionVault.address,
+ 'setLoanApr(uint256)',
+ regularAccounts[0].address,
+ );
+
+ await accessControl['grantRole(bytes32,address)'](
+ roles.tokenRoles.mTBILL.redemptionVaultAdmin,
+ regularAccounts[0].address,
+ );
+
+ await setLoanAprTest({ redemptionVault, owner }, 7500, {
+ from: regularAccounts[0],
+ });
+ });
+ });
+
+ describe('redeemRequest()', () => {
+ describe('holdback', () => {
+ it('when 40% instant and 60% holdback', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ mTBILL,
+ redemptionVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 40_00,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('when 90% instant and 10% holdback', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ mTBILL,
+ redemptionVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 90_00,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('when 0% instant and 100% holdback', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ mTBILL,
+ redemptionVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 0,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('when 50% instant and 50% holdback and request recipient is different from msg.sender', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ mTBILL,
+ redemptionVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 0,
+ customRecipient: regularAccounts[1],
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('when 50% instant and 50% holdback and instant recipient is different from msg.sender', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ mTBILL,
+ redemptionVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 0,
+ customRecipientInstant: regularAccounts[1],
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('when 50% instant and 50% holdback and request and instant recipients are different from msg.sender and from each other', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ mTBILL,
+ redemptionVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 0,
+ customRecipient: regularAccounts[1],
+ customRecipientInstant: regularAccounts[2],
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('should fail: when 100% instant and 0% holdback', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ mTBILL,
+ redemptionVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 100_00,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'InvalidAmount',
+ },
+ },
+ );
+ });
+
+ it('should fail: when instant share exceeds max instant share', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ mTBILL,
+ redemptionVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setMaxInstantShareTest(
+ { vault: redemptionVault, owner },
+ 80_00,
+ );
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 90_00,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'InstantShareTooHigh',
+ },
+ },
+ );
+ });
+ });
+
+ it('should fail: when there is no token in vault', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 1,
+ {
+ revertCustomError: {
+ customErrorName: 'UnknownPaymentToken',
+ },
+ },
+ );
+ });
+
+ it('should fail: when trying to redeem 0 amount', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 0,
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidAmount',
+ },
+ },
+ );
+ });
+
+ it('should fail: when function paused', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ } = await loadRvFixture();
+ await mintToken(mTBILL, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ redemptionVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ const selector = encodeFnSelector('redeemRequest(address,uint256)');
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVault,
+ selector,
+ );
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('should fail: call with insufficient allowance', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, owner, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertMessage: 'ERC20: insufficient allowance',
+ },
+ );
+ });
+
+ it('should fail: call with insufficient balance', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertMessage: 'ERC20: transfer amount exceeds balance',
+ },
+ );
+ });
+
+ it('should fail: dataFeed rate 0 ', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+
+ await approveBase18(owner, stableCoins.dai, redemptionVault, 10);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await mintToken(mTBILL, owner, 100_000);
+ await setRoundData({ mockedAggregator }, 0);
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 1,
+ {
+ revertMessage: 'DF: feed is deprecated',
+ },
+ );
+ await setRoundData({ mockedAggregator }, 1);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 0);
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 1,
+ {
+ revertMessage: 'DF: feed is deprecated',
+ },
+ );
+ });
+
+ it('should fail: call for amount < minAmount', async () => {
+ const {
+ redemptionVault,
+ mockedAggregator,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+
+ await mintToken(mTBILL, owner, 100_000);
+ await approveBase18(owner, mTBILL, redemptionVault, 100_000);
+
+ await setMinAmountTest({ vault: redemptionVault, owner }, 100_000);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 99_999,
+ {
+ revertCustomError: {
+ customErrorName: 'AmountLessThanMin',
+ },
+ },
+ );
+ });
+
+ it('should fail: greenlist enabled and user not in greenlist ', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+
+ await redemptionVault.setGreenlistEnable(true);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 1,
+ {
+ revertCustomError: {
+ customErrorName: 'NotGreenlisted',
+ },
+ },
+ );
+ });
+
+ it('should fail: user in blacklist ', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ blackListableTester,
+ accessControl,
+ regularAccounts,
+ } = await loadRvFixture();
+
+ await blackList(
+ { blacklistable: blackListableTester, accessControl, owner },
+ regularAccounts[0],
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 1,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_BLACKLISTED,
+ },
+ );
+ });
+
+ it('should fail: user in sanctions list', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ mockedSanctionsList,
+ } = await loadRvFixture();
+
+ await sanctionUser(
+ { sanctionsList: mockedSanctionsList },
+ regularAccounts[0],
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 1,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'Sanctioned',
+ },
+ },
+ );
+ });
+
+ it('should fail: when function paused (custom recipient overload)', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ customRecipient,
+ } = await loadRvFixture();
+ await mintToken(mTBILL, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ redemptionVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ const selector = encodeFnSelector(
+ 'redeemRequest(address,uint256,address,uint256,uint256,address)',
+ );
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVault,
+ selector,
+ );
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('should fail: greenlist enabled and recipient not in greenlist (custom recipient overload)', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ greenListableTester,
+ accessControl,
+ customRecipient,
+ } = await loadRvFixture();
+
+ await redemptionVault.setGreenlistEnable(true);
+
+ await greenList(
+ { greenlistable: greenListableTester, accessControl, owner },
+ owner,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 1,
+ {
+ revertCustomError: {
+ customErrorName: 'NotGreenlisted',
+ },
+ },
+ );
+ });
+
+ it('should fail: recipient in blacklist (custom recipient overload)', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ blackListableTester,
+ accessControl,
+ regularAccounts,
+ customRecipient,
+ } = await loadRvFixture();
+
+ await blackList(
+ { blacklistable: blackListableTester, accessControl, owner },
+ customRecipient,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 1,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_BLACKLISTED,
+ },
+ );
+ });
+
+ it('should fail: recipient in sanctions list (custom recipient overload)', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ mockedSanctionsList,
+ customRecipient,
+ } = await loadRvFixture();
+
+ await sanctionUser(
+ { sanctionsList: mockedSanctionsList },
+ customRecipient,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 1,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'Sanctioned',
+ },
+ },
+ );
+ });
+
+ it('redeem request 100 mTBILL, greenlist enabled and user in greenlist ', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ greenListableTester,
+ mTokenToUsdDataFeed,
+ accessControl,
+ regularAccounts,
+ dataFeed,
+ } = await loadRvFixture();
+
+ await redemptionVault.setGreenlistEnable(true);
+
+ await greenList(
+ { greenlistable: greenListableTester, accessControl, owner },
+ regularAccounts[0],
+ );
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, regularAccounts[0], 100);
+ await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('instant limit configs are not applied', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ mockedAggregator,
+ } = await loadRvFixture();
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 4);
+
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: hours(12), limit: parseUnits('100') },
+ );
+ await setInstantLimitConfigTest(
+ { vault: redemptionVault, owner },
+ { window: days(1), limit: parseUnits('100') },
+ );
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, regularAccounts[0], 500);
+ await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 500);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 500,
+ { from: regularAccounts[0] },
+ );
+ });
+
+ it('redeem request with 10% growth is applied', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ customFeedGrowth,
+ } = await loadRvFixture();
+
+ await mTokenToUsdDataFeed.changeAggregator(customFeedGrowth.address);
+ await setRoundDataGrowth({ owner, customFeedGrowth }, 1, -1000, 10);
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, regularAccounts[0], 100);
+ await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('redeem request with -10% growth is applied', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ customFeedGrowth,
+ } = await loadRvFixture();
+
+ await mTokenToUsdDataFeed.changeAggregator(customFeedGrowth.address);
+ await setMinGrowthApr({ owner, customFeedGrowth }, -10);
+ await setRoundDataGrowth({ owner, customFeedGrowth }, 1, -1000, -10);
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, regularAccounts[0], 100);
+ await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('redeem request 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ it('redeem request 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$ and token fee 1%', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 100,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ it('redeem request 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$ without checking of minDepositAmount', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 100,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await redemptionVault.freeFromMinAmount(owner.address, true);
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ });
+
+ it('redeem request 100 mTBILL, when price of stable is 1.03$ and mToken price is 5$ and user in waivedFeeRestriction', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 100,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setWaivedFeeAccountTest(
+ { vault: redemptionVault, owner },
+ owner.address,
+ true,
+ );
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ waivedFee: true,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ });
+ it('redeem request 100 mTBILL (custom recipient overload)', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ customRecipient,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, regularAccounts[0], 100);
+ await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('redeem request 100 mTBILL when recipient == msg.sender (custom recipient overload)', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, regularAccounts[0], 100);
+ await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient: regularAccounts[0],
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('redeem request 100 mTBILL when other fn overload is paused (custom recipient overload)', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ customRecipient,
+ } = await loadRvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVault,
+ encodeFnSelector('redeemRequest(address,uint256)'),
+ );
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, regularAccounts[0], 100);
+ await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+
+ it('redeem request 100 mTBILL when other fn overload is paused', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ } = await loadRvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVault,
+ encodeFnSelector(
+ 'redeemRequest(address,uint256,address,uint256,uint256,address)',
+ ),
+ );
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, regularAccounts[0], 100);
+ await approveBase18(regularAccounts[0], mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ },
+ );
+ });
+ });
+
+ describe('approveRequest()', async () => {
+ it('should fail: when request id exceeds maxApproveRequestId', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await setMaxApproveRequestIdTest({ redemptionVault, owner }, 0);
+
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ 1,
+ parseUnits('5'),
+ {
+ revertCustomError: {
+ customErrorName: 'RequestIdTooHigh',
+ args: [1, 0],
+ },
+ },
+ );
+ });
+
+ describe('avgRate=false', async () => {
+ it('should fail: call from address without vault admin role', async () => {
+ const {
+ redemptionVault,
+ regularAccounts,
+ mTokenToUsdDataFeed,
+ mTBILL,
+ } = await loadRvFixture();
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner: regularAccounts[1],
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ 1,
+ parseUnits('1'),
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { owner, redemptionVault, mTBILL, mTokenToUsdDataFeed } =
+ await loadRvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVault,
+ encodeFnSelector('approveRequest(uint256,uint256,bool)'),
+ );
+
+ await approveRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 0,
+ parseUnits('1'),
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('should fail: if some fee = 100%', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 10000,
+ true,
+ );
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await approveRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 0,
+ parseUnits('1'),
+ {
+ revertCustomError: {
+ customErrorName: 'FeeExceedsAmount',
+ },
+ },
+ );
+ });
+
+ it('should fail: request by id not exist', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await approveRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 1,
+ parseUnits('1'),
+ {
+ revertCustomError: {
+ customErrorName: 'RequestNotExists',
+ },
+ },
+ );
+ });
+
+ describe('request addresses access', () => {
+ const setupPendingRedeemRequest = async (
+ fixture: Awaited>,
+ opts?: {
+ customRecipient?: SignerWithAddress;
+ customClaimer?: SignerWithAddress;
+ },
+ ) => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ dataFeed,
+ requestRedeemer,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ } = fixture;
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ mTBILL,
+ redemptionVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData(
+ { mockedAggregator: mockedAggregatorMToken },
+ 5,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient: opts?.customRecipient ?? regularAccounts[0],
+ ...(opts?.customClaimer
+ ? {
+ customClaimer: opts.customClaimer,
+ instantShare: 0,
+ }
+ : {}),
+ },
+ stableCoins.dai,
+ 100,
+ { from: regularAccounts[0] },
+ );
+ };
+
+ it('should fail: approve request when recipient got blacklisted', async () => {
+ const fixture = await loadRvFixture();
+ const {
+ owner,
+ redemptionVault,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ blackListableTester,
+ accessControl,
+ regularAccounts,
+ } = fixture;
+ await setupPendingRedeemRequest(fixture);
+
+ await blackList(
+ { blacklistable: blackListableTester, accessControl, owner },
+ regularAccounts[0],
+ );
+
+ await approveRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 0,
+ parseUnits('5'),
+ { revertCustomError: acErrors.WMAC_BLACKLISTED },
+ );
+ });
+
+ it('should fail: approve request when recipient got ungreenlisted when greenlist enable flag is true', async () => {
+ const fixture = await loadRvFixture();
+ const { owner, redemptionVault, mTBILL, mTokenToUsdDataFeed } =
+ fixture;
+ await setupPendingRedeemRequest(fixture);
+
+ await redemptionVault.setGreenlistEnable(true);
+
+ await approveRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 0,
+ parseUnits('5'),
+ {
+ revertCustomError: {
+ customErrorName: 'NotGreenlisted',
+ },
+ },
+ );
+ });
+
+ it('should fail: approve request when recipient got sanction listed', async () => {
+ const fixture = await loadRvFixture();
+ const {
+ owner,
+ redemptionVault,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ regularAccounts,
+ mockedSanctionsList,
+ } = fixture;
+ await setupPendingRedeemRequest(fixture);
+
+ await sanctionUser(
+ { sanctionsList: mockedSanctionsList },
+ regularAccounts[0],
+ );
+
+ await approveRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 0,
+ parseUnits('5'),
+ {
+ revertCustomError: {
+ customErrorName: 'Sanctioned',
+ },
+ },
+ );
+ });
+ });
+
+ it('should fail: request already processed', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await approveRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ +requestId,
+ parseUnits('1'),
+ );
+ await approveRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ +requestId,
+ parseUnits('1'),
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('approve request from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await approveRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ +requestId,
+ parseUnits('1'),
+ );
+ });
+
+ it('should succeed when other approve entrypoints are paused', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await pauseOtherRedemptionApproveFns(
+ redemptionVault,
+ encodeFnSelector('approveRequest(uint256,uint256,bool)'),
+ );
+
+ await approveRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ +requestId,
+ parseUnits('1'),
+ );
+ });
+
+ it('should approve request in non sequential order when sequentialRequestProcessing is disabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await approveRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 1,
+ parseUnits('1'),
+ );
+ });
+
+ it('should fail: approve request in non sequential order when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: redemptionVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 300);
+ await approveBase18(owner, mTBILL, redemptionVault, 300);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await asyncForEach(
+ Array.from({ length: 3 }, (_, i) => i),
+ async (i) => {
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ },
+ true,
+ );
+
+ await approveRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 0,
+ parseUnits('1'),
+ );
+
+ await approveRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 2,
+ parseUnits('1'),
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidRequestSequence',
+ args: [2, 1],
+ },
+ },
+ );
+ });
+
+ it('should approve request id 0 and then id 1 when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: redemptionVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await approveRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 0,
+ parseUnits('1'),
+ );
+
+ await approveRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 1,
+ parseUnits('1'),
+ );
+ });
+
+ it('should enforce fifo across separate transactions when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: redemptionVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVault, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await asyncForEach(
+ Array.from({ length: 9 }, (_, i) => i),
+ async (i) => {
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ },
+ true,
+ );
+
+ await asyncForEach(
+ [0, 1, 2],
+ async (requestId) => {
+ await approveRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ requestId,
+ parseUnits('1'),
+ );
+ },
+ true,
+ );
+
+ await approveRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 3,
+ parseUnits('1'),
+ );
+
+ await approveRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 5,
+ parseUnits('1'),
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidRequestSequence',
+ args: [5, 4],
+ },
+ },
+ );
+
+ await approveRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 4,
+ parseUnits('1'),
+ );
+
+ await asyncForEach(
+ [6, 7, 8],
+ async (requestId) => {
+ await approveRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ requestId,
+ parseUnits('1'),
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidRequestSequence',
+ args: [requestId, 5],
+ },
+ },
+ );
+ },
+ true,
+ );
+
+ await asyncForEach(
+ [5, 6, 7],
+ async (requestId) => {
+ await approveRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ requestId,
+ parseUnits('1'),
+ );
+ },
+ true,
+ );
+
+ await approveRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 8,
+ parseUnits('1'),
+ );
+ });
+ });
+
+ describe('avgRate=true', async () => {
+ it('should fail: call from address without vault admin role', async () => {
+ const {
+ redemptionVault,
+ regularAccounts,
+ mTokenToUsdDataFeed,
+ mTBILL,
+ } = await loadRvFixture();
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner: regularAccounts[1],
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ 1,
+ parseUnits('1'),
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { owner, redemptionVault, mTBILL, mTokenToUsdDataFeed } =
+ await loadRvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVault,
+ encodeFnSelector('approveRequest(uint256,uint256,bool)'),
+ );
+
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ 0,
+ parseUnits('1'),
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('when instant part is 0', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ 0,
+ parseUnits('5'),
+ );
+ });
+
+ it('should fail: request by id not exist', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ 1,
+ parseUnits('1'),
+ {
+ revertCustomError: {
+ customErrorName: 'RequestNotExists',
+ },
+ },
+ );
+ });
+
+ it('should fail: request already processed', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ +requestId,
+ parseUnits('5'),
+ );
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ +requestId,
+ parseUnits('5'),
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('should fail: when calculated holdback part rate is 0 and instant part is non-zero', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ +requestId,
+ parseUnits('1'),
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidAvgRate',
+ },
+ },
+ );
+ });
+
+ it('when calclulated holdback part rate is 0 and instant part is 0 should use the price passed as newMTokenRate', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ +requestId,
+ parseUnits('1'),
+ );
+ });
+
+ it('should not check for deviation tolerance', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+ await setVariabilityToleranceTest(
+ { vault: redemptionVault, owner },
+ 1,
+ );
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ +requestId,
+ parseUnits('4'),
+ );
+ });
+
+ it('approve request from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ +requestId,
+ parseUnits('5'),
+ );
+ });
+
+ it('should succeed when other approve entrypoints are paused', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await pauseOtherRedemptionApproveFns(
+ redemptionVault,
+ encodeFnSelector('approveRequest(uint256,uint256,bool)'),
+ );
+
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ +requestId,
+ parseUnits('5'),
+ );
+ });
+
+ it('should approve request in non sequential order when sequentialRequestProcessing is disabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ 1,
+ parseUnits('5'),
+ );
+ });
+
+ it('should fail: approve request in non sequential order when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: redemptionVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 300);
+ await approveBase18(owner, mTBILL, redemptionVault, 300);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await asyncForEach(
+ Array.from({ length: 3 }, (_, i) => i),
+ async (i) => {
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ },
+ true,
+ );
+
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ 0,
+ parseUnits('5'),
+ );
+
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ 2,
+ parseUnits('5'),
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidRequestSequence',
+ args: [2, 1],
+ },
+ },
+ );
+ });
+
+ it('should approve request id 0 and then id 1 when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: redemptionVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ 0,
+ parseUnits('5'),
+ );
+
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ 1,
+ parseUnits('5'),
+ );
+ });
+ });
+ });
+
+ describe('safeBulkApproveRequestAtSavedRate()', async () => {
+ it('should fail: call from address without vault admin role', async () => {
+ const {
+ redemptionVault,
+ regularAccounts,
+ mTokenToUsdDataFeed,
+ mTBILL,
+ } = await loadRvFixture();
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner: regularAccounts[1],
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ [{ id: 1 }],
+ 'request-rate',
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('should fail: request by id not exist', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }],
+ 'request-rate',
+ {
+ revertCustomError: {
+ customErrorName: 'RequestNotExists',
+ },
+ },
+ );
+ });
+
+ it('should skip requests above maxApproveRequestId without reverting', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await setMaxApproveRequestIdTest({ redemptionVault, owner }, 0);
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 0 }, { id: 1, expectedToExecute: false }],
+ 'request-rate',
+ );
+ });
+
+ it('should fail: request already processed', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.001);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ 'request-rate',
+ );
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ 'request-rate',
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { owner, redemptionVault, mTBILL, mTokenToUsdDataFeed } =
+ await loadRvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVault,
+ encodeFnSelector('safeBulkApproveRequestAtSavedRate(uint256[])'),
+ );
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 0 }],
+ 'request-rate',
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('should fail: process multiple requests, when one of them already precessed', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.001);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ 0,
+ parseUnits('5.000001'),
+ );
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }, { id: 0 }],
+ 'request-rate',
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('should fail: process multiple requests, when couple of them have equal id', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.001);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ 0,
+ parseUnits('5.000001'),
+ );
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }, { id: 1 }],
+ 'request-rate',
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('approve 1 request from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ 'request-rate',
+ );
+ });
+
+ it('approve 2 request from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }, { id: 0 }],
+ 'request-rate',
+ );
+ });
+
+ it('approve 10 request from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ regularAccounts,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVault, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await asyncForEach(
+ Array.from({ length: 10 }, (_, i) => i),
+ async (i) => {
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient: regularAccounts[i],
+ },
+ stableCoins.dai,
+ 100,
+ );
+ },
+ true,
+ );
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ Array.from({ length: 10 }, (_, i) => ({ id: i })),
+ 'request-rate',
+ );
+ });
+
+ it('approve 1 request when there is not enough liquidity', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId, expectedToExecute: false }],
+ 'request-rate',
+ );
+ });
+
+ it('should fail: when token fee is not 0, user is not fee waived and request redeemer do not have enough tokens to transfer the fee', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ regularAccounts,
+ } = await loadRvFixture();
+
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 100,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 0,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 0, expectedToExecute: false }],
+ 'request-rate',
+ );
+ });
+
+ it('approve 2 request when there is enough liquidity only for first one', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 600);
+
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 600,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [
+ { id: 0, expectedToExecute: true },
+ { id: 1, expectedToExecute: false },
+ ],
+ 'request-rate',
+ );
+ });
+
+ it('approve 2 requests both with different payment tokens', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.usdc, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.usdc,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.usdc,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }, { id: 0 }],
+ 'request-rate',
+ );
+ });
+
+ it('approve 2 requests both with different payment tokens when there is not enough liquidity for first one', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.usdc, requestRedeemer, 100000);
+
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.usdc,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.usdc,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }, { id: 0, expectedToExecute: false }],
+ 'request-rate',
+ );
+ });
+
+ it('should succeed when other approve entrypoints are paused', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await pauseOtherRedemptionApproveFns(
+ redemptionVault,
+ encodeFnSelector('safeBulkApproveRequestAtSavedRate(uint256[])'),
+ );
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ 'request-rate',
+ );
+ });
+
+ it('should approve requests in non sequential order when sequentialRequestProcessing is disabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }, { id: 0 }],
+ 'request-rate',
+ );
+ });
+
+ it('should skip out-of-order bulk approvals without reverting when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: redemptionVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [
+ { id: 1, expectedToExecute: false },
+ { id: 0, expectedToExecute: true },
+ ],
+ 'request-rate',
+ );
+ });
+
+ it('should approve requests in sequential order when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: redemptionVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 300);
+ await approveBase18(owner, mTBILL, redemptionVault, 300);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await asyncForEach(
+ Array.from({ length: 3 }, (_, i) => i),
+ async () => {
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ },
+ true,
+ );
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 0 }, { id: 1 }, { id: 2 }],
+ 'request-rate',
+ );
+ });
+
+ it('should not approve requests after insufficient liquidity when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: redemptionVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, requestRedeemer, 600);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 600,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [
+ { id: 0, expectedToExecute: true },
+ { id: 1, expectedToExecute: false },
+ ],
+ 'request-rate',
+ );
+ });
+ });
+
+ describe('safeBulkApproveRequest() (custom price overload)', async () => {
+ it('should fail: call from address without vault admin role', async () => {
+ const {
+ redemptionVault,
+ regularAccounts,
+ mTokenToUsdDataFeed,
+ mTBILL,
+ } = await loadRvFixture();
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner: regularAccounts[1],
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ [{ id: 1 }],
+ parseUnits('1'),
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { owner, redemptionVault, mTBILL, mTokenToUsdDataFeed } =
+ await loadRvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVault,
+ encodeFnSelector('safeBulkApproveRequest(uint256[],uint256)'),
+ );
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 0 }],
+ parseUnits('1'),
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('should fail: request by id not exist', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }],
+ parseUnits('1'),
+ {
+ revertCustomError: {
+ customErrorName: 'RequestNotExists',
+ },
+ },
+ );
+ });
+
+ it('should skip requests above maxApproveRequestId without reverting', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await setMaxApproveRequestIdTest({ redemptionVault, owner }, 0);
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 0 }, { id: 1, expectedToExecute: false }],
+ parseUnits('5'),
+ );
+ });
+
+ it('should fail: if new rate greater then variabilityTolerance', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.001);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ parseUnits('6'),
+ {
+ revertCustomError: {
+ customErrorName: 'PriceVariationExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: if new rate lower then variabilityTolerance', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.001);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ parseUnits('4'),
+ {
+ revertCustomError: {
+ customErrorName: 'PriceVariationExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: request already processed', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.001);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ parseUnits('5.000001'),
+ );
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ parseUnits('5.00001'),
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('should fail: process multiple requests, when one of them already precessed', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.001);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ 0,
+ parseUnits('5.000001'),
+ );
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }, { id: 0 }],
+ parseUnits('5.00001'),
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('should fail: process multiple requests, when couple of them have equal id', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.001);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ 0,
+ parseUnits('5.000001'),
+ );
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }, { id: 1 }],
+ parseUnits('5.00001'),
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('approve 1 request from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ parseUnits('5.000001'),
+ );
+ });
+
+ it('approve 2 request from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }, { id: 0 }],
+ parseUnits('5.000001'),
+ );
+ });
+
+ it('approve 10 request from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ regularAccounts,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVault, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await asyncForEach(
+ Array.from({ length: 10 }, (_, i) => i),
+ async (i) => {
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient: regularAccounts[i],
+ },
+ stableCoins.dai,
+ 100,
+ );
+ },
+ true,
+ );
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ Array.from({ length: 10 }, (_, i) => ({ id: i })),
+ parseUnits('5.000001'),
+ );
+ });
+
+ it('approve 1 request when there is not enough liquidity', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId, expectedToExecute: false }],
+ parseUnits('5.000001'),
+ );
+ });
+
+ it('approve 2 request when there is enough liquidity only for first one', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 600);
+
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 600,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [
+ { id: 0, expectedToExecute: true },
+ { id: 1, expectedToExecute: false },
+ ],
+ parseUnits('5.000001'),
+ );
+ });
+
+ it('approve 2 requests both with different payment tokens', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.usdc, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.usdc,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.usdc,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }, { id: 0 }],
+ parseUnits('5.000001'),
+ );
+ });
+
+ it('approve 2 requests both with different payment tokens when there is not enough liquidity for first one', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.usdc, requestRedeemer, 100000);
+
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.usdc,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.usdc,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }, { id: 0, expectedToExecute: false }],
+ parseUnits('5.000001'),
+ );
+ });
+
+ it('should succeed when other approve entrypoints are paused', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await pauseOtherRedemptionApproveFns(
+ redemptionVault,
+ encodeFnSelector('safeBulkApproveRequest(uint256[],uint256)'),
+ );
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ parseUnits('5.000001'),
+ );
+ });
+
+ it('should approve requests in non sequential order when sequentialRequestProcessing is disabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }, { id: 0 }],
+ parseUnits('5.000001'),
+ );
+ });
+
+ it('should skip out-of-order bulk approvals without reverting when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: redemptionVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [
+ { id: 1, expectedToExecute: false },
+ { id: 0, expectedToExecute: true },
+ ],
+ parseUnits('5.000001'),
+ );
+ });
+
+ it('should approve requests in sequential order when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: redemptionVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 300);
+ await approveBase18(owner, mTBILL, redemptionVault, 300);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await asyncForEach(
+ Array.from({ length: 3 }, (_, i) => i),
+ async (i) => {
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ },
+ true,
+ );
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 0 }, { id: 1 }, { id: 2 }],
+ parseUnits('5.000001'),
+ );
+ });
+
+ it('should not approve requests after insufficient liquidity when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: redemptionVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, requestRedeemer, 600);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 600,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [
+ { id: 0, expectedToExecute: true },
+ { id: 1, expectedToExecute: false },
+ ],
+ parseUnits('5.000001'),
+ );
+ });
+ });
+
+ describe('safeBulkApproveRequest() (current price overload)', async () => {
+ it('should fail: call from address without vault admin role', async () => {
+ const {
+ redemptionVault,
+ regularAccounts,
+ mTokenToUsdDataFeed,
+ mTBILL,
+ } = await loadRvFixture();
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner: regularAccounts[1],
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ [{ id: 1 }],
+ undefined,
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { owner, redemptionVault, mTBILL, mTokenToUsdDataFeed } =
+ await loadRvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVault,
+ encodeFnSelector('safeBulkApproveRequest(uint256[])'),
+ );
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 0 }],
+ undefined,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('should fail: request by id not exist', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }],
+ undefined,
+ {
+ revertCustomError: {
+ customErrorName: 'RequestNotExists',
+ },
+ },
+ );
+ });
+
+ it('should skip requests above maxApproveRequestId without reverting', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await setMaxApproveRequestIdTest({ redemptionVault, owner }, 0);
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 0 }, { id: 1, expectedToExecute: false }],
+ undefined,
+ );
+ });
+
+ it('should fail: if new rate greater then variabilityTolerance', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.001);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 6);
+
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ undefined,
+ {
+ revertCustomError: {
+ customErrorName: 'PriceVariationExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: if new rate lower then variabilityTolerance', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.001);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 4);
+
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ undefined,
+ {
+ revertCustomError: {
+ customErrorName: 'PriceVariationExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: request already processed', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.001);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ undefined,
+ );
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ undefined,
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('should fail: process multiple requests, when one of them already precessed', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.001);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ 0,
+ parseUnits('5.000001'),
+ );
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }, { id: 0 }],
+ undefined,
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('should fail: process multiple requests, when couple of them have equal id', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.001);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ 0,
+ parseUnits('5.000001'),
+ );
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }, { id: 1 }],
+ undefined,
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('approve 1 request from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ undefined,
+ );
+ });
+
+ it('approve 1 request from vaut admin account when 10% growth is applied', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ customFeedGrowth,
+ } = await loadRvFixture();
+
+ await mTokenToUsdDataFeed.changeAggregator(customFeedGrowth.address);
+ await setRoundDataGrowth({ owner, customFeedGrowth }, 1, -1000, 10);
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ undefined,
+ );
+ });
+
+ it('approve 1 request from vaut admin account when -10% growth is applied', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ customFeedGrowth,
+ } = await loadRvFixture();
+
+ await mTokenToUsdDataFeed.changeAggregator(customFeedGrowth.address);
+ await setMinGrowthApr({ owner, customFeedGrowth }, -10);
+ await setRoundDataGrowth({ owner, customFeedGrowth }, 1, -1000, -10);
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ undefined,
+ );
+ });
+
+ it('approve 2 request from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }, { id: 0 }],
+ undefined,
+ );
+ });
+
+ it('approve 10 request from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ regularAccounts,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVault, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await asyncForEach(
+ Array.from({ length: 10 }, (_, i) => i),
+ async (i) => {
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient: regularAccounts[i],
+ },
+ stableCoins.dai,
+ 100,
+ );
+ },
+ true,
+ );
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ Array.from({ length: 10 }, (_, i) => ({ id: i })),
+ undefined,
+ );
+ });
+
+ it('approve 1 request when there is not enough liquidity', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId, expectedToExecute: false }],
+ undefined,
+ );
+ });
+
+ it('approve 2 request when there is enough liquidity only for first one', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 600);
+
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 600,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [
+ { id: 0, expectedToExecute: true },
+ { id: 1, expectedToExecute: false },
+ ],
+ undefined,
+ );
+ });
+
+ it('approve 2 requests both with different payment tokens', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.usdc, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.usdc,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.usdc,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }, { id: 0 }],
+ undefined,
+ );
+ });
+
+ it('approve 2 requests both with different payment tokens when there is not enough liquidity for first one', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.usdc, requestRedeemer, 100000);
+
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.usdc,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.usdc,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }, { id: 0, expectedToExecute: false }],
+ undefined,
+ );
+ });
+
+ it('should succeed when other approve entrypoints are paused', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await pauseOtherRedemptionApproveFns(
+ redemptionVault,
+ encodeFnSelector('safeBulkApproveRequest(uint256[])'),
+ );
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ undefined,
+ );
+ });
+
+ it('should approve requests in non sequential order when sequentialRequestProcessing is disabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 1 }, { id: 0 }],
+ undefined,
+ );
+ });
+
+ it('should skip out-of-order bulk approvals without reverting when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: redemptionVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [
+ { id: 1, expectedToExecute: false },
+ { id: 0, expectedToExecute: true },
+ ],
+ undefined,
+ );
+ });
+
+ it('should approve requests in sequential order when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: redemptionVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 300);
+ await approveBase18(owner, mTBILL, redemptionVault, 300);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await asyncForEach(
+ Array.from({ length: 3 }, (_, i) => i),
+ async (i) => {
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ },
+ true,
+ );
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: 0 }, { id: 1 }, { id: 2 }],
+ undefined,
+ );
+ });
+
+ it('should not approve requests after insufficient liquidity when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: redemptionVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, requestRedeemer, 600);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 600,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [
+ { id: 0, expectedToExecute: true },
+ { id: 1, expectedToExecute: false },
+ ],
+ undefined,
+ );
+ });
+ });
+
+ describe('safeBulkApproveRequestAvgRate() (custom price overload)', async () => {
+ it('should fail: call from address without vault admin role', async () => {
+ const {
+ redemptionVault,
+ regularAccounts,
+ mTokenToUsdDataFeed,
+ mTBILL,
+ } = await loadRvFixture();
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner: regularAccounts[1],
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }],
+ parseUnits('1'),
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { owner, redemptionVault, mTBILL, mTokenToUsdDataFeed } =
+ await loadRvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVault,
+ encodeFnSelector(
+ 'safeBulkApproveRequestAvgRate(uint256[],uint256)',
+ ),
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 0 }],
+ parseUnits('1'),
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('should fail: request by id not exist', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }],
+ parseUnits('1'),
+ {
+ revertCustomError: {
+ customErrorName: 'RequestNotExists',
+ },
+ },
+ );
+ });
+
+ it('should skip requests above maxApproveRequestId without reverting', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await setMaxApproveRequestIdTest({ redemptionVault, owner }, 0);
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 0 }, { id: 1, expectedToExecute: false }],
+ parseUnits('5'),
+ );
+ });
+
+ it('should fail: if new rate greater then variabilityTolerance', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.001);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: requestId }],
+ parseUnits('6'),
+ {
+ revertCustomError: {
+ customErrorName: 'PriceVariationExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: if new rate lower then variabilityTolerance', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.001);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: requestId }],
+ parseUnits('4'),
+ {
+ revertCustomError: {
+ customErrorName: 'PriceVariationExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: request already processed', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.001);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ [{ id: requestId }],
+ parseUnits('5.000001'),
+ );
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: requestId }],
+ parseUnits('5.00001'),
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('should fail: process multiple requests, when one of them already precessed', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.001);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ 0,
+ parseUnits('5.000001'),
+ );
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }, { id: 0 }],
+ parseUnits('5.00001'),
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('should fail: process multiple requests, when couple of them have equal id', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.001);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ 0,
+ parseUnits('5.000001'),
+ );
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }, { id: 1 }],
+ parseUnits('5.00001'),
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('process multiple requests, when one of them does not have instant part', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.001);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }, { id: 0 }],
+ parseUnits('5.00001'),
+ );
+ });
+
+ it('approve 1 request from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: requestId }],
+ parseUnits('5.000001'),
+ );
+ });
+
+ it('approve 2 request from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }, { id: 0 }],
+ parseUnits('5.000001'),
+ );
+ });
+
+ it('approve 10 request from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ regularAccounts,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVault, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await asyncForEach(
+ Array.from({ length: 10 }, (_, i) => i),
+ async (i) => {
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient: regularAccounts[i],
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ },
+ true,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ Array.from({ length: 10 }, (_, i) => ({ id: i })),
+ parseUnits('5.000001'),
+ );
+ });
+
+ it('approve 1 request when there is not enough liquidity', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: requestId, expectedToExecute: false }],
+ parseUnits('5.000001'),
+ );
+ });
+
+ it('approve 2 request when there is enough liquidity only for first one', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 300);
+ await mintToken(stableCoins.dai, redemptionVault, 600);
+
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 600,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [
+ { id: 0, expectedToExecute: true },
+ { id: 1, expectedToExecute: false },
+ ],
+ parseUnits('5.000001'),
+ );
+ });
+
+ it('approve 2 requests both with different payment tokens', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.usdc, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(stableCoins.usdc, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.usdc,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.usdc,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }, { id: 0 }],
+ parseUnits('5.000001'),
+ );
+ });
+
+ it('approve 2 requests both with different payment tokens when there is not enough liquidity for first one', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.usdc, requestRedeemer, 100000);
+ await mintToken(stableCoins.usdc, redemptionVault, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.usdc,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.usdc,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }, { id: 0, expectedToExecute: false }],
+ parseUnits('5.000001'),
+ );
+ });
+
+ it('should succeed when other approve entrypoints are paused', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await pauseOtherRedemptionApproveFns(
+ redemptionVault,
+ encodeFnSelector(
+ 'safeBulkApproveRequestAvgRate(uint256[],uint256)',
+ ),
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: requestId }],
+ parseUnits('5.000001'),
+ );
+ });
+
+ it('should approve requests in non sequential order when sequentialRequestProcessing is disabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }, { id: 0 }],
+ parseUnits('5'),
+ );
+ });
+
+ it('should skip out-of-order bulk approvals without reverting when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: redemptionVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [
+ { id: 1, expectedToExecute: false },
+ { id: 0, expectedToExecute: true },
+ ],
+ parseUnits('5'),
+ );
+ });
+
+ it('should approve requests in sequential order when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: redemptionVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 300);
+ await approveBase18(owner, mTBILL, redemptionVault, 300);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await asyncForEach(
+ Array.from({ length: 3 }, (_, i) => i),
+ async (i) => {
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ },
+ true,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 0 }, { id: 1 }, { id: 2 }],
+ parseUnits('5'),
+ );
+ });
+
+ it('should not approve requests after insufficient liquidity when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: redemptionVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, requestRedeemer, 300);
+ await mintToken(stableCoins.dai, redemptionVault, 600);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 600,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [
+ { id: 0, expectedToExecute: true },
+ { id: 1, expectedToExecute: false },
+ ],
+ parseUnits('5'),
+ );
+ });
+ });
+
+ describe('safeBulkApproveRequestAvgRate() (current price overload)', async () => {
+ it('should fail: call from address without vault admin role', async () => {
+ const {
+ redemptionVault,
+ regularAccounts,
+ mTokenToUsdDataFeed,
+ mTBILL,
+ } = await loadRvFixture();
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner: regularAccounts[1],
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }],
+ undefined,
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { owner, redemptionVault, mTBILL, mTokenToUsdDataFeed } =
+ await loadRvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVault,
+ encodeFnSelector('safeBulkApproveRequestAvgRate(uint256[])'),
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 0 }],
+ undefined,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('should fail: request by id not exist', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }],
+ undefined,
+ {
+ revertCustomError: {
+ customErrorName: 'RequestNotExists',
+ },
+ },
+ );
+ });
+
+ it('should skip requests above maxApproveRequestId without reverting', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await setMaxApproveRequestIdTest({ redemptionVault, owner }, 0);
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 0 }, { id: 1, expectedToExecute: false }],
+ undefined,
+ );
+ });
+
+ it('should fail: if new rate greater then variabilityTolerance', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.001);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 6);
+
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: requestId }],
+ undefined,
+ {
+ revertCustomError: {
+ customErrorName: 'PriceVariationExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: if new rate lower then variabilityTolerance', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.001);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 4);
+
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: requestId }],
+ undefined,
+ {
+ revertCustomError: {
+ customErrorName: 'PriceVariationExceeded',
+ },
+ },
+ );
+ });
+
+ it('should fail: request already processed', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.001);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: requestId }],
+ undefined,
+ );
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: requestId }],
+ undefined,
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('should fail: process multiple requests, when one of them already precessed', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.001);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ 0,
+ parseUnits('5.000001'),
+ );
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }, { id: 0 }],
+ undefined,
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('should fail: process multiple requests, when couple of them have equal id', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.001);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ 0,
+ parseUnits('5.000001'),
+ );
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }, { id: 1 }],
+ undefined,
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('when one of the requests instant part is 0', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.001);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }, { id: 0 }],
+ undefined,
+ );
+ });
+
+ it('approve 1 request from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: requestId }],
+ undefined,
+ );
+ });
+
+ it('approve 1 request from vaut admin account when 10% growth is applied', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ customFeedGrowth,
+ } = await loadRvFixture();
+
+ await mTokenToUsdDataFeed.changeAggregator(customFeedGrowth.address);
+ await setRoundDataGrowth({ owner, customFeedGrowth }, 1, -1000, 10);
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: requestId }],
+ undefined,
+ );
+ });
+
+ it('approve 1 request from vaut admin account when -10% growth is applied', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ customFeedGrowth,
+ } = await loadRvFixture();
+
+ await mTokenToUsdDataFeed.changeAggregator(customFeedGrowth.address);
+ await setMinGrowthApr({ owner, customFeedGrowth }, -10);
+ await setRoundDataGrowth({ owner, customFeedGrowth }, 1, -1000, -10);
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: requestId }],
+ undefined,
+ );
+ });
+
+ it('approve 2 request from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }, { id: 0 }],
+ undefined,
+ );
+ });
+
+ it('approve 10 request from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ regularAccounts,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 1000);
+ await approveBase18(owner, mTBILL, redemptionVault, 1000);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await asyncForEach(
+ Array.from({ length: 10 }, (_, i) => i),
+ async (i) => {
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ customRecipient: regularAccounts[i],
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ },
+ true,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ Array.from({ length: 10 }, (_, i) => ({ id: i })),
+ undefined,
+ );
+ });
+
+ it('approve 1 request when there is not enough liquidity', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: requestId, expectedToExecute: false }],
+ undefined,
+ );
+ });
+
+ it('approve 2 request when there is enough liquidity only for first one', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 300);
+ await mintToken(stableCoins.dai, redemptionVault, 1000);
+
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 600,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [
+ { id: 0, expectedToExecute: true },
+ { id: 1, expectedToExecute: false },
+ ],
+ undefined,
+ );
+ });
+
+ it('approve 2 requests both with different payment tokens', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.usdc, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(stableCoins.usdc, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.usdc,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.usdc,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }, { id: 0 }],
+ undefined,
+ );
+ });
+
+ it('approve 2 requests both with different payment tokens when there is not enough liquidity for first one', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.usdc, requestRedeemer, 100000);
+ await mintToken(stableCoins.usdc, redemptionVault, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.usdc,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.usdc,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.usdc,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }, { id: 0, expectedToExecute: false }],
+ undefined,
+ );
+ });
+
+ it('should succeed when other approve entrypoints are paused', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await pauseOtherRedemptionApproveFns(
+ redemptionVault,
+ encodeFnSelector('safeBulkApproveRequestAvgRate(uint256[])'),
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: requestId }],
+ undefined,
+ );
+ });
+
+ it('should approve requests in non sequential order when sequentialRequestProcessing is disabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 1 }, { id: 0 }],
+ undefined,
+ );
+ });
+
+ it('should skip out-of-order bulk approvals without reverting when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: redemptionVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [
+ { id: 1, expectedToExecute: false },
+ { id: 0, expectedToExecute: true },
+ ],
+ undefined,
+ );
+ });
+
+ it('should approve requests in sequential order when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: redemptionVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await mintToken(mTBILL, owner, 300);
+ await approveBase18(owner, mTBILL, redemptionVault, 300);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await asyncForEach(
+ Array.from({ length: 3 }, (_, i) => i),
+ async (i) => {
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ },
+ true,
+ );
+
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [{ id: 0 }, { id: 1 }, { id: 2 }],
+ undefined,
+ );
+ });
+
+ it('should not approve requests after insufficient liquidity when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: redemptionVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, requestRedeemer, 300);
+ await mintToken(stableCoins.dai, redemptionVault, 600);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 600,
+ );
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ await redeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ instantShare: 50_00,
+ },
+ stableCoins.dai,
+ 100,
+ );
+ await safeBulkApproveRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ isAvgRate: true,
+ },
+ [
+ { id: 0, expectedToExecute: true },
+ { id: 1, expectedToExecute: false },
+ ],
+ undefined,
+ );
+ });
+ });
+
+ describe('rejectRequest()', async () => {
+ it('should fail: call from address without vault admin role', async () => {
+ const {
+ redemptionVault,
+ regularAccounts,
+ mTokenToUsdDataFeed,
+ mTBILL,
+ } = await loadRvFixture();
+ await rejectRedeemRequestTest(
+ {
+ redemptionVault,
+ owner: regularAccounts[1],
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ 1,
+ {
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('should fail: when function is paused', async () => {
+ const { owner, redemptionVault, mTokenToUsdDataFeed, mTBILL } =
+ await loadRvFixture();
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVault,
+ encodeFnSelector('rejectRequest(uint256)'),
+ );
+
+ await rejectRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 0,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('should fail: request by id not exist', async () => {
+ const {
+ owner,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await rejectRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 1,
+ {
+ revertCustomError: {
+ customErrorName: 'RequestNotExists',
+ },
+ },
+ );
+ });
+
+ it('should fail: request already processed', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.001);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await rejectRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ +requestId,
+ );
+ await rejectRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ +requestId,
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('safe approve request from vaut admin account', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ dataFeed,
+ } = await loadRvFixture();
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, mTBILL, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ const requestId = 0;
+
+ await rejectRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ +requestId,
+ );
+ });
+
+ it('should reject request id 0 and then id 1 when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: redemptionVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, owner, 200);
+ await approveBase18(owner, mTBILL, redemptionVault, 200);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+
+ await rejectRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 0,
+ );
+
+ await rejectRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 1,
+ );
+ });
+
+ it('should fail: reject request id in non sequential order when sequentialRequestProcessing is enabled', async () => {
+ const {
+ owner,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ redemptionVault,
+ stableCoins,
+ mTBILL,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+
+ await setSequentialRequestProcessingTest(
+ { vault: redemptionVault, owner },
+ true,
+ );
+
+ await mintToken(stableCoins.dai, redemptionVault, 100000);
+ await mintToken(mTBILL, owner, 300);
+ await approveBase18(owner, mTBILL, redemptionVault, 300);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1.03);
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 5);
+
+ await asyncForEach(
+ Array.from({ length: 3 }, (_, i) => i),
+ async (i) => {
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ );
+ },
+ true,
+ );
+
+ await rejectRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ 2,
+ {
+ revertCustomError: {
+ customErrorName: 'InvalidRequestSequence',
+ args: [2, 0],
+ },
+ },
+ );
+ });
+ });
+
+ describe('redeemRequest() complex', () => {
+ it('should fail: when is paused', async () => {
+ const {
+ redemptionVault,
+ owner,
+ mTBILL,
+ stableCoins,
+ regularAccounts,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+
+ await pauseVault({ pauseManager, owner }, redemptionVault);
+ await mintToken(stableCoins.dai, redemptionVault, 100);
+ await mintToken(mTBILL, regularAccounts[0], 100);
+ await approveBase18(
+ regularAccounts[0],
+ stableCoins.dai,
+ redemptionVault,
+ 100,
+ );
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ from: regularAccounts[0],
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('is on pause, but admin can use everything', async () => {
+ const {
+ redemptionVault,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+
+ await pauseVault({ pauseManager, owner }, redemptionVault);
+
+ await mintToken(stableCoins.dai, redemptionVault, 1000);
+ await mintToken(mTBILL, owner, 100);
+ await approveBase18(owner, stableCoins.dai, redemptionVault, 100);
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('call for amount == minAmount, then approve', async () => {
+ const {
+ redemptionVault,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+
+ await mintToken(mTBILL, owner, 100_000);
+ await mintToken(stableCoins.dai, requestRedeemer, 100000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 100000,
+ );
+ await approveBase18(owner, mTBILL, redemptionVault, 100_000);
+
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setMinAmountTest({ vault: redemptionVault, owner }, 100_000);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100_000,
+ );
+
+ const requestId = 0;
+
+ await approveRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ +requestId,
+ parseUnits('1'),
+ );
+ });
+
+ it('call for amount == minAmount, then safe approve', async () => {
+ const {
+ redemptionVault,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ requestRedeemer,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+
+ await mintToken(mTBILL, owner, 100_000);
+ await mintToken(stableCoins.dai, requestRedeemer, 1000000);
+ await approveBase18(
+ requestRedeemer,
+ stableCoins.dai,
+ redemptionVault,
+ 1000000,
+ );
+ await approveBase18(owner, mTBILL, redemptionVault, 100_000);
+
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setMinAmountTest({ vault: redemptionVault, owner }, 100_000);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100_000,
+ );
+
+ const requestId = 0;
+
+ await approveRedeemRequestTest(
+ {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenToUsdDataFeed,
+ },
+ +requestId,
+ parseUnits('1.000001'),
+ );
+ });
+
+ it('call for amount == minAmount, then reject', async () => {
+ const {
+ redemptionVault,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ owner,
+ mTBILL,
+ stableCoins,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ } = await loadRvFixture();
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator }, 1);
+
+ await mintToken(mTBILL, owner, 100_000);
+ await mintToken(stableCoins.dai, redemptionVault, 100_000);
+ await approveBase18(owner, mTBILL, redemptionVault, 100_000);
+
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setMinAmountTest({ vault: redemptionVault, owner }, 100_000);
+
+ await redeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoins.dai,
+ 100_000,
+ );
+
+ const requestId = 0;
+
+ await rejectRedeemRequestTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ +requestId,
+ );
+ });
+ });
+
+ describe('loan operations', () => {
+ const prepareTest = async (
+ fixture: DefaultFixture,
+ stableCoin: ERC20Mock,
+ setupVault = true,
+ ) => {
+ const {
+ redemptionVault,
+ owner,
+ mTBILL,
+ mTokenLoan,
+ loanLp,
+ redemptionVaultLoanSwapper,
+ dataFeed,
+ mTokenToUsdDataFeed,
+ mockedAggregator,
+ mockedAggregatorMToken,
+ } = fixture;
+
+ await mintToken(mTBILL, owner, 100);
+ await mintToken(mTokenLoan, loanLp, 1000);
+ await approveBase18(loanLp, mTokenLoan, redemptionVault, 1000);
+ await mintToken(stableCoin, redemptionVaultLoanSwapper, 1000);
+
+ await approveBase18(loanLp, stableCoin, redemptionVault, 1000);
+
+ if (setupVault) {
+ await withdrawTest(
+ { vault: redemptionVault, owner },
+ stableCoin,
+ await stableCoin.balanceOf(redemptionVault.address),
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoin,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await addPaymentTokenTest(
+ { vault: redemptionVaultLoanSwapper, owner },
+ stableCoin,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+ await setRoundData({ mockedAggregator }, 1);
+ }
+
+ await redeemInstantTest(
+ { redemptionVault, owner, mTBILL, mTokenToUsdDataFeed },
+ stableCoin,
+ 100,
+ );
+ };
+ describe('bulkRepayLpLoanRequest()', () => {
+ it('should fail: when function is paused', async () => {
+ const fixture = await loadRvFixture();
+ const { redemptionVault, owner, mTBILL } = fixture;
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVault,
+ encodeFnSelector('bulkRepayLpLoanRequest(uint256[])'),
+ );
+
+ await bulkRepayLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ [{ id: 0 }],
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('approve 1 request', async () => {
+ const fixture = await loadRvFixture();
+ const {
+ redemptionVault,
+ owner,
+ mTBILL,
+ loanRepaymentAddress,
+ stableCoins,
+ } = fixture;
+
+ await prepareTest(fixture, stableCoins.dai);
+
+ await mintToken(stableCoins.dai, loanRepaymentAddress, 100);
+
+ await approveBase18(
+ loanRepaymentAddress,
+ stableCoins.dai,
+ redemptionVault,
+ 100,
+ );
+
+ await bulkRepayLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ [{ id: 0 }],
+ );
+ });
+
+ it('approve 2 request with same token out', async () => {
+ const fixture = await loadRvFixture();
+ const {
+ redemptionVault,
+ owner,
+ mTBILL,
+ loanRepaymentAddress,
+ stableCoins,
+ } = fixture;
+
+ await prepareTest(fixture, stableCoins.dai);
+ await prepareTest(fixture, stableCoins.dai, false);
+
+ await mintToken(stableCoins.dai, loanRepaymentAddress, 200);
+
+ await approveBase18(
+ loanRepaymentAddress,
+ stableCoins.dai,
+ redemptionVault,
+ 200,
+ );
+
+ await bulkRepayLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ [{ id: 0 }, { id: 1 }],
+ );
+ });
+
+ it('approve 2 request with different token out', async () => {
+ const fixture = await loadRvFixture();
+ const {
+ redemptionVault,
+ owner,
+ mTBILL,
+ loanRepaymentAddress,
+ stableCoins,
+ } = fixture;
+
+ await prepareTest(fixture, stableCoins.dai);
+ await prepareTest(fixture, stableCoins.usdc);
+
+ await mintToken(stableCoins.dai, loanRepaymentAddress, 200);
+ await mintToken(stableCoins.usdc, loanRepaymentAddress, 200);
+
+ await approveBase18(
+ loanRepaymentAddress,
+ stableCoins.dai,
+ redemptionVault,
+ 100,
+ );
+
+ await approveBase18(
+ loanRepaymentAddress,
+ stableCoins.usdc,
+ redemptionVault,
+ 100,
+ );
+
+ await bulkRepayLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ [{ id: 0 }, { id: 1 }],
+ );
+ });
+
+ it('should fail: when loan repayment address does not have enough balance', async () => {
+ const fixture = await loadRvFixture();
+ const {
+ redemptionVault,
+ owner,
+ mTBILL,
+ loanRepaymentAddress,
+ stableCoins,
+ } = fixture;
+
+ await prepareTest(fixture, stableCoins.dai);
+
+ await approveBase18(
+ loanRepaymentAddress,
+ stableCoins.dai,
+ redemptionVault,
+ 100,
+ );
+
+ await bulkRepayLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ [{ id: 0 }],
+ {
+ revertMessage: 'ERC20: transfer amount exceeds balance',
+ },
+ );
+ });
+
+ it('should fail: when loan repayment address does not have enough allowance', async () => {
+ const fixture = await loadRvFixture();
+ const {
+ redemptionVault,
+ owner,
+ mTBILL,
+ loanRepaymentAddress,
+ stableCoins,
+ } = fixture;
+
+ await prepareTest(fixture, stableCoins.dai);
+
+ await mintToken(stableCoins.dai, loanRepaymentAddress, 100);
+
+ await bulkRepayLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ [{ id: 0 }],
+ {
+ revertMessage: 'ERC20: insufficient allowance',
+ },
+ );
+ });
+
+ it('should fail: when loan repayment address have balance for lp transfer but not enough for fee transfer', async () => {
+ const fixture = await loadRvFixture();
+ const {
+ redemptionVault,
+ owner,
+ mTBILL,
+ loanRepaymentAddress,
+ stableCoins,
+ } = fixture;
+
+ await setInstantFeeTest({ vault: redemptionVault, owner }, 100);
+ await prepareTest(fixture, stableCoins.dai);
+
+ await mintToken(stableCoins.dai, loanRepaymentAddress, 99);
+ await approveBase18(
+ loanRepaymentAddress,
+ stableCoins.dai,
+ redemptionVault,
+ 100,
+ );
+
+ await bulkRepayLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ [{ id: 0 }],
+ {
+ revertMessage: 'ERC20: transfer amount exceeds balance',
+ },
+ );
+ });
+
+ it('should fail: request was already approved', async () => {
+ const fixture = await loadRvFixture();
+ const {
+ redemptionVault,
+ owner,
+ mTBILL,
+ loanRepaymentAddress,
+ stableCoins,
+ } = fixture;
+
+ await prepareTest(fixture, stableCoins.dai);
+
+ await mintToken(stableCoins.dai, loanRepaymentAddress, 100);
+ await approveBase18(
+ loanRepaymentAddress,
+ stableCoins.dai,
+ redemptionVault,
+ 100,
+ );
+
+ await bulkRepayLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ [{ id: 0 }],
+ );
+
+ await bulkRepayLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ [{ id: 0 }],
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('should fail: request not exist', async () => {
+ const fixture = await loadRvFixture();
+ const { redemptionVault, owner, mTBILL } = fixture;
+
+ await bulkRepayLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ [{ id: 0 }],
+ {
+ revertCustomError: {
+ customErrorName: 'RequestNotExists',
+ },
+ },
+ );
+ });
+
+ it('should fail: call from not vault admin', async () => {
+ const fixture = await loadRvFixture();
+ const {
+ redemptionVault,
+ owner,
+ mTBILL,
+ regularAccounts,
+ stableCoins,
+ loanRepaymentAddress,
+ } = fixture;
+
+ await prepareTest(fixture, stableCoins.dai);
+
+ await mintToken(stableCoins.dai, loanRepaymentAddress, 100);
+ await approveBase18(
+ loanRepaymentAddress,
+ stableCoins.dai,
+ redemptionVault,
+ 100,
+ );
+
+ await bulkRepayLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ [{ id: 0 }],
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+
+ it('approve 1 request when loanApr is not zero but does not exceed instant fee', async () => {
+ const fixture = await loadRvFixture();
+ const {
+ redemptionVault,
+ owner,
+ mTBILL,
+ loanRepaymentAddress,
+ stableCoins,
+ } = fixture;
+
+ await setInstantFeeTest({ vault: redemptionVault, owner }, 100);
+ await prepareTest(fixture, stableCoins.dai);
+ await increase(days(365));
+
+ await mintToken(stableCoins.dai, loanRepaymentAddress, 101);
+ await approveBase18(
+ loanRepaymentAddress,
+ stableCoins.dai,
+ redemptionVault,
+ 101,
+ );
+
+ await setLoanAprTest({ redemptionVault, owner }, 50);
+
+ await bulkRepayLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ [{ id: 0 }],
+ );
+ });
+
+ it('approve 1 request when loanApr is not zero and exceeds instant fee', async () => {
+ const fixture = await loadRvFixture();
+ const {
+ redemptionVault,
+ owner,
+ mTBILL,
+ loanRepaymentAddress,
+ stableCoins,
+ } = fixture;
+
+ await setInstantFeeTest({ vault: redemptionVault, owner }, 0);
+ await prepareTest(fixture, stableCoins.usdt);
+ const request = await redemptionVault.loanRequests(0);
+ await ethers.provider.send('evm_setNextBlockTimestamp', [
+ request.createdAt.toNumber() + days(365),
+ ]);
+
+ await mintToken(stableCoins.usdt, loanRepaymentAddress, 1000);
+ await approveBase18(
+ loanRepaymentAddress,
+ stableCoins.usdt,
+ redemptionVault,
+ 1000,
+ );
+
+ await setLoanAprTest({ redemptionVault, owner }, 10000);
+
+ await bulkRepayLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ [{ id: 0 }],
+ );
+ });
+
+ it('approve 1 request when instant fee changed after request creation', async () => {
+ const fixture = await loadRvFixture();
+ const {
+ redemptionVault,
+ owner,
+ mTBILL,
+ loanRepaymentAddress,
+ stableCoins,
+ } = fixture;
+
+ await setInstantFeeTest({ vault: redemptionVault, owner }, 200);
+ await prepareTest(fixture, stableCoins.dai);
+ await setInstantFeeTest({ vault: redemptionVault, owner }, 50);
+ await increase(days(365));
+
+ await mintToken(stableCoins.dai, loanRepaymentAddress, 102);
+ await approveBase18(
+ loanRepaymentAddress,
+ stableCoins.dai,
+ redemptionVault,
+ 102,
+ );
+
+ await setLoanAprTest({ redemptionVault, owner }, 100);
+
+ await bulkRepayLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ [{ id: 0 }],
+ );
+ });
+
+ it('approve 1 request when total fee exceeds actual repayment amount', async () => {
+ const fixture = await loadRvFixture();
+ const {
+ redemptionVault,
+ owner,
+ mTBILL,
+ loanRepaymentAddress,
+ stableCoins,
+ } = fixture;
+
+ await setInstantFeeTest({ vault: redemptionVault, owner }, 0);
+ await prepareTest(fixture, stableCoins.usdt);
+ const request = await redemptionVault.loanRequests(0);
+ await ethers.provider.send('evm_setNextBlockTimestamp', [
+ request.createdAt.toNumber() + days(365),
+ ]);
+
+ await mintToken(stableCoins.usdt, loanRepaymentAddress, 1000);
+ await approveBase18(
+ loanRepaymentAddress,
+ stableCoins.usdt,
+ redemptionVault,
+ 1000,
+ );
+
+ await setLoanAprTest({ redemptionVault, owner }, 20000);
+ await bulkRepayLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ [{ id: 0 }],
+ );
+ });
+
+ it('approve 2 request with same token out when loanApr is not zero', async () => {
+ const fixture = await loadRvFixture();
+ const {
+ redemptionVault,
+ owner,
+ mTBILL,
+ loanRepaymentAddress,
+ stableCoins,
+ } = fixture;
+
+ await prepareTest(fixture, stableCoins.usdt);
+ await prepareTest(fixture, stableCoins.usdt, false);
+ await setInstantFeeTest({ vault: redemptionVault, owner }, 0);
+
+ const r0 = await redemptionVault.loanRequests(0);
+ const r1 = await redemptionVault.loanRequests(1);
+ await ethers.provider.send('evm_setNextBlockTimestamp', [
+ Math.max(r0.createdAt.toNumber(), r1.createdAt.toNumber()) +
+ days(365),
+ ]);
+
+ await mintToken(stableCoins.usdt, loanRepaymentAddress, 2000);
+ await approveBase18(
+ loanRepaymentAddress,
+ stableCoins.usdt,
+ redemptionVault,
+ 2000,
+ );
+
+ await setLoanAprTest({ redemptionVault, owner }, 10000);
+
+ await bulkRepayLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ [{ id: 0 }, { id: 1 }],
+ );
+ });
+
+ it('approve 2 request with different token out when loanApr is not zero', async () => {
+ const fixture = await loadRvFixture();
+ const {
+ redemptionVault,
+ owner,
+ mTBILL,
+ loanRepaymentAddress,
+ stableCoins,
+ } = fixture;
+
+ await prepareTest(fixture, stableCoins.dai);
+ await prepareTest(fixture, stableCoins.usdc);
+ await setInstantFeeTest({ vault: redemptionVault, owner }, 0);
+
+ const r0 = await redemptionVault.loanRequests(0);
+ const r1 = await redemptionVault.loanRequests(1);
+ await ethers.provider.send('evm_setNextBlockTimestamp', [
+ Math.max(r0.createdAt.toNumber(), r1.createdAt.toNumber()) +
+ days(1),
+ ]);
+
+ await mintToken(stableCoins.dai, loanRepaymentAddress, 1000);
+ await mintToken(stableCoins.usdc, loanRepaymentAddress, 1000);
+ await approveBase18(
+ loanRepaymentAddress,
+ stableCoins.dai,
+ redemptionVault,
+ 1000,
+ );
+ await approveBase18(
+ loanRepaymentAddress,
+ stableCoins.usdc,
+ redemptionVault,
+ 1000,
+ );
+
+ await setLoanAprTest({ redemptionVault, owner }, 5000);
+ await bulkRepayLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ [{ id: 0 }, { id: 1 }],
+ );
+ });
+
+ it('approve 3 request with same token out when loanApr is not zero', async () => {
+ const fixture = await loadRvFixture();
+ const {
+ redemptionVault,
+ owner,
+ mTBILL,
+ loanRepaymentAddress,
+ stableCoins,
+ } = fixture;
+
+ await prepareTest(fixture, stableCoins.usdt);
+ await prepareTest(fixture, stableCoins.usdt, false);
+ await prepareTest(fixture, stableCoins.usdt, false);
+ await setInstantFeeTest({ vault: redemptionVault, owner }, 0);
+
+ const r0 = await redemptionVault.loanRequests(0);
+ const r1 = await redemptionVault.loanRequests(1);
+ const r2 = await redemptionVault.loanRequests(2);
+ await ethers.provider.send('evm_setNextBlockTimestamp', [
+ Math.max(
+ r0.createdAt.toNumber(),
+ r1.createdAt.toNumber(),
+ r2.createdAt.toNumber(),
+ ) + days(365),
+ ]);
+
+ await mintToken(stableCoins.usdt, loanRepaymentAddress, 5000);
+ await approveBase18(
+ loanRepaymentAddress,
+ stableCoins.usdt,
+ redemptionVault,
+ 5000,
+ );
+
+ await setLoanAprTest({ redemptionVault, owner }, 5000);
+
+ await bulkRepayLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ [{ id: 0 }, { id: 1 }, { id: 2 }],
+ );
+ });
+
+ it('approve 1 request when loanApr accrued interest is truncated to token decimals', async () => {
+ const fixture = await loadRvFixture();
+ const {
+ redemptionVault,
+ owner,
+ mTBILL,
+ loanRepaymentAddress,
+ stableCoins,
+ } = fixture;
+
+ await setInstantFeeTest({ vault: redemptionVault, owner }, 0);
+ await prepareTest(fixture, stableCoins.usdc6);
+ await increase(days(1));
+
+ const request = await redemptionVault.loanRequests(0);
+ const tokenDecimals = await stableCoins.usdc6.decimals();
+ const loanApr = BigNumber.from(3333);
+ const currentTimestamp = await getCurrentBlockTimestamp();
+ const duration = BigNumber.from(currentTimestamp).sub(
+ request.createdAt,
+ );
+
+ const accruedInterestRaw = request.amountTokenOut
+ .mul(loanApr)
+ .mul(duration)
+ .div(BigNumber.from(10_000).mul(365).mul(86400));
+
+ const accruedInterestTruncated = truncateToTokenDecimals(
+ accruedInterestRaw,
+ tokenDecimals,
+ );
+
+ expect(accruedInterestRaw).gt(accruedInterestTruncated);
+ expect(accruedInterestTruncated).gt(request.amountFee);
+
+ await mintToken(stableCoins.usdc6, loanRepaymentAddress, 1000);
+ await approveBase18(
+ loanRepaymentAddress,
+ stableCoins.usdc6,
+ redemptionVault,
+ 1000,
+ );
+
+ await setLoanAprTest({ redemptionVault, owner }, loanApr);
+
+ await bulkRepayLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ [{ id: 0 }],
+ );
+ });
+
+ it('approve 1 request when untruncated accrued interest exceeds instant fee but truncated does not', async () => {
+ const fixture = await loadRvFixture();
+ const {
+ redemptionVault,
+ owner,
+ mTBILL,
+ loanRepaymentAddress,
+ stableCoins,
+ } = fixture;
+
+ await setInstantFeeTest({ vault: redemptionVault, owner }, 100);
+ await prepareTest(fixture, stableCoins.usdc6);
+
+ const request = await redemptionVault.loanRequests(0);
+ const tokenDecimals = await stableCoins.usdc6.decimals();
+ const loanApr = BigNumber.from(133);
+ const durationSeconds = 23_950_800;
+
+ await ethers.provider.send('evm_setNextBlockTimestamp', [
+ request.createdAt.toNumber() + durationSeconds,
+ ]);
+ await ethers.provider.send('evm_mine', []);
+
+ const duration = BigNumber.from(durationSeconds);
+ const accruedInterestRaw = request.amountTokenOut
+ .mul(loanApr)
+ .mul(duration)
+ .div(BigNumber.from(10_000).mul(365).mul(86400));
+
+ const accruedInterestTruncated = truncateToTokenDecimals(
+ accruedInterestRaw,
+ tokenDecimals,
+ );
+
+ expect(accruedInterestRaw).gt(request.amountFee);
+ expect(accruedInterestTruncated).lte(request.amountFee);
+ expect(accruedInterestRaw).gt(accruedInterestTruncated);
+
+ await mintToken(stableCoins.usdc6, loanRepaymentAddress, 100);
+ await approveBase18(
+ loanRepaymentAddress,
+ stableCoins.usdc6,
+ redemptionVault,
+ 100,
+ );
+
+ await setLoanAprTest({ redemptionVault, owner }, loanApr);
+
+ await bulkRepayLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ [{ id: 0 }],
+ );
+
+ const requestAfter = await redemptionVault.loanRequests(0);
+ expect(requestAfter.amountFee).eq(request.amountFee);
+ });
+ });
+
+ describe('cancelLpLoanRequest()', () => {
+ const prepareCancelTest = async (
+ fixture: DefaultFixture,
+ stableCoin: ERC20Mock,
+ ) => {
+ const { redemptionVault, loanRepaymentAddress } = fixture;
+ await prepareTest(fixture, stableCoin);
+ await mintToken(stableCoin, loanRepaymentAddress, 100);
+ await approveBase18(
+ loanRepaymentAddress,
+ stableCoin,
+ redemptionVault,
+ 100,
+ );
+ };
+
+ it('should fail: when function is paused', async () => {
+ const fixture = await loadRvFixture();
+ const { redemptionVault, owner, mTBILL } = fixture;
+
+ await pauseVaultFn(
+ { pauseManager, owner },
+ redemptionVault,
+ encodeFnSelector('cancelLpLoanRequest(uint256)'),
+ );
+
+ await cancelLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ 0,
+ {
+ revertCustomError: {
+ customErrorName: 'Paused',
+ },
+ },
+ );
+ });
+
+ it('should cancel request', async () => {
+ const fixture = await loadRvFixture();
+ const { redemptionVault, owner, mTBILL, stableCoins } = fixture;
+
+ await prepareCancelTest(fixture, stableCoins.dai);
+
+ await cancelLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ 0,
+ );
+ });
+
+ it('should fail: request not exist', async () => {
+ const fixture = await loadRvFixture();
+ const { redemptionVault, owner, mTBILL } = fixture;
+
+ await cancelLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ 0,
+ {
+ revertCustomError: {
+ customErrorName: 'RequestNotExists',
+ },
+ },
+ );
+ });
+
+ it('should fail: request already cancelled', async () => {
+ const fixture = await loadRvFixture();
+ const { redemptionVault, owner, mTBILL, stableCoins } = fixture;
+
+ await prepareCancelTest(fixture, stableCoins.dai);
+
+ await cancelLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ 0,
+ );
+
+ await cancelLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ 0,
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('should fail: request was processed', async () => {
+ const fixture = await loadRvFixture();
+ const { redemptionVault, owner, mTBILL, stableCoins } = fixture;
+
+ await prepareCancelTest(fixture, stableCoins.dai);
+ await bulkRepayLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ [{ id: 0 }],
+ );
+
+ await cancelLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ 0,
+ {
+ revertCustomError: {
+ customErrorName: 'UnexpectedRequestStatus',
+ },
+ },
+ );
+ });
+
+ it('should fail: call from not vault admin', async () => {
+ const fixture = await loadRvFixture();
+ const {
+ redemptionVault,
+ owner,
+ mTBILL,
+ stableCoins,
+ regularAccounts,
+ } = fixture;
+
+ await prepareCancelTest(fixture, stableCoins.dai);
+ await bulkRepayLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ [{ id: 0 }],
+ );
+
+ await cancelLpLoanRequestTest(
+ { redemptionVault, owner, mTBILL },
+ 0,
+ {
+ from: regularAccounts[0],
+ revertCustomError: acErrors.WMAC_HASNT_PERMISSION,
+ },
+ );
+ });
+ });
+ });
+
+ describe('_obtainVaultLiquidityExternal', () => {
+ it('should fail: when not self call', async () => {
+ const { redemptionVault } = await loadRvFixture();
+
+ await expect(
+ redemptionVault._obtainVaultLiquidityExternal(
+ constants.AddressZero,
+ 0,
+ 0,
+ 0,
+ 0,
+ ),
+ ).to.be.revertedWithCustomError(redemptionVault, 'NotSelfCall');
+ });
+
+ it('when is self call', async () => {
+ const { redemptionVault } = await loadRvFixture();
+
+ const impersonatedRv = await ethers.getImpersonatedSigner(
+ redemptionVault.address,
+ );
+ await setBalance(impersonatedRv.address, parseUnits('100'));
+
+ await expect(
+ redemptionVault
+ .connect(impersonatedRv)
+ ._obtainVaultLiquidityExternal(constants.AddressZero, 0, 0, 0, 0),
+ ).to.not.revertedWithCustomError(redemptionVault, 'NotSelfCall');
+ });
+ });
+
+ describe('_obtainLoanLpLiquidityExternal', () => {
+ it('should fail: when not self call', async () => {
+ const { redemptionVault } = await loadRvFixture();
+
+ await expect(
+ redemptionVault._obtainLoanLpLiquidityExternal(
+ constants.AddressZero,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ ),
+ ).to.be.revertedWithCustomError(redemptionVault, 'NotSelfCall');
+ });
+
+ it('when is self call', async () => {
+ const { redemptionVault } = await loadRvFixture();
+
+ const impersonatedRv = await ethers.getImpersonatedSigner(
+ redemptionVault.address,
+ );
+ await setBalance(impersonatedRv.address, parseUnits('100'));
+
+ await expect(
+ redemptionVault
+ .connect(impersonatedRv)
+ ._obtainLoanLpLiquidityExternal(
+ constants.AddressZero,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ ),
+ ).to.not.revertedWithCustomError(redemptionVault, 'NotSelfCall');
+ });
+ });
+
+ describe('_convertUsdToToken', () => {
+ it('when amountUsd == 0', async () => {
+ const { redemptionVault, owner, stableCoins, dataFeed } =
+ await loadRvFixture();
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ expect(
+ (
+ await redemptionVault.convertUsdToTokenTest(
+ 0,
+ stableCoins.dai.address,
+ 0,
+ )
+ ).amountToken,
+ ).eq(0);
+ });
+
+ it('should fail: when tokenRate == 0', async () => {
+ const { redemptionVault } = await loadRvFixture();
+
+ await redemptionVault.setOverrideGetTokenRate(true);
+ await redemptionVault.setGetTokenRateValue(0);
+
+ await expect(
+ redemptionVault.convertUsdToTokenTest(
+ 1,
+ redemptionVault.address,
+ 0,
+ ),
+ ).to.be.revertedWithCustomError(redemptionVault, 'InvalidTokenRate');
+ });
+
+ it('when tokenRate == 0 but override rate is not 0', async () => {
+ const { redemptionVault, stableCoins, owner, dataFeed } =
+ await loadRvFixture();
+
+ await redemptionVault.setOverrideGetTokenRate(true);
+ await redemptionVault.setGetTokenRateValue(0);
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ expect(
+ (
+ await redemptionVault.convertUsdToTokenTest(
+ 0,
+ stableCoins.dai.address,
+ 1,
+ )
+ ).amountToken,
+ ).eq(0);
+ });
+
+ it('when payment token is not setup and override rate is not 0', async () => {
+ const { redemptionVault } = await loadRvFixture();
+
+ await redemptionVault.setOverrideGetTokenRate(true);
+ await redemptionVault.setGetTokenRateValue(0);
+
+ expect(
+ (
+ await redemptionVault.convertUsdToTokenTest(
+ 0,
+ constants.AddressZero,
+ 1,
+ )
+ ).amountToken,
+ ).eq(0);
+ });
+
+ it('should fail: when unknwon payment token', async () => {
+ const { redemptionVault } = await loadRvFixture();
+
+ await expect(
+ redemptionVault.convertUsdToTokenTest(1, constants.AddressZero, 0),
+ ).to.be.revertedWithoutReason();
+ });
+ });
+
+ describe('_convertMTokenToUsd', () => {
+ it('when amountMToken == 0', async () => {
+ const { redemptionVault } = await loadRvFixture();
+
+ expect(
+ (await redemptionVault.convertMTokenToUsdTest(0, 0)).amountUsd,
+ ).eq(0);
+ });
+
+ it('should fail: when override rate == 0', async () => {
+ const { redemptionVault } = await loadRvFixture();
+
+ await redemptionVault.setOverrideGetTokenRate(true);
+ await redemptionVault.setGetTokenRateValue(0);
+
+ await expect(
+ redemptionVault.convertMTokenToUsdTest(1, 0),
+ ).to.be.revertedWithCustomError(redemptionVault, 'InvalidTokenRate');
+ });
+ });
+
+ describe('_calculateHoldbackPartRateFromAvg', () => {
+ it('returns 0 when target total value is not above instant part (equality)', async () => {
+ const { redemptionVault } = await loadRvFixture();
+ const amountMToken = parseUnits('50');
+ const amountMTokenInstant = parseUnits('50');
+ const avgMTokenRate = parseUnits('1');
+ const mTokenRate = parseUnits('200');
+ const expected = expectedHoldbackPartRateFromAvg(
+ BigInt(amountMToken.toString()),
+ BigInt(amountMTokenInstant.toString()),
+ BigInt(mTokenRate.toString()),
+ BigInt(avgMTokenRate.toString()),
+ );
+ expect(expected).eq(0n);
+ expect(
+ await redemptionVault.calculateHoldbackPartRateFromAvgTest(
+ amountMToken,
+ amountMTokenInstant,
+ mTokenRate,
+ avgMTokenRate,
+ ),
+ ).eq(expected.toString());
+ });
+
+ it('returns 0 when avg rate implies lower target value than instant leg', async () => {
+ const { redemptionVault } = await loadRvFixture();
+ const amountMToken = parseUnits('100');
+ const amountMTokenInstant = parseUnits('10');
+ const avgMTokenRate = parseUnits('1');
+ const mTokenRate = parseUnits('2000');
+ const expected = expectedHoldbackPartRateFromAvg(
+ BigInt(amountMToken.toString()),
+ BigInt(amountMTokenInstant.toString()),
+ BigInt(mTokenRate.toString()),
+ BigInt(avgMTokenRate.toString()),
+ );
+ expect(expected).eq(0n);
+ expect(
+ await redemptionVault.calculateHoldbackPartRateFromAvgTest(
+ amountMToken,
+ amountMTokenInstant,
+ mTokenRate,
+ avgMTokenRate,
+ ),
+ ).eq(expected.toString());
+ });
+
+ it('returns 0 when amountMTokenInstant is 0 and avgMTokenRate is 0', async () => {
+ const { redemptionVault } = await loadRvFixture();
+ const amountMToken = parseUnits('100');
+ const one = parseUnits('1');
+ const expected = expectedHoldbackPartRateFromAvg(
+ BigInt(amountMToken.toString()),
+ 0n,
+ BigInt(one.toString()),
+ 0n,
+ );
+ expect(
+ await redemptionVault.calculateHoldbackPartRateFromAvgTest(
+ amountMToken,
+ 0,
+ one,
+ 0,
+ ),
+ ).eq(expected.toString());
+ });
+
+ it('returns 0 when amountMTokenInstant is 0', async () => {
+ const { redemptionVault } = await loadRvFixture();
+ const amountMToken = parseUnits('100');
+ const avgMTokenRate = parseUnits('1.25');
+ const mTokenRate = parseUnits('1');
+ const expected = expectedHoldbackPartRateFromAvg(
+ BigInt(amountMToken.toString()),
+ 0n,
+ BigInt(mTokenRate.toString()),
+ BigInt(avgMTokenRate.toString()),
+ );
+ expect(expected).eq(0n);
+ expect(
+ await redemptionVault.calculateHoldbackPartRateFromAvgTest(
+ amountMToken,
+ 0,
+ mTokenRate,
+ avgMTokenRate,
+ ),
+ ).eq(expected.toString());
+ });
+
+ it('applies integer rounding on the final rate', async () => {
+ const { redemptionVault } = await loadRvFixture();
+ const amountMToken = 3n;
+ const amountMTokenInstant = 1n;
+ const mTokenRate = 3n;
+ const avgMTokenRate = 2n;
+ const expected = expectedHoldbackPartRateFromAvg(
+ amountMToken,
+ amountMTokenInstant,
+ mTokenRate,
+ avgMTokenRate,
+ );
+ expect(
+ await redemptionVault.calculateHoldbackPartRateFromAvgTest(
+ amountMToken,
+ amountMTokenInstant,
+ mTokenRate,
+ avgMTokenRate,
+ ),
+ ).eq(expected.toString());
+ });
+
+ it('succeeds with amountMToken == 0 when branch returns 0 before division', async () => {
+ const { redemptionVault } = await loadRvFixture();
+ const amountMTokenInstant = parseUnits('100');
+ const mTokenRate = parseUnits('10');
+ const avgMTokenRate = parseUnits('1');
+ const expected = expectedHoldbackPartRateFromAvg(
+ 0n,
+ BigInt(amountMTokenInstant.toString()),
+ BigInt(mTokenRate.toString()),
+ BigInt(avgMTokenRate.toString()),
+ );
+ expect(expected).eq(0n);
+ expect(
+ await redemptionVault.calculateHoldbackPartRateFromAvgTest(
+ 0,
+ amountMTokenInstant,
+ mTokenRate,
+ avgMTokenRate,
+ ),
+ ).eq('0');
+ });
+
+ it('reverts when amountMToken == 0 but holdback part would be positive (division by zero)', async () => {
+ const { redemptionVault } = await loadRvFixture();
+ const amountMTokenInstant = parseUnits('100');
+ const avgMTokenRate = parseUnits('2');
+ const mTokenRate = parseUnits('1');
+ await expect(
+ redemptionVault.calculateHoldbackPartRateFromAvgTest(
+ 0,
+ amountMTokenInstant,
+ mTokenRate,
+ avgMTokenRate,
+ ),
+ ).to.be.reverted;
+ });
+
+ it('matches reference for mixed instant and holdback with realistic WAD rates', async () => {
+ const { redemptionVault } = await loadRvFixture();
+ const amountMToken = parseUnits('70');
+ const amountMTokenInstant = parseUnits('30');
+ const avgMTokenRate = parseUnits('1');
+ const mTokenRate = parseUnits('1');
+ const expected = expectedHoldbackPartRateFromAvg(
+ BigInt(amountMToken.toString()),
+ BigInt(amountMTokenInstant.toString()),
+ BigInt(mTokenRate.toString()),
+ BigInt(avgMTokenRate.toString()),
+ );
+ expect(
+ await redemptionVault.calculateHoldbackPartRateFromAvgTest(
+ amountMToken,
+ amountMTokenInstant,
+ mTokenRate,
+ avgMTokenRate,
+ ),
+ ).eq(expected.toString());
+ });
+
+ it('handles large values without overflow when inputs are bounded', async () => {
+ const { redemptionVault } = await loadRvFixture();
+ const amountMToken = 10n ** 30n * 6n;
+ const amountMTokenInstant = 10n ** 30n * 4n;
+ const avgMTokenRate = 10n ** 18n * 2n;
+ const mTokenRate = 10n ** 18n * 5n;
+ const expected = expectedHoldbackPartRateFromAvg(
+ amountMToken,
+ amountMTokenInstant,
+ mTokenRate,
+ avgMTokenRate,
+ );
+ expect(
+ await redemptionVault.calculateHoldbackPartRateFromAvgTest(
+ amountMToken,
+ amountMTokenInstant,
+ mTokenRate,
+ avgMTokenRate,
+ ),
+ ).eq(expected.toString());
+ });
+ });
+
+ describe('_calcAndValidateRedeem', () => {
+ it('should fail: when amountMTokenIn == 0', async () => {
+ const { redemptionVault, stableCoins, owner, dataFeed } =
+ await loadRvFixture();
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await expect(
+ redemptionVault.calcAndValidateRedeemTest(
+ constants.AddressZero,
+ stableCoins.dai.address,
+ 0,
+ 0,
+ 0,
+ false,
+ 0,
+ false,
+ ),
+ ).to.be.revertedWithCustomError(redemptionVault, 'InvalidAmount');
+ });
+
+ it('should override fee percent', async () => {
+ const {
+ redemptionVault,
+ stableCoins,
+ owner,
+ dataFeed,
+ mockedAggregatorMToken,
+ } = await loadRvFixture();
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+
+ const result =
+ await redemptionVault.callStatic.calcAndValidateRedeemTest(
+ constants.AddressZero,
+ stableCoins.dai.address,
+ parseUnits('100'),
+ 0,
+ 0,
+ true,
+ 10_00,
+ false,
+ );
+
+ expect(result.feeAmount).eq(parseUnits('10'));
+ expect(result.amountTokenOutWithoutFee).eq(parseUnits('90'));
+ });
+
+ it('should override token out rate and fee percent', async () => {
+ const {
+ redemptionVault,
+ stableCoins,
+ owner,
+ dataFeed,
+ mockedAggregatorMToken,
+ } = await loadRvFixture();
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+ await setRoundData({ mockedAggregator: mockedAggregatorMToken }, 1);
+
+ const result =
+ await redemptionVault.callStatic.calcAndValidateRedeemTest(
+ constants.AddressZero,
+ stableCoins.dai.address,
+ parseUnits('100'),
+ 0,
+ parseUnits('2'),
+ true,
+ 10_00,
+ false,
+ );
+
+ expect(result.feeAmount).eq(parseUnits('5'));
+ expect(result.amountTokenOutWithoutFee).eq(parseUnits('45'));
+ });
+
+ it('should override token out rate, mtoken rate and fee percent', async () => {
+ const { redemptionVault, stableCoins, owner, dataFeed } =
+ await loadRvFixture();
+
+ await addPaymentTokenTest(
+ { vault: redemptionVault, owner },
+ stableCoins.dai,
+ dataFeed.address,
+ 0,
+ true,
+ );
+
+ const result =
+ await redemptionVault.callStatic.calcAndValidateRedeemTest(
+ constants.AddressZero,
+ stableCoins.dai.address,
+ parseUnits('100'),
+ parseUnits('1'),
+ parseUnits('2'),
+ true,
+ 10_00,
+ false,
+ );
+
+ expect(result.feeAmount).eq(parseUnits('5'));
+ expect(result.amountTokenOutWithoutFee).eq(parseUnits('45'));
+ });
+ });
+ });
+
+ otherTests(rvFixture);
+ });
+};