1 Summary
ConsenSys Diligence conducted a security audit on Orchid’s batch send smart contract used for multiple disbursements of ether and ERC20 tokens in a single transaction.
1.1 Orchid’s OXT Token Information
ConsenSys Diligence prepared this Report on behalf of Orchid Labs (“Orchid”) to summarize the results of our security audit, which was limited to a technical audit of Orchid’s smart contracts (see the Audit Scope for more details). We understand Orchid intends to use these smart contracts to release OXT tokens to its buyers. Note that the actual release/distribution of OXT tokens will be managed by Orchid in accordance with its policies and procedures.
Please see the links below, which were supplied by Orchid, for more information. ConsenSys Diligence is not responsible for the information included in these links or any transactions contemplated by Orchid.
Orchid OXT token contract:
OXT release schedule:
Orchid Whitepaper, including description of OXT:
https://www.orchid.com/assets/whitepaper/whitepaper.pdf
Orchid’s OXT web portal:
2 Audit Scope
This audit covered the following files:
File Name | SHA-1 Hash |
---|---|
code/BitwaveMultiSend.sol | 0480a2253c7af2ff9c8f0644626a93c72a8efa98 |
3 Key Observations/Recommendations
- The codebase is small enough that the lack of comments or documentation is not detrimental to the audit process.
- The development team is advised to create at least a minimal test suite covering the happy paths of the batch sends with end-to-end testing. Still, 100% code coverage is desirable.
- Since the files imported in the smart contract in scope for the audit were not provided, the audit team made some assumptions about their contents to analyze the behavior of the smart contract with these external components. The assumed files can be found in the Appendix section at the end of the report.
4 Security Specification
This section describes, from a security perspective, the expected behavior of the system under audit. It is not a substitute for documentation. The purpose of this section is to identify specific security properties that were validated by the audit team.
4.1 Important Security Properties
The following is a non-exhaustive list of security properties that were verified in this audit:
- Funds are incapable of being locked in the contract as a result of a send.
- The owner is the only actor capable of utilizing the smart contract.
5 Issues
Each issue has an assigned severity:
- Minor issues are subjective in nature. They are typically suggestions around best practices or readability. Code maintainers should use their own judgment as to whether to address such issues.
- Medium issues are objective in nature but are not security vulnerabilities. These should be addressed unless there is a clear reason not to.
- Major issues are security vulnerabilities that may not be directly exploitable or may require certain conditions in order to be exploited. All major issues should be addressed.
- Critical issues are directly exploitable security vulnerabilities that need to be fixed.
5.1 Warning about ERC20 handling function
Description
There is something worth bringing up for discussion in the ERC20 disbursement function.
code/BitwaveMultiSend.sol:L55
assert(token.transferFrom(msg.sender, _to[i], _value[i]) == true);
In the above presented line, the external call is being compared to a truthful boolean. And, even though, this is clearly part of the ERC20 specification there have historically been cases where tokens with sizeable market caps and liquidity have erroneously not implemented return values in any of the transfer functions.
The question presents itself as to whether these non-ERC20-conforming tokens are meant to be supported or not.
The audit team believes that the purpose of this smart contract is to disburse OXT tokens and therefore, since its development was under the umbrella of the Orchid team, absolutely no security concerns should arise from this issue.
5.2 Discussion on the permissioning of send functions
Description
Since the disbursement of funds is all made atomically (i.e., the Ether funds held by the smart contract are transient) there is no need to permission the function with the restrictedToOwner
modifier.
Even in the case of ERC20 tokens, there is no need to permission the function since the smart contract can only spend allowance attributed to it by the caller (msg.sender
).
This being said there is value in permissioning this contract, specifically if attribution of the deposited funds in readily available tools like Etherscan is important. Because turning this into a publicly available tool for batch sends of Ether and ERC20 tokens would mean that someone could wrongly attribute some disbursement to Orchid Labs should they be ignorant to this fact.
A possible solution to this problem would be the usage of events to properly attribute the disbursements but it is, indeed, an additional burden to carefully analyse these for proper attribution.
5.3 Improve function visibility Minor
Description
The following methods are not called internally in the token contract and visibility can, therefore, be restricted to external
rather than public
. This is more gas efficient because less code is emitted and data does not need to be copied into memory. It also makes functions a bit simpler to reason about because there’s no need to worry about the possibility of internal calls.
BitwaveMultiSend.sendEth()
BitwaveMultiSend.sendErc20()
Recommendation
Change visibility of these methods to external
.
5.4 Ether send function remainder handling Minor
Description
The Ether send function depicted below implements logic to reimburse the sender if an extraneous amount is left in the contract after the disbursement.
code/BitwaveMultiSend.sol:L22-L43
function sendEth(address payable [] memory _to, uint256[] memory _value) public restrictedToOwner payable returns (bool _success) {
// input validation
require(_to.length == _value.length);
require(_to.length <= 255);
// count values for refunding sender
uint256 beforeValue = msg.value;
uint256 afterValue = 0;
// loop through to addresses and send value
for (uint8 i = 0; i < _to.length; i++) {
afterValue = afterValue.add(_value[i]);
assert(_to[i].send(_value[i]));
}
// send back remaining value to sender
uint256 remainingValue = beforeValue.sub(afterValue);
if (remainingValue > 0) {
assert(msg.sender.send(remainingValue));
}
return true;
}
It is also the only place where the SafeMath
dependency is being used. More specifically to check there was no underflow in the arithmetic adding up the disbursed amounts.
However, since the individual sends would revert themselves should more Ether than what was available in the balance be specified these protection measures seem unnecessary.
Not only the above is true but the current codebase does not allow to take funds locked within the contract out in the off chance someone forced funds into this smart contract (e.g., by self-destructing some other smart contract containing funds into this one).
Recommendation
The easiest way to handle both retiring SafeMath
and returning locked funds would be to phase out all the intra-function arithmetic and just transferring address(this).balance
to msg.sender
at the end of the disbursement.
Since all the funds in there are meant to be from the caller of the function this serves the purpose of returning extraneous funds to him well and, adding to that, it allows for some front-running fun if someone “self-destructed” funds to this smart contract by mistake.
5.5 Unneeded type cast of contract type Minor
Description
The typecast being done on the address
parameter in the lien below is unneeded.
code/BitwaveMultiSend.sol:L51
ERC20 token = ERC20(_tokenAddress);
Recommendation
Assign the right type at the function parameter definition like so:
function sendErc20(ERC20 _tokenAddress, address[] memory _to, uint256[] memory _value) public restrictedToOwner returns (bool _success) {
5.6 Inadequate use of assert
Minor
Description
The usage of require
vs assert
has always been a matter of discussion because of the fine lines distinguishing these transaction-terminating expressions.
However, the usage of the assert
syntax in this case is not the most appropriate.
Borrowing the explanation from the latest solidity docs (v. https://solidity.readthedocs.io/en/latest/control-structures.html#id4) :
The assert function should only be used to test for internal errors, and to check invariants.
Since assert-style exceptions (using the 0xfe
opcode) consume all gas available to the call and require-style ones (using the 0xfd
opcode) do not since the Metropolis release when the REVERT
instruction was added, the usage of require
in the lines depicted in the examples section would only result in gas savings and the same security assumptions.
In this case, even though the calls are being made to external contracts the supposedly abide to a predefined specification, this is by no means an invariant of the presented system since the component is external to the built system and its integrity cannot be formally verified.
Examples
code/BitwaveMultiSend.sol:L34
assert(_to[i].send(_value[i]));
code/BitwaveMultiSend.sol:L40
assert(msg.sender.send(remainingValue));
code/BitwaveMultiSend.sol:L55
assert(token.transferFrom(msg.sender, _to[i], _value[i]) == true);
Recommendation
Exchange the assert
statements for require
ones.
6 Tool-Based Analysis
Several tools were used to perform automated analysis of the reviewed contracts. These issues were reviewed by the audit team, and relevant issues are listed in the Issue Details section.
6.1 MythX
MythX is a security analysis API for Ethereum smart contracts. It performs multiple types of analysis, including fuzzing and symbolic execution, to detect many common vulnerability types. The tool was used for automated vulnerability discovery for all audited contracts and libraries. More details on MythX can be found at mythx.io.
The output of a MythX Full Mode analysis was reviewed by the audit team and no relevant issues were raised as part of the process.
6.2 Ethlint
Ethlint is an open source project for linting Solidity code. Only security-related issues were reviewed by the audit team.
Below is the raw output of the Ethlint vulnerability scan:
code/BitwaveMultiSend.sol
3:7 error "./ERC20.sol": Import statements must use double quotes only. quotes
22:16 error There should be no whitespace between "address payable" and the opening square bracket. array-declarations
24:8 warning Provide an error message for require() error-reason
25:8 warning Provide an error message for require() error-reason
34:19 warning Consider using 'transfer' in place of 'send'. security/no-send
40:19 warning Consider using 'transfer' in place of 'send'. security/no-send
47:8 warning Provide an error message for require() error-reason
48:8 warning Provide an error message for require() error-reason
✖ 2 errors, 6 warnings found.
6.3 Surya
Surya is a utility tool for smart contract systems. It provides a number of visual outputs and information about the structure of smart contracts. It also supports querying the function call graph in multiple ways to aid in the manual inspection and control flow analysis of contracts.
Below is a complete list of functions with their visibility and modifiers:
Contract | Type | Bases | ||
---|---|---|---|---|
└ | Function Name | Visibility | Mutability | Modifiers |
BitwaveMultiSend | Implementation | |||
└ | Public ❗️ | 🛑 | NO❗️ | |
└ | sendEth | Public ❗️ | 💵 | restrictedToOwner |
└ | sendErc20 | Public ❗️ | 🛑 | restrictedToOwner |
Legend
Symbol | Meaning |
---|---|
🛑 | Function can modify state |
💵 | Function is payable |
Appendix
A.1.1 Assumed out-of-scope, imported files
SafeMath.sol
1// File: openzeppelin-solidity/contracts/math/SafeMath.sol
2
3pragma solidity ^0.5.0;
4
5/**
6 * @dev Wrappers over Solidity's arithmetic operations with added overflow
7 * checks.
8 *
9 * Arithmetic operations in Solidity wrap on overflow. This can easily result
10 * in bugs, because programmers usually assume that an overflow raises an
11 * error, which is the standard behavior in high level programming languages.
12 * `SafeMath` restores this intuition by reverting the transaction when an
13 * operation overflows.
14 *
15 * Using this library instead of the unchecked operations eliminates an entire
16 * class of bugs, so it's recommended to use it always.
17 */
18library SafeMath {
19 /**
20 * @dev Returns the addition of two unsigned integers, reverting on
21 * overflow.
22 *
23 * Counterpart to Solidity's `+` operator.
24 *
25 * Requirements:
26 * - Addition cannot overflow.
27 */
28 function add(uint256 a, uint256 b) internal pure returns (uint256) {
29 uint256 c = a + b;
30 require(c >= a, "SafeMath: addition overflow");
31
32 return c;
33 }
34
35 /**
36 * @dev Returns the subtraction of two unsigned integers, reverting on
37 * overflow (when the result is negative).
38 *
39 * Counterpart to Solidity's `-` operator.
40 *
41 * Requirements:
42 * - Subtraction cannot overflow.
43 */
44 function sub(uint256 a, uint256 b) internal pure returns (uint256) {
45 require(b <= a, "SafeMath: subtraction overflow");
46 uint256 c = a - b;
47
48 return c;
49 }
50
51 /**
52 * @dev Returns the multiplication of two unsigned integers, reverting on
53 * overflow.
54 *
55 * Counterpart to Solidity's `*` operator.
56 *
57 * Requirements:
58 * - Multiplication cannot overflow.
59 */
60 function mul(uint256 a, uint256 b) internal pure returns (uint256) {
61 // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
62 // benefit is lost if 'b' is also tested.
63 // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
64 if (a == 0) {
65 return 0;
66 }
67
68 uint256 c = a * b;
69 require(c / a == b, "SafeMath: multiplication overflow");
70
71 return c;
72 }
73
74 /**
75 * @dev Returns the integer division of two unsigned integers. Reverts on
76 * division by zero. The result is rounded towards zero.
77 *
78 * Counterpart to Solidity's `/` operator. Note: this function uses a
79 * `revert` opcode (which leaves remaining gas untouched) while Solidity
80 * uses an invalid opcode to revert (consuming all remaining gas).
81 *
82 * Requirements:
83 * - The divisor cannot be zero.
84 */
85 function div(uint256 a, uint256 b) internal pure returns (uint256) {
86 // Solidity only automatically asserts when dividing by 0
87 require(b > 0, "SafeMath: division by zero");
88 uint256 c = a / b;
89 // assert(a == b * c + a % b); // There is no case in which this doesn't hold
90
91 return c;
92 }
93
94 /**
95 * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
96 * Reverts when dividing by zero.
97 *
98 * Counterpart to Solidity's `%` operator. This function uses a `revert`
99 * opcode (which leaves remaining gas untouched) while Solidity uses an
100 * invalid opcode to revert (consuming all remaining gas).
101 *
102 * Requirements:
103 * - The divisor cannot be zero.
104 */
105 function mod(uint256 a, uint256 b) internal pure returns (uint256) {
106 require(b != 0, "SafeMath: modulo by zero");
107 return a % b;
108 }
109}
ERC20.sol
1// File: openzeppelin-solidity/contracts/token/ERC20/IERC20.sol
2
3pragma solidity ^0.5.0;
4
5/**
6 * @dev Interface of the ERC20 standard as defined in the EIP. Does not include
7 * the optional functions; to access them see `ERC20Detailed`.
8 */
9interface IERC20 {
10 /**
11 * @dev Returns the amount of tokens in existence.
12 */
13 function totalSupply() external view returns (uint256);
14
15 /**
16 * @dev Returns the amount of tokens owned by `account`.
17 */
18 function balanceOf(address account) external view returns (uint256);
19
20 /**
21 * @dev Moves `amount` tokens from the caller's account to `recipient`.
22 *
23 * Returns a boolean value indicating whether the operation succeeded.
24 *
25 * Emits a `Transfer` event.
26 */
27 function transfer(address recipient, uint256 amount) external returns (bool);
28
29 /**
30 * @dev Returns the remaining number of tokens that `spender` will be
31 * allowed to spend on behalf of `owner` through `transferFrom`. This is
32 * zero by default.
33 *
34 * This value changes when `approve` or `transferFrom` are called.
35 */
36 function allowance(address owner, address spender) external view returns (uint256);
37
38 /**
39 * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
40 *
41 * Returns a boolean value indicating whether the operation succeeded.
42 *
43 * > Beware that changing an allowance with this method brings the risk
44 * that someone may use both the old and the new allowance by unfortunate
45 * transaction ordering. One possible solution to mitigate this race
46 * condition is to first reduce the spender's allowance to 0 and set the
47 * desired value afterwards:
48 * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
49 *
50 * Emits an `Approval` event.
51 */
52 function approve(address spender, uint256 amount) external returns (bool);
53
54 /**
55 * @dev Moves `amount` tokens from `sender` to `recipient` using the
56 * allowance mechanism. `amount` is then deducted from the caller's
57 * allowance.
58 *
59 * Returns a boolean value indicating whether the operation succeeded.
60 *
61 * Emits a `Transfer` event.
62 */
63 function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
64
65 /**
66 * @dev Emitted when `value` tokens are moved from one account (`from`) to
67 * another (`to`).
68 *
69 * Note that `value` may be zero.
70 */
71 event Transfer(address indexed from, address indexed to, uint256 value);
72
73 /**
74 * @dev Emitted when the allowance of a `spender` for an `owner` is set by
75 * a call to `approve`. `value` is the new allowance.
76 */
77 event Approval(address indexed owner, address indexed spender, uint256 value);
78}
79
80// File: openzeppelin-solidity/contracts/token/ERC20/ERC20.sol
81
82pragma solidity ^0.5.0;
83
84
85
86/**
87 * @dev Implementation of the `IERC20` interface.
88 *
89 * This implementation is agnostic to the way tokens are created. This means
90 * that a supply mechanism has to be added in a derived contract using `_mint`.
91 * For a generic mechanism see `ERC20Mintable`.
92 *
93 * *For a detailed writeup see our guide [How to implement supply
94 * mechanisms](https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226).*
95 *
96 * We have followed general OpenZeppelin guidelines: functions revert instead
97 * of returning `false` on failure. This behavior is nonetheless conventional
98 * and does not conflict with the expectations of ERC20 applications.
99 *
100 * Additionally, an `Approval` event is emitted on calls to `transferFrom`.
101 * This allows applications to reconstruct the allowance for all accounts just
102 * by listening to said events. Other implementations of the EIP may not emit
103 * these events, as it isn't required by the specification.
104 *
105 * Finally, the non-standard `decreaseAllowance` and `increaseAllowance`
106 * functions have been added to mitigate the well-known issues around setting
107 * allowances. See `IERC20.approve`.
108 */
109contract ERC20 is IERC20 {
110 using SafeMath for uint256;
111
112 mapping (address => uint256) private _balances;
113
114 mapping (address => mapping (address => uint256)) private _allowances;
115
116 uint256 private _totalSupply;
117
118 /**
119 * @dev See `IERC20.totalSupply`.
120 */
121 function totalSupply() public view returns (uint256) {
122 return _totalSupply;
123 }
124
125 /**
126 * @dev See `IERC20.balanceOf`.
127 */
128 function balanceOf(address account) public view returns (uint256) {
129 return _balances[account];
130 }
131
132 /**
133 * @dev See `IERC20.transfer`.
134 *
135 * Requirements:
136 *
137 * - `recipient` cannot be the zero address.
138 * - the caller must have a balance of at least `amount`.
139 */
140 function transfer(address recipient, uint256 amount) public returns (bool) {
141 _transfer(msg.sender, recipient, amount);
142 return true;
143 }
144
145 /**
146 * @dev See `IERC20.allowance`.
147 */
148 function allowance(address owner, address spender) public view returns (uint256) {
149 return _allowances[owner][spender];
150 }
151
152 /**
153 * @dev See `IERC20.approve`.
154 *
155 * Requirements:
156 *
157 * - `spender` cannot be the zero address.
158 */
159 function approve(address spender, uint256 value) public returns (bool) {
160 _approve(msg.sender, spender, value);
161 return true;
162 }
163
164 /**
165 * @dev See `IERC20.transferFrom`.
166 *
167 * Emits an `Approval` event indicating the updated allowance. This is not
168 * required by the EIP. See the note at the beginning of `ERC20`;
169 *
170 * Requirements:
171 * - `sender` and `recipient` cannot be the zero address.
172 * - `sender` must have a balance of at least `value`.
173 * - the caller must have allowance for `sender`'s tokens of at least
174 * `amount`.
175 */
176 function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
177 _transfer(sender, recipient, amount);
178 _approve(sender, msg.sender, _allowances[sender][msg.sender].sub(amount));
179 return true;
180 }
181
182 /**
183 * @dev Atomically increases the allowance granted to `spender` by the caller.
184 *
185 * This is an alternative to `approve` that can be used as a mitigation for
186 * problems described in `IERC20.approve`.
187 *
188 * Emits an `Approval` event indicating the updated allowance.
189 *
190 * Requirements:
191 *
192 * - `spender` cannot be the zero address.
193 */
194 function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
195 _approve(msg.sender, spender, _allowances[msg.sender][spender].add(addedValue));
196 return true;
197 }
198
199 /**
200 * @dev Atomically decreases the allowance granted to `spender` by the caller.
201 *
202 * This is an alternative to `approve` that can be used as a mitigation for
203 * problems described in `IERC20.approve`.
204 *
205 * Emits an `Approval` event indicating the updated allowance.
206 *
207 * Requirements:
208 *
209 * - `spender` cannot be the zero address.
210 * - `spender` must have allowance for the caller of at least
211 * `subtractedValue`.
212 */
213 function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
214 _approve(msg.sender, spender, _allowances[msg.sender][spender].sub(subtractedValue));
215 return true;
216 }
217
218 /**
219 * @dev Moves tokens `amount` from `sender` to `recipient`.
220 *
221 * This is internal function is equivalent to `transfer`, and can be used to
222 * e.g. implement automatic token fees, slashing mechanisms, etc.
223 *
224 * Emits a `Transfer` event.
225 *
226 * Requirements:
227 *
228 * - `sender` cannot be the zero address.
229 * - `recipient` cannot be the zero address.
230 * - `sender` must have a balance of at least `amount`.
231 */
232 function _transfer(address sender, address recipient, uint256 amount) internal {
233 require(sender != address(0), "ERC20: transfer from the zero address");
234 require(recipient != address(0), "ERC20: transfer to the zero address");
235
236 _balances[sender] = _balances[sender].sub(amount);
237 _balances[recipient] = _balances[recipient].add(amount);
238 emit Transfer(sender, recipient, amount);
239 }
240
241 /** @dev Creates `amount` tokens and assigns them to `account`, increasing
242 * the total supply.
243 *
244 * Emits a `Transfer` event with `from` set to the zero address.
245 *
246 * Requirements
247 *
248 * - `to` cannot be the zero address.
249 */
250 function _mint(address account, uint256 amount) internal {
251 require(account != address(0), "ERC20: mint to the zero address");
252
253 _totalSupply = _totalSupply.add(amount);
254 _balances[account] = _balances[account].add(amount);
255 emit Transfer(address(0), account, amount);
256 }
257
258 /**
259 * @dev Destoys `amount` tokens from `account`, reducing the
260 * total supply.
261 *
262 * Emits a `Transfer` event with `to` set to the zero address.
263 *
264 * Requirements
265 *
266 * - `account` cannot be the zero address.
267 * - `account` must have at least `amount` tokens.
268 */
269 function _burn(address account, uint256 value) internal {
270 require(account != address(0), "ERC20: burn from the zero address");
271
272 _totalSupply = _totalSupply.sub(value);
273 _balances[account] = _balances[account].sub(value);
274 emit Transfer(account, address(0), value);
275 }
276
277 /**
278 * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
279 *
280 * This is internal function is equivalent to `approve`, and can be used to
281 * e.g. set automatic allowances for certain subsystems, etc.
282 *
283 * Emits an `Approval` event.
284 *
285 * Requirements:
286 *
287 * - `owner` cannot be the zero address.
288 * - `spender` cannot be the zero address.
289 */
290 function _approve(address owner, address spender, uint256 value) internal {
291 require(owner != address(0), "ERC20: approve from the zero address");
292 require(spender != address(0), "ERC20: approve to the zero address");
293
294 _allowances[owner][spender] = value;
295 emit Approval(owner, spender, value);
296 }
297
298 /**
299 * @dev Destoys `amount` tokens from `account`.`amount` is then deducted
300 * from the caller's allowance.
301 *
302 * See `_burn` and `_approve`.
303 */
304 function _burnFrom(address account, uint256 amount) internal {
305 _burn(account, amount);
306 _approve(account, msg.sender, _allowances[account][msg.sender].sub(amount));
307 }
308}
Appendix 2 - Disclosure
ConsenSys Diligence (“CD”) typically receives compensation from one or more clients (the “Clients”) for performing the analysis contained in these reports (the “Reports”). The Reports may be distributed through other means, including via ConsenSys publications and other distributions.
The Reports are not an endorsement or indictment of any particular project or team, and the Reports do not guarantee the security of any particular project. This Report does not consider, and should not be interpreted as considering or having any bearing on, the potential economics of a token, token sale or any other product, service or other asset. Cryptographic tokens are emergent technologies and carry with them high levels of technical risk and uncertainty. No Report provides any warranty or representation to any Third-Party in any respect, including regarding the bugfree nature of code, the business model or proprietors of any such business model, and the legal compliance of any such business. No third party should rely on the Reports in any way, including for the purpose of making any decisions to buy or sell any token, product, service or other asset. Specifically, for the avoidance of doubt, this Report does not constitute investment advice, is not intended to be relied upon as investment advice, is not an endorsement of this project or team, and it is not a guarantee as to the absolute security of the project. CD owes no duty to any Third-Party by virtue of publishing these Reports.
PURPOSE OF REPORTS The Reports and the analysis described therein are created solely for Clients and published with their consent. The scope of our review is limited to a review of Solidity code and only the Solidity code we note as being within the scope of our review within this report. The Solidity language itself remains under development and is subject to unknown risks and flaws. The review does not extend to the compiler layer, or any other areas beyond Solidity that could present security risks. Cryptographic tokens are emergent technologies and carry with them high levels of technical risk and uncertainty.
CD makes the Reports available to parties other than the Clients (i.e., “third parties”) – on its website. CD hopes that by making these analyses publicly available, it can help the blockchain ecosystem develop technical best practices in this rapidly evolving area of innovation.
LINKS TO OTHER WEB SITES FROM THIS WEB SITE You may, through hypertext or other computer links, gain access to web sites operated by persons other than ConsenSys and CD. Such hyperlinks are provided for your reference and convenience only, and are the exclusive responsibility of such web sites’ owners. You agree that ConsenSys and CD are not responsible for the content or operation of such Web sites, and that ConsenSys and CD shall have no liability to you or any other person or entity for the use of third party Web sites. Except as described below, a hyperlink from this web Site to another web site does not imply or mean that ConsenSys and CD endorses the content on that Web site or the operator or operations of that site. You are solely responsible for determining the extent to which you may use any content at any other web sites to which you link from the Reports. ConsenSys and CD assumes no responsibility for the use of third party software on the Web Site and shall have no liability whatsoever to any person or entity for the accuracy or completeness of any outcome generated by such software.
TIMELINESS OF CONTENT The content contained in the Reports is current as of the date appearing on the Report and is subject to change without notice. Unless indicated otherwise, by ConsenSys and CD.