If you notice some outdated information please let us know!
PASS
The final review score is indicated as a percentage. The percentage is calculated as Achieved Points due to MAX Possible Points. For each element the answer can be either Yes/No or a percentage. For a detailed breakdown of the individual weights of each question, please consult this document.
Very simply, the audit looks for the following declarations from the developer's site. With these declarations, it is reasonable to trust the smart contracts.
This report is for informational purposes only and does not constitute investment advice of any kind, nor does it constitute an offer to provide investment advisory or other services. Nothing in this report shall be considered a solicitation or offer to buy or sell any security, token, future, option or other financial instrument or to offer or provide any investment advice or service to any person in any jurisdiction. Nothing contained in this report constitutes investment advice or offers any opinion with respect to the suitability of any security, and the views expressed in this report should not be taken as advice to buy, sell or hold any security. The information in this report should not be relied upon for the purpose of investing. In preparing the information contained in this report, we have not taken into account the investment needs, objectives and financial circumstances of any particular investor. This information has no regard to the specific investment objectives, financial situation and particular needs of any specific recipient of this information and investments discussed may not be suitable for all investors.
Any views expressed in this report by us were prepared based upon the information available to us at the time such views were written. The views expressed within this report are limited to DeFiSafety and the author and do not reflect those of any additional or third party and are strictly based upon DeFiSafety, its authors, interpretations and evaluation of relevant data. Changed or additional information could cause such views to change. All information is subject to possible correction. Information may quickly become unreliable for various reasons, including changes in market conditions or economic circumstances.
This completed report is copyright (c) DeFiSafety 2023. Permission is given to copy in whole, retaining this copyright label.
This section looks at the code deployed on the relevant chain that gets reviewed and its corresponding software repository. The document explaining these questions is here.
1. Are the smart contract addresses easy to find? (%)
They can be found at https://mstable.gitbook.io/developer-docs/contracts/massets , as indicated in the Appendix.
2. How active is the primary contract? (%)
Contract mUSD is used 10+ times a day, as indicated in the Appendix.
3. Does the protocol have a public software repository? (Y/N)
Location: https://github.com/mstable
4. Is there a development history visible? (%)
At 935 commits at 9 branches, this protocol is clearly as stable as the USD peg it seeks to emulate.
5. Is the team public (not anonymous)?
This team is public.
The difference between this and the old link is solely the link. This section looks at the software documentation. The document explaining these questions is here.
6. Is there a whitepaper? (Y/N)
Location: https://docs.mstable.org/
7. Is the protocol's software architecture documented? (Y/N)
This protocol's software architecture is documented in full.
8. Does the software documentation fully cover the deployed contracts' source code? (%)
There is full coverage of deployed contracts by software function documentation.
9. Is it possible to trace the documented software to its implementation in the protocol's source code? (%)
There is explicit traceability between software documentation and implemented code.
10. Has the protocol tested their deployed code? (%)
Code examples are in the Appendix at the end of this report.. As per the SLOC, there is 507% testing to code (TtC). This score is guided by the Test to Code ratio (TtC). Generally a good test to code ratio is over 100%. However, the reviewer's best judgement is the final deciding factor.
11. How covered is the protocol's code? (%)
mStable has 92% code coverage.
12. Does the protocol provide scripts and instructions to run their tests? (Y/N)
Scripts/Instructions location: https://github.com/mstable/mStable-contracts/blob/master/README.md
13. Is there a detailed report of the protocol's test results?(%)
mStable has reports from their GitHub CI reports as well as from coveralls.
14. Has the protocol undergone Formal Verification? (Y/N)
This protocol has not undergone formal verification.
15. Were the smart contracts deployed to a testnet? (Y/N)
This protocol has been deployed to a testnet.
This section looks at the 3rd party software audits done. It is explained in this document.
16. Is the protocol sufficiently audited? (%)
mStable has had audits from ConsenSys Diligence and Bramah Systems (before deployment), as well as from Certik and PeckShield (after deployment). All audit reports can be found here.. There's even a link to the old version of our process quality review - lucky us!
17. Is the bounty value acceptably high (%)
This protocol offers an active bug bounty of $50K
This section covers the documentation of special access controls for a DeFi protocol. The admin access controls are the contracts that allow updating contracts or coefficients in the protocol. Since these contracts can allow the protocol admins to "change the rules", complete disclosure of capabilities is vital for user's transparency. It is explained in this document.
18. Is the protocol's admin control information easy to find?
Admin control information is very easy to find.
19. Are relevant contracts clearly labelled as upgradeable or immutable? (%)
All relevant contracts are clearly labelled in their relative state
20. Is the type of smart contract ownership clearly indicated? (%)
Ownership is well explained: it's held in multiple Gnosis safe multisigs.
21. Are the protocol's smart contract change capabilities described? (%)
Smart contract change capabilities are clearly identified in relevant contracts.
22. Is the protocol's admin control information easy to understand? (%)
This information is in software specific language. A non-defi native individual would find understanding this information complicated.
23. Is there sufficient Pause Control documentation? (%)
This protocol's pause control is documented, with the contracts dependent identified.
24. Is there sufficient Timelock documentation? (%)
This protocol has good timelock documentation, with all relevant contracts identified.
25. Is the Timelock of an adequate length? (Y/N)
The timelock is specified as 7 days long.
This section goes over the documentation that a protocol may or may not supply about their Oracle usage. Oracles are a fundamental part of DeFi as they are responsible for relaying tons of price data information to thousands of protocols using blockchain technology. Not only are they important for price feeds, but they are also an essential component of transaction verification and security. This is explained in this document.
26. Is the protocol's Oracle sufficiently documented? (%)
mStable doesn't use oracles, and this is identified and justified in their documentation.
27. Is front running mitigated by this protocol? (Y/N)
This protocol documents front running mitigation measures in the form of min-price set functions. As an AMM, there is little they can do but this effort earns the points.
28. Can flashloan attacks be applied to the protocol, and if so, are those flashloan attack risks mitigated? (Y/N)
mStable documents flashloan attack countermeasures at this location.
1// SPDX-License-Identifier: AGPL-3.0-or-later
2pragma solidity 0.8.6;
3pragma abicoder v2;
4
5// External
6import { IPlatformIntegration } from "../interfaces/IPlatformIntegration.sol";
7import { IInvariantValidator } from "../interfaces/IInvariantValidator.sol";
8import { IBasicToken } from "../shared/IBasicToken.sol";
9
10// Internal
11import "./MassetStructs.sol";
12
13// Libs
14import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
15import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
16import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
17import { StableMath } from "../shared/StableMath.sol";
18import { MassetHelpers } from "../shared/MassetHelpers.sol";
19
20/**
21 * @title Manager
22 * @author mStable
23 * @notice Simply contains logic to perform Basket Manager duties for an mAsset.
24 * Allowing logic can be abstracted here to avoid bytecode inflation.
25 * @dev VERSION: 1.0
26 * DATE: 2021-01-22
27 */
28library MassetManager {
29 using SafeERC20 for IERC20;
30 using StableMath for uint256;
31
32 event BassetsMigrated(address[] bAssets, address newIntegrator);
33 event TransferFeeEnabled(address indexed bAsset, bool enabled);
34 event BassetAdded(address indexed bAsset, address integrator);
35 event BassetStatusChanged(address indexed bAsset, BassetStatus status);
36 event BasketStatusChanged();
37 event StartRampA(uint256 currentA, uint256 targetA, uint256 startTime, uint256 rampEndTime);
38 event StopRampA(uint256 currentA, uint256 time);
39
40 uint256 private constant MIN_RAMP_TIME = 1 days;
41 uint256 private constant MAX_A = 1e6;
42
43 /**
44 * @notice Adds a bAsset to the given personal, data and mapping, provided it is valid
45 * @param _bAssetPersonal Basset data storage array
46 * @param _bAssetData Basset data storage array
47 * @param _bAssetIndexes Mapping of bAsset address to their index
48 * @param _bAsset Address of the ERC20 token to add to the Basket
49 * @param _integration Address of the Platform Integration
50 * @param _mm Base 1e8 var to determine measurement ratio
51 * @param _hasTxFee Are transfer fees charged on this bAsset (e.g. USDT)
52 */
53 function addBasset(
54 BassetPersonal[] storage _bAssetPersonal,
55 BassetData[] storage _bAssetData,
56 mapping(address => uint8) storage _bAssetIndexes,
57 address _bAsset,
58 address _integration,
59 uint256 _mm,
60 bool _hasTxFee
61 ) external {
62 require(_bAsset != address(0), "bAsset address must be valid");
63 uint8 bAssetCount = uint8(_bAssetPersonal.length);
64
65 uint8 idx = _bAssetIndexes[_bAsset];
66 require(
67 bAssetCount == 0 || _bAssetPersonal[idx].addr != _bAsset,
68 "bAsset already exists in Basket"
69 );
70
71 // Should fail if bAsset is not added to integration
72 // Programmatic enforcement of bAsset validity should service through decentralised feed
73 if (_integration != address(0)) {
74 IPlatformIntegration(_integration).checkBalance(_bAsset);
75 }
76
77 uint256 bAssetDecimals = IBasicToken(_bAsset).decimals();
78 require(
79 bAssetDecimals >= 4 && bAssetDecimals <= 18,
80 "Token must have sufficient decimal places"
81 );
82
83 uint256 delta = uint256(18) - bAssetDecimals;
84 uint256 ratio = _mm * (10**delta);
85
86 _bAssetIndexes[_bAsset] = bAssetCount;
87
88 _bAssetPersonal.push(
89 BassetPersonal({
90 addr: _bAsset,
91 integrator: _integration,
92 hasTxFee: _hasTxFee,
93 status: BassetStatus.Normal
94 })
95 );
96 _bAssetData.push(BassetData({ ratio: SafeCast.toUint128(ratio), vaultBalance: 0 }));
97
98 emit BassetAdded(_bAsset, _integration);
99 }
100
101 /**
102 * @dev Collects the interest generated from the Basket, minting a relative
103 * amount of mAsset and sending it over to the SavingsManager.
104 * @param _bAssetPersonal Basset personal storage array
105 * @param _bAssetData Basset data storage array
106 * @return indices Array of bAsset idxs [0,1...]
107 * @return rawGains Raw increases in vault Balance
108 */
109 function collectPlatformInterest(
110 BassetPersonal[] memory _bAssetPersonal,
111 BassetData[] storage _bAssetData
112 ) external returns (uint8[] memory indices, uint256[] memory rawGains) {
113 // Get basket details
114 BassetData[] memory bAssetData_ = _bAssetData;
115 uint256 count = bAssetData_.length;
116 indices = new uint8[](count);
117 rawGains = new uint256[](count);
118 // 1. Calculate rawGains in each bAsset, in comparison to current vault balance
119 for (uint256 i = 0; i < count; i++) {
120 indices[i] = uint8(i);
121 BassetPersonal memory bPersonal = _bAssetPersonal[i];
122 BassetData memory bData = bAssetData_[i];
123 // If there is no integration, then nothing can have accrued
124 if (bPersonal.integrator == address(0)) continue;
125 uint256 lending = IPlatformIntegration(bPersonal.integrator).checkBalance(
126 bPersonal.addr
127 );
128 uint256 cache = 0;
129 if (!bPersonal.hasTxFee) {
130 cache = IERC20(bPersonal.addr).balanceOf(bPersonal.integrator);
131 }
132 uint256 balance = lending + cache;
133 uint256 oldVaultBalance = bData.vaultBalance;
134 if (balance > oldVaultBalance && bPersonal.status == BassetStatus.Normal) {
135 _bAssetData[i].vaultBalance = SafeCast.toUint128(balance);
136 uint256 interestDelta = balance - oldVaultBalance;
137 rawGains[i] = interestDelta;
138 } else {
139 rawGains[i] = 0;
140 }
141 }
142 }
143
144 /**
145 * @dev Update transfer fee flag for a given bAsset, should it change its fee practice
146 * @param _bAssetPersonal Basset data storage array
147 * @param _bAssetIndexes Mapping of bAsset address to their index
148 * @param _bAsset bAsset address
149 * @param _flag Charge transfer fee when its set to 'true', otherwise 'false'
150 */
151 function setTransferFeesFlag(
152 BassetPersonal[] storage _bAssetPersonal,
153 mapping(address => uint8) storage _bAssetIndexes,
154 address _bAsset,
155 bool _flag
156 ) external {
157 uint256 index = _getAssetIndex(_bAssetPersonal, _bAssetIndexes, _bAsset);
158 _bAssetPersonal[index].hasTxFee = _flag;
159
160 if (_flag) {
161 // if token has tx fees, it can no longer operate with a cache
162 address integration = _bAssetPersonal[index].integrator;
163 if (integration != address(0)) {
164 uint256 bal = IERC20(_bAsset).balanceOf(integration);
165 if (bal > 0) {
166 IPlatformIntegration(integration).deposit(_bAsset, bal, true);
167 }
168 }
169 }
170
171 emit TransferFeeEnabled(_bAsset, _flag);
172 }
173
174 /**
175 * @dev Transfers all collateral from one lending market to another - used initially
176 * to handle the migration between Aave V1 and Aave V2. Note - only supports non
177 * tx fee enabled assets. Supports going from no integration to integration, but
178 * not the other way around.
179 * @param _bAssetPersonal Basset data storage array
180 * @param _bAssetIndexes Mapping of bAsset address to their index
181 * @param _bAssets Array of basket assets to migrate
182 * @param _newIntegration Address of the new platform integration
183 */
184 function migrateBassets(
185 BassetPersonal[] storage _bAssetPersonal,
186 mapping(address => uint8) storage _bAssetIndexes,
187 address[] calldata _bAssets,
188 address _newIntegration
189 ) external {
190 uint256 len = _bAssets.length;
191 require(len > 0, "Must migrate some bAssets");
192
193 for (uint256 i = 0; i < len; i++) {
194 // 1. Check that the bAsset is in the basket
195 address bAsset = _bAssets[i];
196 uint256 index = _getAssetIndex(_bAssetPersonal, _bAssetIndexes, bAsset);
197 require(!_bAssetPersonal[index].hasTxFee, "A bAsset has a transfer fee");
198
199 // 2. Withdraw everything from the old platform integration
200 address oldAddress = _bAssetPersonal[index].integrator;
201 require(oldAddress != _newIntegration, "Must transfer to new integrator");
202 (uint256 cache, uint256 lendingBal) = (0, 0);
203 if (oldAddress == address(0)) {
204 cache = IERC20(bAsset).balanceOf(address(this));
205 } else {
206 IPlatformIntegration oldIntegration = IPlatformIntegration(oldAddress);
207 cache = IERC20(bAsset).balanceOf(address(oldIntegration));
208 // 2.1. Withdraw from the lending market
209 lendingBal = oldIntegration.checkBalance(bAsset);
210 if (lendingBal > 0) {
211 oldIntegration.withdraw(address(this), bAsset, lendingBal, false);
212 }
213 // 2.2. Withdraw from the cache, if any
214 if (cache > 0) {
215 oldIntegration.withdrawRaw(address(this), bAsset, cache);
216 }
217 }
Tests to Code: 24077 / 4736 = 508 %