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?(%)
Smart contracts are easy to find.
2. How active is the primary contract? (%)
The primary deployer contract is relatively active at more than 10 transactions a day.
3. Does the protocol have a public software repository? (Y/N)
Inverse has a GitHub.
4. Is there a development history visible? (%)
At 168 commits, there is a clear indication of good software processes here.
5. Is the team public (not anonymous)? (Y/N)
One public GitHub contributor was found, and he confirms that he works at Inverse. Other public members were not found. 50%
This section looks at the software documentation. The document explaining these questions is here.
7. Is the protocol's software architecture documented? (Y/N)
Software architecture documentation can be found at: https://www.inverse.finance/transparency/overview
8. Does the software documentation fully cover the deployed contracts' source code? (%)
Software function documentation is covers by all contracts.
9. Is it possible to trace the documented software to its implementation in the protocol's source code? (%)
There is complete traceability in Inverse Finance's function documentation. It can be found here
10. Has the protocol tested their deployed code? (%)
At a 75% TtC, this protocol has clearly done some proper testing.
11. How covered is the protocol's code? (%)
The protocol's code coverage report is availible here: https://github.com/InverseFinance/FiRM/tree/dev/src/test
12. Does the protocol provide scripts and instructions to run their tests? (Y/N)
No scripts provided.
13. Is there a detailed report of the protocol's test results?(%)
There is no report.
14. Has the protocol undergone Formal Verification? (Y/N)
No formal verification has taken place.
15. Were the smart contracts deployed to a testnet? (Y/N)
The contracts have been deployed to the Goerli testnet, as indicated here: https://inverse-dao.gitbook.io/inverse-finance/inverse-finance/technical/smart-contracts
This section looks at the 3rd party software audits done. It is explained in this document.
16. Is the protocol sufficiently audited? (%)
As part of a recently implemented smart-contract review process, the protocol has undergone several audits in the past few months, including: A comprehensive audit of their new fixed rate lending protocol, FiRM, was conducted on the Code4rena platform through an audit contest. The report for this audit can be found (here.)[https://code4rena.com/reports/2022-10-inverse/} Peckshield performed an audit of the bad debt repayment products and a new INV oracle solution. Their report can be found (here.)[https://drive.google.com/file/d/1LWNG08mib2GcI1WqnMt5IdFoW73QU2F8/view] DeFiMoon conducted an audit of the FiRM contracts before the bug bounty contest. The pre-launch audit report can be found (here.)[https://github.com/Defimoonorg/Audit-Report/blob/main/InverseFinance.pdf]
17. Is the bounty value acceptably high (%)
A bug bounty is offered with hats.finance which at time of writing was a little over 20 K.
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?
The admin control information is documented. It indicates the change capability, except software change capability. It does not indicate the software is immutable, even though it is.
19. Are relevant contracts clearly labelled as upgradeable or immutable? (%)
Code is immutable and is mentioned in the documentation here: https://inverse-dao.gitbook.io/inverse-finance/inverse-finance/technical/smart-contracts
20. Is the type of smart contract ownership clearly indicated? (%)
Contract ownership is indicated here: https://inverse-dao.gitbook.io/inverse-finance/inverse-finance/technical/smart-contracts
21. Are the protocol's smart contract change capabilities described? (%)
Code is immutable. This is mentioned in the smart contract addresses page.
22. Is the protocol's admin control information easy to understand? (%)
This information is in very straightforward language.
23. Is there sufficient Pause Control documentation? (%)
Pause control documentation is located here: https://inverse-dao.gitbook.io/inverse-finance-risk/prevention/incident-response It includes "fire drills" which gives them 100% Drill results should be publicly indicated.
24. Is there sufficient Timelock documentation? (%)
The governance section indicates a 40 hour delay before implementation, allowing owners time to adjust. However this does not appear to be a timelock, but rather an administrative process. This is weaker because a bad actor could bypass this. Therefore we will give this a 60% because it is better than nothing.
25. Is the Timelock of an adequate length? (Y/N)
The governance section indicates a 40 hour delay before implementation, allowing owners time to adjust. 40 hours gives a 50% score.
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? (%)
There is good oracle documentation.
27. Is front running mitigated by this protocol? (Y/N)
An explanation of front-running is found here: https://inverse-dao.gitbook.io/inverse-finance/inverse-finance/firm/faqs
28. Can flashloan attacks be applied to the protocol, and if so, are those flashloan attack risks mitigated? (Y/N)
Flashloan countermeasures are found here: https://inverse-dao.gitbook.io/inverse-finance/inverse-finance/firm/faqs
1pragma solidity ^0.5.16;
2pragma experimental ABIEncoderV2;
3
4interface InvInterface {
5 function getPriorVotes(address account, uint blockNumber) external view returns (uint96);
6 function totalSupply() external view returns (uint256);
7}
8
9interface XinvInterface {
10 function getPriorVotes(address account, uint blockNumber) external view returns (uint96);
11 function totalSupply() external view returns (uint256);
12 function exchangeRateCurrent() external returns (uint);
13}
14
15interface TimelockInterface {
16 function delay() external view returns (uint);
17 function GRACE_PERIOD() external view returns (uint);
18 function setDelay(uint256 delay_) external;
19 function acceptAdmin() external;
20 function setPendingAdmin(address pendingAdmin_) external;
21 function queuedTransactions(bytes32 hash) external view returns (bool);
22 function queueTransaction(address target, uint256 value, string calldata signature, bytes calldata data, uint256 eta) external returns (bytes32);
23 function cancelTransaction(address target, uint256 value, string calldata signature, bytes calldata data, uint256 eta) external;
24 function executeTransaction(address target, uint256 value, string calldata signature, bytes calldata data, uint256 eta) external returns (bytes memory);
25}
26
27contract GovernorMills {
28 /// @notice The name of this contract
29 string public constant name = "Inverse Governor Mills";
30
31 /// @notice The maximum number of actions that can be included in a proposal
32 function proposalMaxOperations() public pure returns (uint) { return 20; } // 20 actions
33
34 /// @notice The delay before voting on a proposal may take place, once proposed
35 function votingDelay() public pure returns (uint) { return 1; } // 1 block
36
37 /// @notice The duration of voting on a proposal, in blocks
38 function votingPeriod() public pure returns (uint) { return 17280; } // ~3 days in blocks (assuming 15s blocks)
39
40 /// @notice The address of the Protocol Timelock
41 TimelockInterface public timelock;
42
43 /// @notice The address of the governance token A
44 InvInterface public inv;
45
46 /// @notice The address of the governance token B
47 XinvInterface public xinv;
48
49 /// @notice The total number of proposals
50 uint256 public proposalCount;
51
52 /// @notice The guardian
53 address public guardian;
54
55 /// @notice proposal threshold
56 uint256 public proposalThreshold = 1000 ether; // 1k INV
57
58 /// @notice The number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed
59 uint256 public quorumVotes = 4000 ether; // 4k INV
60
61 struct Proposal {
62 /// @notice Unique id for looking up a proposal
63 uint id;
64
65 /// @notice Creator of the proposal
66 address proposer;
67
68 /// @notice The timestamp that the proposal will be available for execution, set once the vote succeeds
69 uint eta;
70
71 /// @notice the ordered list of target addresses for calls to be made
72 address[] targets;
73
74 /// @notice The ordered list of values (i.e. msg.value) to be passed to the calls to be made
75 uint[] values;
76
77 /// @notice The ordered list of function signatures to be called
78 string[] signatures;
79
80 /// @notice The ordered list of calldata to be passed to each call
81 bytes[] calldatas;
82
83 /// @notice The block at which voting begins: holders must delegate their votes prior to this block
84 uint startBlock;
85
86 /// @notice The block at which voting ends: votes must be cast prior to this block
87 uint endBlock;
88
89 /// @notice Current number of votes in favor of this proposal
90 uint forVotes;
91
92 /// @notice Current number of votes in opposition to this proposal
93 uint againstVotes;
94
95 /// @notice Flag marking whether the proposal has been canceled
96 bool canceled;
97
98 /// @notice Flag marking whether the proposal has been executed
99 bool executed;
100
101 /// @notice Receipts of ballots for the entire set of voters
102 mapping (address => Receipt) receipts;
103 }
104
105 /// @notice Ballot receipt record for a voter
106 struct Receipt {
107 /// @notice Whether or not a vote has been cast
108 bool hasVoted;
109
110 /// @notice Whether or not the voter supports the proposal
111 bool support;
112
113 /// @notice The number of votes the voter had, which were cast
114 uint96 votes;
115 }
116
117 /// @notice Possible states that a proposal may be in
118 enum ProposalState {
119 Pending,
120 Active,
121 Canceled,
122 Defeated,
123 Succeeded,
124 Queued,
125 Expired,
126 Executed
127 }
128
129 /// @notice The official record of all proposals ever proposed
130 mapping (uint => Proposal) public proposals;
131
132 /// @notice The latest proposal for each proposer
133 mapping (address => uint) public latestProposalIds;
134
135 /// @notice Addresses that can propose without voting power
136 mapping (address => bool) public proposerWhitelist;
137
138 /// @notice proposal id => xinv.exchangeRateCurrent
139 mapping (uint => uint) public xinvExchangeRates;
140
141 /// @notice The EIP-712 typehash for the contract's domain
142 bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");
143
144 /// @notice The EIP-712 typehash for the ballot struct used by the contract
145 bytes32 public constant BALLOT_TYPEHASH = keccak256("Ballot(uint256 proposalId,bool support)");
146
147 /// @notice An event emitted when a new proposal is created
148 event ProposalCreated(uint id, address proposer, address[] targets, uint[] values, string[] signatures, bytes[] calldatas, uint startBlock, uint endBlock, string description);
149
150 /// @notice An event emitted when a vote has been cast on a proposal
151 event VoteCast(address voter, uint proposalId, bool support, uint votes);
152
153 /// @notice An event emitted when a proposal has been canceled
154 event ProposalCanceled(uint id);
155
156 /// @notice An event emitted when a proposal has been queued in the Timelock
157 event ProposalQueued(uint id, uint eta);
158
159 /// @notice An event emitted when a proposal has been executed in the Timelock
160 event ProposalExecuted(uint id);
161
162 /// @notice An event emitted when a new guardian has been set
163 event NewGuardian(address guardian);
164
165 /// @notice An event emitted when proposal threshold is updated
166 event ProposalThresholdUpdated(uint256 oldThreshold, uint256 newThreshold);
167
168 /// @notice An event emitted when proposal quorum is updated
169 event QuorumUpdated(uint256 oldQuorum, uint256 newQuorum);
170
171 /// @notice An event emitted when an address is added or removed from the proposer whitelist
172 event ProposerWhitelistUpdated(address proposer, bool value);
173
174 constructor(TimelockInterface timelock_, InvInterface inv_, XinvInterface xinv_) public {
175 timelock = timelock_;
176 inv = inv_;
177 xinv = xinv_;
178 guardian = msg.sender;
179 }
180
181 function _getPriorVotes(address _proposer, uint256 _blockNumber, uint256 _exchangeRate) internal view returns (uint96) {
182 uint96 invPriorVotes = inv.getPriorVotes(_proposer, _blockNumber);
183 uint96 xinvPriorVotes = uint96(
184 (
185 uint256(
186 xinv.getPriorVotes(_proposer, _blockNumber)
187 ) * _exchangeRate
188 ) / 1 ether
189 );
190
191 return add96(invPriorVotes, xinvPriorVotes);
192 }
193
194 function setGuardian(address _newGuardian) public {
195 require(msg.sender == guardian, "GovernorMills::setGuardian: only guardian");
196 guardian = _newGuardian;
197
198 emit NewGuardian(guardian);
199 }
200
201 /**
202 * @notice Add new pending admin to queue
203 * @param newPendingAdmin The new admin
204 * @param eta ETA
205 */
206 function __queueSetTimelockPendingAdmin(address newPendingAdmin, uint256 eta) public {
207 require(msg.sender == guardian, "GovernorMills::__queueSetTimelockPendingAdmin: only guardian");
208 timelock.queueTransaction(address(timelock), 0, "setPendingAdmin(address)", abi.encode(newPendingAdmin), eta);
209 }
210
211 function __executeSetTimelockPendingAdmin(address newPendingAdmin, uint256 eta) public {
212 require(msg.sender == guardian, "GovernorMills::__executeSetTimelockPendingAdmin: only guardian");
213 timelock.executeTransaction(address(timelock), 0, "setPendingAdmin(address)", abi.encode(newPendingAdmin), eta);
214 }
215
216 function propose(address[] memory targets, uint[] memory values, string[] memory signatures, bytes[] memory calldatas, string memory description) public returns (uint) {
217 require(_getPriorVotes(msg.sender, sub256(block.number, 1), xinv.exchangeRateCurrent()) > proposalThreshold || proposerWhitelist[msg.sender], "GovernorMills::propose: proposer votes below proposal threshold");
218 require(targets.length == values.length && targets.length == signatures.length && targets.length == calldatas.length, "GovernorMills::propose: proposal function information arity mismatch");
219 require(targets.length != 0, "GovernorMills::propose: must provide actions");
220 require(targets.length <= proposalMaxOperations(), "GovernorMills::propose: too many actions");
221
222 uint latestProposalId = latestProposalIds[msg.sender];
223 if (latestProposalId != 0) {
224 ProposalState proposersLatestProposalState = state(latestProposalId);
225 require(proposersLatestProposalState != ProposalState.Active, "GovernorMills::propose: one live proposal per proposer, found an already active proposal");
226 require(proposersLatestProposalState != ProposalState.Pending, "GovernorMills::propose: one live proposal per proposer, found an already pending proposal");
227 }
228
229 uint startBlock = add256(block.number, votingDelay());
230 uint endBlock = add256(startBlock, votingPeriod());
231
232 proposalCount++;
233 Proposal memory newProposal = Proposal({
234 id: proposalCount,
235 proposer: msg.sender,
236 eta: 0,
237 targets: targets,
238 values: values,
239 signatures: signatures,
240 calldatas: calldatas,
241 startBlock: startBlock,
242 endBlock: endBlock,
243 forVotes: 0,
244 againstVotes: 0,
245 canceled: false,
246 executed: false
247 });
248
249 proposals[newProposal.id] = newProposal;
250 xinvExchangeRates[newProposal.id] = xinv.exchangeRateCurrent();
251 latestProposalIds[newProposal.proposer] = newProposal.id;
252
253 emit ProposalCreated(newProposal.id, msg.sender, targets, values, signatures, calldatas, startBlock, endBlock, description);
254 return newProposal.id;
255 }
Tests to Code: 83 / 544 = 15 %