If you notice some outdated information please let us know!
-25%(Penalty)
FAIL
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? (%)
2. How active is the primary contract? (%)
3. Does the protocol have a public software repository? (Y/N)
Location: https://github.com/anyswap
4. Is there a development history visible? (%)
At 16 commits, the development history of Multichain's contract repository is not yet as rich as a portal to another world should be.
5. Is the team public (not anonymous)?
Team members are public, though there is no one centralised list of employees. This LinkedIn employment list is incomplete, the CEO (for example) is not here.
This section looks at the software documentation. The document explaining these questions is here.
6. Is there a whitepaper? (Y/N)
Location: https://docs.multichain.org/
7. Is the protocol's software architecture documented? (Y/N)
This protocol's software architecture is documented in sufficient detail.
8. Does the software documentation fully cover the deployed contracts' source code? (%)
There is no coverage of deployed contracts by software function documentation. However, there is API documentation that awards them 20% for this metric.
9. Is it possible to trace the documented software to its implementation in the protocol's source code? (%)
There is no traceability between software documentation and implemented code. Without software function documentation, Multichain cannot have traceable documentation.
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 14% 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? (%)
There are no documented tests for code coverage in the Multichain GitHub or audits.
12. Does the protocol provide scripts and instructions to run their tests? (Y/N)
Multichain testing environment instructions can be found at https://github.com/anyswap/multichain-smart-contracts#multichain-smart-contract. However, this could really use some further elaboration.
13. Is there a detailed report of the protocol's test results?(%)
There is no detailed report of the Multichain test results.
14. Has the protocol undergone Formal Verification? (Y/N)
Multichain has not undergone a Formal Verification test.
15. Were the smart contracts deployed to a testnet? (Y/N)
Multichain has not documented their contracts deployment 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? (%)
AnySwap has been audited once before launch (it is now known as multichain since a rebranding). It has been since audited multiple times, with a Trail of Bits security assessment being released very recently.
17. Is the bounty value acceptably high (%)
This protocol offers an active bug bounty of $2M
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 was not documented in any part of the documentation.
19. Are relevant contracts clearly labelled as upgradeable or immutable? (%)
The relevant contracts are not identified as immutable / upgradeable.
20. Is the type of smart contract ownership clearly indicated? (%)
Ownership is not clearly indicated.
21. Are the protocol's smart contract change capabilities described? (%)
Smart contract change capabilities are not identified in any contracts.
22. Is the protocol's admin control information easy to understand? (%)
This information is not documented, making evaluation impossible.
23. Is there sufficient Pause Control documentation? (%)
Multichain's pause control is not documented.
24. Is there sufficient Timelock documentation? (%)
Multichain has no timelock documentation. It is clear that the founder is familiar with the importance of timelock documentation when it comes to DAO contracts, so it stands to reason there should be timelock documentation for the rest of the protocol.
25. Is the Timelock of an adequate length? (Y/N)
The timelock is of a non-specified length.
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. These questions are explained in this document.
26. Is the protocol's Oracle sufficiently documented? (%)
Multichain uses an MPC network instead of an oracle based system. The contracts dependent are identified. There is no relevant software function documentation.
27. Is front running mitigated by this protocol? (Y/N)
Multichain documents no front running mitigation strategies.
28. Can flashloan attacks be applied to the protocol, and if so, are those flashloan attack risks mitigated? (Y/N)
This protocol documents no flashloan countermeasures.
1// SPDX-License-Identifier: GPL-3.0-or-later
2
3pragma solidity ^0.8.10;
4
5import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
6import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
7import "@openzeppelin/contracts/access/AccessControlEnumerable.sol";
8
9
10library TokenOperation {
11 using Address for address;
12
13 function safeMint(
14 address token,
15 address to,
16 uint256 value
17 ) internal {
18 // mint(address,uint256)
19 _callOptionalReturn(token, abi.encodeWithSelector(0x40c10f19, to, value));
20 }
21
22 function safeBurnAny(
23 address token,
24 address from,
25 uint256 value
26 ) internal {
27 // burn(address,uint256)
28 _callOptionalReturn(token, abi.encodeWithSelector(0x9dc29fac, from, value));
29 }
30
31 function safeBurnSelf(
32 address token,
33 uint256 value
34 ) internal {
35 // burn(uint256)
36 _callOptionalReturn(token, abi.encodeWithSelector(0x42966c68, msg.sender, value));
37 }
38
39 function safeBurnFrom(
40 address token,
41 address from,
42 uint256 value
43 ) internal {
44 // burnFrom(address,uint256)
45 _callOptionalReturn(token, abi.encodeWithSelector(0x79cc6790, from, value));
46 }
47
48 function _callOptionalReturn(address token, bytes memory data) private {
49 bytes memory returndata = token.functionCall(data, "TokenOperation: low-level call failed");
50 if (returndata.length > 0) {
51 // Return data is optional
52 require(abi.decode(returndata, (bool)), "TokenOperation: did not succeed");
53 }
54 }
55}
56
57interface IBridge {
58 function Swapin(bytes32 txhash, address account, uint256 amount) external returns (bool);
59 function Swapout(uint256 amount, address bindaddr) external returns (bool);
60
61 event LogSwapin(bytes32 indexed txhash, address indexed account, uint256 amount);
62 event LogSwapout(address indexed account, address indexed bindaddr, uint256 amount);
63}
64
65interface IRouter {
66 function mint(address to, uint256 amount) external returns (bool);
67 function burn(address from, uint256 amount) external returns (bool);
68}
69
70contract MintBurnWrapper is AccessControlEnumerable, IBridge, IRouter {
71 using SafeERC20 for IERC20;
72
73 struct Supply {
74 uint256 max; // single limit of each mint
75 uint256 cap; // total limit of all mint
76 uint256 total; // total minted minus burned
77 }
78
79 bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
80
81 mapping(address => Supply) public minterSupply;
82 uint256 public totalMintCap; // total mint cap
83 uint256 public totalMinted; // total minted amount
84
85 enum TokenType {
86 MintBurnAny, // mint and burn(address from, uint256 amount), don't need approve
87 MintBurnFrom, // mint and burnFrom(address from, uint256 amount), need approve
88 MintBurnSelf, // mint and burn(uint256 amount), call transferFrom first, need approve
89 Transfer // transfer and transferFrom, need approve
90 }
91
92 address public immutable token; // the target token this contract is wrapping
93 TokenType public immutable tokenType;
94
95 bool public allMintPaused; // pause all mint calling
96 bool public allBurnPaused; // pause all burn calling
97 mapping(address => bool) public mintPaused; // pause specify minters' mint calling
98 mapping(address => bool) public burnPaused; // pause specify minters' burn calling
99
100 constructor(address _token, TokenType _tokenType, uint256 _totalMintCap, address _admin) {
101 require(_token != address(0), "zero token address");
102 require(_admin != address(0), "zero admin address");
103 token = _token;
104 tokenType = _tokenType;
105 totalMintCap = _totalMintCap;
106 _grantRole(DEFAULT_ADMIN_ROLE, _admin);
107 }
108
109 function owner() external view returns (address) {
110 return getRoleMember(DEFAULT_ADMIN_ROLE, 0);
111 }
112
113 function _mint(address to, uint256 amount) internal {
114 require(to != address(this), "forbid mint to address(this)");
115 require(!allMintPaused && !mintPaused[msg.sender], "mint paused");
116 Supply storage s = minterSupply[msg.sender];
117 require(amount <= s.max, "minter max exceeded");
118 s.total += amount;
119 require(s.total <= s.cap, "minter cap exceeded");
120 totalMinted += amount;
121 require(totalMinted <= totalMintCap, "total mint cap exceeded");
122
123 if (tokenType == TokenType.Transfer) {
124 IERC20(token).safeTransfer(to, amount);
125 } else {
126 TokenOperation.safeMint(token, to, amount);
127 }
128 }
129
130 function _burn(address from, uint256 amount) internal {
131 require(from != address(this), "forbid burn from address(this)");
132 require(!allBurnPaused && !burnPaused[msg.sender], "burn paused");
133 Supply storage s = minterSupply[msg.sender];
134 require(s.total >= amount, "minter burn amount exceeded");
135 s.total -= amount;
136 require(totalMinted >= amount, "total burn amount exceeded");
137 totalMinted -= amount;
138
139 if (tokenType == TokenType.Transfer) {
140 IERC20(token).safeTransferFrom(from, address(this), amount);
141 } else if (tokenType == TokenType.MintBurnAny) {
142 TokenOperation.safeBurnAny(token, from, amount);
143 } else if (tokenType == TokenType.MintBurnFrom) {
144 TokenOperation.safeBurnFrom(token, from, amount);
145 } else if (tokenType == TokenType.MintBurnSelf) {
146 IERC20(token).safeTransferFrom(from, address(this), amount);
147 TokenOperation.safeBurnSelf(token, amount);
148 }
149 }
150
151 // impl IRouter `mint`
152 function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) returns (bool) {
153 _mint(to, amount);
154 return true;
155 }
156
157 // impl IRouter `burn`
158 function burn(address from, uint256 amount) external onlyRole(MINTER_ROLE) returns (bool) {
159 _burn(from, amount);
160 return true;
161 }
162
163 // impl IBridge `Swapin`
164 function Swapin(bytes32 txhash, address account, uint256 amount) external onlyRole(MINTER_ROLE) returns (bool) {
165 _mint(account, amount);
166 emit LogSwapin(txhash, account, amount);
167 return true;
168 }
169
170 // impl IBridge `Swapout`
171 function Swapout(uint256 amount, address bindaddr) public returns (bool) {
172 require(bindaddr != address(0), "zero bind address");
173 _burn(msg.sender, amount);
174 emit LogSwapout(msg.sender, bindaddr, amount);
175 return true;
176 }
177
178 function setTotalMintCap(uint256 cap) external onlyRole(DEFAULT_ADMIN_ROLE) {
179 totalMintCap = cap;
180 }
181
182 function addMinter(address minter, uint256 cap, uint256 max) external onlyRole(DEFAULT_ADMIN_ROLE) {
183 _grantRole(MINTER_ROLE, minter);
184 minterSupply[minter].cap = cap;
185 minterSupply[minter].max = max;
186 mintPaused[minter] = false;
187 burnPaused[minter] = false;
188 }
189
190 function removeMinter(address minter) external onlyRole(DEFAULT_ADMIN_ROLE) {
191 _revokeRole(MINTER_ROLE, minter);
192 minterSupply[minter].cap = 0;
193 minterSupply[minter].max = 0;
194 }
195
196 function setMinterCap(address minter, uint256 cap) external onlyRole(DEFAULT_ADMIN_ROLE) {
197 require(hasRole(MINTER_ROLE, minter), "not minter");
198 minterSupply[minter].cap = cap;
199 }
200
201 function setMinterMax(address minter, uint256 max) external onlyRole(DEFAULT_ADMIN_ROLE) {
202 require(hasRole(MINTER_ROLE, minter), "not minter");
203 minterSupply[minter].max = max;
204 }
205
206 function setMinterTotal(address minter, uint256 total) external onlyRole(DEFAULT_ADMIN_ROLE) {
207 minterSupply[minter].total = total;
208 }
209
210 function setAllMintPaused(bool paused) external onlyRole(DEFAULT_ADMIN_ROLE) {
211 allMintPaused = paused;
212 }
213
214 function setAllBurnPaused(bool paused) external onlyRole(DEFAULT_ADMIN_ROLE) {
215 allBurnPaused = paused;
216 }
217
218 function setAllMintAndBurnPaused(bool paused) external onlyRole(DEFAULT_ADMIN_ROLE) {
219 allMintPaused = paused;
220 allBurnPaused = paused;
221 }
222
223 function setMintPaused(address minter, bool paused) external onlyRole(DEFAULT_ADMIN_ROLE) {
224 require(hasRole(MINTER_ROLE, minter), "not minter");
225 mintPaused[minter] = paused;
226 }
227
228 function setBurnPaused(address minter, bool paused) external onlyRole(DEFAULT_ADMIN_ROLE) {
229 require(hasRole(MINTER_ROLE, minter), "not minter");
230 burnPaused[minter] = paused;
231 }
232
233 function setMintAndBurnPaused(address minter, bool paused) external onlyRole(DEFAULT_ADMIN_ROLE) {
234 require(hasRole(MINTER_ROLE, minter), "not minter");
235 mintPaused[minter] = paused;
236 burnPaused[minter] = paused;
237 }
238}
239
Tests to Code: 151 / 1052 = 14 %