If you notice some outdated information please let us know!
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 Mainnet that gets reviewed and its corresponding software repository. The document explaining these questions is here.
1. Are the executing code addresses readily available? (%)
Contracts could not be found in either the docs or the GitHub. No audits specify the address of the contracts.
2. Is the code actively being used? (%)
Activity could not be determined due to an inability to locate contract addresses.
3. Is there a public software repository? (Y/N)
Is there a public software repository with the code at a minimum, but also normally test and scripts. Even if the repository was created just to hold the files and has just 1 transaction, it gets a "Yes". For teams with private repositories, this answer is "No"
4. Is there a development history visible? (%)
At 400 commits, Alpha Homora demonstrates a good commitment to strong documentation practices.
This metric checks if the software repository demonstrates a strong steady history. This is normally demonstrated by commits, branches and releases in a software repository. A healthy history demonstrates a history of more than a month (at a minimum).
5. Is the team public (not anonymous)? (Y/N)
The team is public.
For a "Yes" in this question, the real names of some team members must be public on the website or other documentation (LinkedIn, etc). If the team is anonymous, then this question is a "No".
This section looks at the software documentation. The document explaining these questions is here.
6. Is there a whitepaper? (Y/N)
7. Are the basic software functions documented? (Y/N)
No software functions are documented.
8. Does the software function documentation fully (100%) cover the deployed contracts? (%)
There is no software function documentation.
9. Are there sufficiently detailed comments for all functions within the deployed contract code (%)
The Comments to Code (CtC) ratio is the primary metric for this score.
10. Is it possible to trace from software documentation to the implementation in code (%)
There is no traceability from software documentation to code.
11. Full test suite (Covers all the deployed code) (%)
This score is guided by the Test to Code ratio (TtC). Generally a good test to code ratio is over 100%. However the reviewers best judgement is the final deciding factor.
12. Code coverage (Covers all the deployed lines of code, or explains misses) (%)
No tests for code coverage were seen, but it's clear that this protocol has undergone testing.
14. Report of the results (%)
No test report was found.
15. Formal Verification test done (%)
No formal verification has been undertaken.
16. Stress Testing environment (%)
No testnet deployment was documented.
This section looks at the 3rd party software audits done. It is explained in this document.
17. Did 3rd Party audits take place? (%)
Despite three audits no listed contracts mean we cannot award points for them as per guidance.
18. Is the bug bounty acceptable high? (%)
With a $750k active bounty, this bug bounty is sufficient.
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.
19. Can a user clearly and quickly find the status of the access controls (%)
Admin Control information could not be found.
20. Is the information clear and complete (%)
Admin Control information could not be found.
21. Is the information in non-technical terms that pertain to the investments (%)
Admin Control information could not be found.
22. Is there Pause Control documentation including records of tests (%)
Pause Control information could not be found
1pragma solidity 0.6.12;
2
3import 'OpenZeppelin/openzeppelin-contracts@3.4.0/contracts/token/ERC20/IERC20.sol';
4import 'OpenZeppelin/openzeppelin-contracts@3.4.0/contracts/token/ERC20/SafeERC20.sol';
5import 'OpenZeppelin/openzeppelin-contracts@3.4.0/contracts/token/ERC1155/IERC1155.sol';
6import 'OpenZeppelin/openzeppelin-contracts@3.4.0/contracts/math/SafeMath.sol';
7import 'OpenZeppelin/openzeppelin-contracts@3.4.0/contracts/math/Math.sol';
8
9import './Governable.sol';
10import './utils/ERC1155NaiveReceiver.sol';
11import '../interfaces/IBank.sol';
12import '../interfaces/ICErc20.sol';
13import '../interfaces/IOracle.sol';
14
15library HomoraSafeMath {
16 using SafeMath for uint;
17
18 /// @dev Computes round-up division.
19 function ceilDiv(uint a, uint b) internal pure returns (uint) {
20 return a.add(b).sub(1).div(b);
21 }
22}
23
24contract HomoraCaster {
25 /// @dev Call to the target using the given data.
26 /// @param target The address target to call.
27 /// @param data The data used in the call.
28 function cast(address target, bytes calldata data) external payable {
29 (bool ok, bytes memory returndata) = target.call{value: msg.value}(data);
30 if (!ok) {
31 if (returndata.length > 0) {
32 // The easiest way to bubble the revert reason is using memory via assembly
33 // solhint-disable-next-line no-inline-assembly
34 assembly {
35 let returndata_size := mload(returndata)
36 revert(add(32, returndata), returndata_size)
37 }
38 } else {
39 revert('bad cast call');
40 }
41 }
42 }
43}
44
45contract HomoraBank is Governable, ERC1155NaiveReceiver, IBank {
46 using SafeMath for uint;
47 using HomoraSafeMath for uint;
48 using SafeERC20 for IERC20;
49
50 uint private constant _NOT_ENTERED = 1;
51 uint private constant _ENTERED = 2;
52 uint private constant _NO_ID = uint(-1);
53 address private constant _NO_ADDRESS = address(1);
54
55 struct Bank {
56 bool isListed; // Whether this market exists.
57 uint8 index; // Reverse look up index for this bank.
58 address cToken; // The CToken to draw liquidity from.
59 uint reserve; // The reserve portion allocated to Homora protocol.
60 uint totalDebt; // The last recorded total debt since last action.
61 uint totalShare; // The total debt share count across all open positions.
62 }
63
64 struct Position {
65 address owner; // The owner of this position.
66 address collToken; // The ERC1155 token used as collateral for this position.
67 uint collId; // The token id used as collateral.
68 uint collateralSize; // The size of collateral token for this position.
69 uint debtMap; // Bitmap of nonzero debt. i^th bit is set iff debt share of i^th bank is nonzero.
70 mapping(address => uint) debtShareOf; // The debt share for each token.
71 }
72
73 uint public _GENERAL_LOCK; // TEMPORARY: re-entrancy lock guard.
74 uint public _IN_EXEC_LOCK; // TEMPORARY: exec lock guard.
75 uint public override POSITION_ID; // TEMPORARY: position ID currently under execution.
76 address public override SPELL; // TEMPORARY: spell currently under execution.
77
78 address public caster; // The caster address for untrusted execution.
79 IOracle public oracle; // The oracle address for determining prices.
80 uint public feeBps; // The fee collected as protocol reserve in basis points from interest.
81 uint public override nextPositionId; // Next available position ID, starting from 1 (see initialize).
82
83 address[] public allBanks; // The list of all listed banks.
84 mapping(address => Bank) public banks; // Mapping from token to bank data.
85 mapping(address => bool) public cTokenInBank; // Mapping from cToken to its existence in bank.
86 mapping(uint => Position) public positions; // Mapping from position ID to position data.
87
88 bool public allowContractCalls; // The boolean status whether to allow call from contract (false = onlyEOA)
89 mapping(address => bool) public whitelistedTokens; // Mapping from token to whitelist status
90 mapping(address => bool) public whitelistedSpells; // Mapping from spell to whitelist status
91 mapping(address => bool) public whitelistedUsers; // Mapping from user to whitelist status
92
93 uint public bankStatus; // Each bit stores certain bank status, e.g. borrow allowed, repay allowed
94
95 /// @dev Ensure that the function is called from EOA when allowContractCalls is set to false and caller is not whitelisted
96 modifier onlyEOAEx() {
97 if (!allowContractCalls && !whitelistedUsers[msg.sender]) {
98 require(msg.sender == tx.origin, 'not eoa');
99 }
100 _;
101 }
102
103 /// @dev Reentrancy lock guard.
104 modifier lock() {
105 require(_GENERAL_LOCK == _NOT_ENTERED, 'general lock');
106 _GENERAL_LOCK = _ENTERED;
107 _;
108 _GENERAL_LOCK = _NOT_ENTERED;
109 }
110
111 /// @dev Ensure that the function is called from within the execution scope.
112 modifier inExec() {
113 require(POSITION_ID != _NO_ID, 'not within execution');
114 require(SPELL == msg.sender, 'not from spell');
115 require(_IN_EXEC_LOCK == _NOT_ENTERED, 'in exec lock');
116 _IN_EXEC_LOCK = _ENTERED;
117 _;
118 _IN_EXEC_LOCK = _NOT_ENTERED;
119 }
120
121 /// @dev Ensure that the interest rate of the given token is accrued.
122 modifier poke(address token) {
123 accrue(token);
124 _;
125 }
126
127 /// @dev Initialize the bank smart contract, using msg.sender as the first governor.
128 /// @param _oracle The oracle smart contract address.
129 /// @param _feeBps The fee collected to Homora bank.
130 function initialize(IOracle _oracle, uint _feeBps) external initializer {
131 __Governable__init();
132 _GENERAL_LOCK = _NOT_ENTERED;
133 _IN_EXEC_LOCK = _NOT_ENTERED;
134 POSITION_ID = _NO_ID;
135 SPELL = _NO_ADDRESS;
136 caster = address(new HomoraCaster());
137 oracle = _oracle;
138 require(address(_oracle) != address(0), 'bad oracle address');
139 feeBps = _feeBps;
140 nextPositionId = 1;
141 bankStatus = 3; // allow both borrow and repay
142 emit SetOracle(address(_oracle));
143 emit SetFeeBps(_feeBps);
144 }
145
146 /// @dev Return the current executor (the owner of the current position).
147 function EXECUTOR() external view override returns (address) {
148 uint positionId = POSITION_ID;
149 require(positionId != _NO_ID, 'not under execution');
150 return positions[positionId].owner;
151 }
152
153 /// @dev Set allowContractCalls
154 /// @param ok The status to set allowContractCalls to (false = onlyEOA)
155 function setAllowContractCalls(bool ok) external onlyGov {
156 allowContractCalls = ok;
157 }
158
159 /// @dev Set whitelist spell status
160 /// @param spells list of spells to change status
161 /// @param statuses list of statuses to change to
162 function setWhitelistSpells(address[] calldata spells, bool[] calldata statuses)
163 external
164 onlyGov
165 {
166 require(spells.length == statuses.length, 'spells & statuses length mismatched');
167 for (uint idx = 0; idx < spells.length; idx++) {
168 whitelistedSpells[spells[idx]] = statuses[idx];
169 }
170 }
171
172 /// @dev Set whitelist token status
173 /// @param tokens list of tokens to change status
174 /// @param statuses list of statuses to change to
175 function setWhitelistTokens(address[] calldata tokens, bool[] calldata statuses)
176 external
177 onlyGov
178 {
179 require(tokens.length == statuses.length, 'tokens & statuses length mismatched');
180 for (uint idx = 0; idx < tokens.length; idx++) {
181 if (statuses[idx]) {
182 // check oracle suppport
183 require(support(tokens[idx]), 'oracle not support token');
184 }
185 whitelistedTokens[tokens[idx]] = statuses[idx];
186 }
187 }
188
189 /// @dev Set whitelist user status
190 /// @param users list of users to change status
191 /// @param statuses list of statuses to change to
192 function setWhitelistUsers(address[] calldata users, bool[] calldata statuses) external onlyGov {
193 require(users.length == statuses.length, 'users & statuses length mismatched');
194 for (uint idx = 0; idx < users.length; idx++) {
195 whitelistedUsers[users[idx]] = statuses[idx];
196 }
197 }
198
199 /// @dev Check whether the oracle supports the token
200 /// @param token ERC-20 token to check for support
201 function support(address token) public view override returns (bool) {
202 return oracle.support(token);
203 }
204
205 /// @dev Set bank status
206 /// @param _bankStatus new bank status to change to
207 function setBankStatus(uint _bankStatus) external onlyGov {
208 bankStatus = _bankStatus;
209 }
210
211 /// @dev Bank borrow status allowed or not
212 /// @notice check last bit of bankStatus
213 function allowBorrowStatus() public view returns (bool) {
214 return (bankStatus & 0x01) > 0;
215 }
216
217 /// @dev Bank repay status allowed or not
218 /// @notice Check second-to-last bit of bankStatus
219 function allowRepayStatus() public view returns (bool) {
220 return (bankStatus & 0x02) > 0;
221 }
222
223 /// @dev Trigger interest accrual for the given bank.
224 /// @param token The underlying token to trigger the interest accrual.
225 function accrue(address token) public override {
226 Bank storage bank = banks[token];
227 require(bank.isListed, 'bank not exist');
228 uint totalDebt = bank.totalDebt;
229 uint debt = ICErc20(bank.cToken).borrowBalanceCurrent(address(this));
230 if (debt > totalDebt) {
231 uint fee = debt.sub(totalDebt).mul(feeBps).div(10000);
232 bank.totalDebt = debt;
233 bank.reserve = bank.reserve.add(doBorrow(token, fee));
234 } else if (totalDebt != debt) {
235 // We should never reach here because CREAMv2 does not support *repayBorrowBehalf*
236 // functionality. We set bank.totalDebt = debt nonetheless to ensure consistency. But do
237 // note that if *repayBorrowBehalf* exists, an attacker can maliciously deflate debt
238 // share value and potentially make this contract stop working due to math overflow.
239 bank.totalDebt = debt;
240 }
241 }
242
243 /// @dev Convenient function to trigger interest accrual for a list of banks.
244 /// @param tokens The list of banks to trigger interest accrual.
245 function accrueAll(address[] memory tokens) external {
246 for (uint idx = 0; idx < tokens.length; idx++) {
247 accrue(tokens[idx]);
248 }
249 }
250
251 /// @dev Return the borrow balance for given position and token without triggering interest accrual.
252 /// @param positionId The position to query for borrow balance.
253 /// @param token The token to query for borrow balance.
254 function borrowBalanceStored(uint positionId, address token) public view override returns (uint) {
255 uint totalDebt = banks[token].totalDebt;
256 uint totalShare = banks[token].totalShare;
257 uint share = positions[positionId].debtShareOf[token];
258 if (share == 0 || totalDebt == 0) {
259 return 0;
260 } else {
261 return share.mul(totalDebt).ceilDiv(totalShare);
262 }
263 }
264
265 /// @dev Trigger interest accrual and return the current borrow balance.
266 /// @param positionId The position to query for borrow balance.
267 /// @param token The token to query for borrow balance.
268 function borrowBalanceCurrent(uint positionId, address token) external override returns (uint) {
269 accrue(token);
270 return borrowBalanceStored(positionId, token);
271 }
272
273 /// @dev Return bank information for the given token.
274 /// @param token The token address to query for bank information.
275 function getBankInfo(address token)
276 external
277 view
278 override
279 returns (
280 bool isListed,
281 address cToken,
282 uint reserve,
283 uint totalDebt,
284 uint totalShare
285 )
286 {
287 Bank storage bank = banks[token];
288 return (bank.isListed, bank.cToken, bank.reserve, bank.totalDebt, bank.totalShare);
289 }
290
291 /// @dev Return position information for the given position id.
292 /// @param positionId The position id to query for position information.
293 function getPositionInfo(uint positionId)
294 public
295 view
296 override
297 returns (
298 address owner,
299 address collToken,
300 uint collId,
301 uint collateralSize
302 )
303 {
304 Position storage pos = positions[positionId];
305 return (pos.owner, pos.collToken, pos.collId, pos.collateralSize);
306 }
Comments to Code: 123 / 671 = 18 %
Tests to Code: 925 / 671 = 138 %