Orchid Multisend

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:

https://github.com/OrchidTechnologies/orchid/blob/3187d0716f16eeb59552d2c103c02efd5a530a76/tok-ethereum/token.sol

OXT release schedule:

https://github.com/OrchidTechnologies/orchid/blob/3187d0716f16eeb59552d2c103c02efd5a530a76/tst-ethereum/distributor/release_schedule.pdf

Orchid Whitepaper, including description of OXT:

https://www.orchid.com/assets/whitepaper/whitepaper.pdf

Orchid’s OXT web portal:

https://www.orchid.com/oxt

2 Audit Scope

This audit covered the following files:

File Name SHA-1 Hash
code/BitwaveMultiSend.sol 0480a2253c7af2ff9c8f0644626a93c72a8efa98

3 Key Observations/Recommendations

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:

5 Issues

Each issue has an assigned severity:

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.