// File: solidity\contracts\utility\interfaces\IOwned.sol
pragma solidity 0.4.26;
/*
Owned contract interface
*/
contract IOwned {
// this function isn't abstract since the compiler emits automatically generated getter functions as external
function owner() public view returns (address) {this;}
function transferOwnership(address _newOwner) public;
function acceptOwnership() public;
}
// File: solidity\contracts\token\interfaces\IERC20Token.sol
pragma solidity 0.4.26;
/*
ERC20 Standard Token interface
*/
contract IERC20Token {
// these functions aren't abstract since the compiler emits automatically generated getter functions as external
function name() public view returns (string) {this;}
function symbol() public view returns (string) {this;}
function decimals() public view returns (uint8) {this;}
function totalSupply() public view returns (uint256) {this;}
function balanceOf(address _owner) public view returns (uint256) {_owner; this;}
function allowance(address _owner, address _spender) public view returns (uint256) {_owner; _spender; this;}
function transfer(address _to, uint256 _value) public returns (bool success);
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success);
function approve(address _spender, uint256 _value) public returns (bool success);
}
// File: solidity\contracts\utility\interfaces\ITokenHolder.sol
pragma solidity 0.4.26;
/*
Token Holder interface
*/
contract ITokenHolder is IOwned {
function withdrawTokens(IERC20Token _token, address _to, uint256 _amount) public;
}
// File: solidity\contracts\converter\interfaces\IConverterAnchor.sol
pragma solidity 0.4.26;
/*
Converter Anchor interface
*/
contract IConverterAnchor is IOwned, ITokenHolder {
}
// File: solidity\contracts\utility\interfaces\IWhitelist.sol
pragma solidity 0.4.26;
/*
Whitelist interface
*/
contract IWhitelist {
function isWhitelisted(address _address) public view returns (bool);
}
// File: solidity\contracts\converter\interfaces\IConverter.sol
pragma solidity 0.4.26;
/*
Converter interface
*/
contract IConverter is IOwned {
function converterType() public pure returns (uint16);
function anchor() public view returns (IConverterAnchor) {this;}
function isActive() public view returns (bool);
function targetAmountAndFee(IERC20Token _sourceToken, IERC20Token _targetToken, uint256 _amount) public view returns (uint256, uint256);
function convert(IERC20Token _sourceToken,
IERC20Token _targetToken,
uint256 _amount,
address _trader,
address _beneficiary) public payable returns (uint256);
function conversionWhitelist() public view returns (IWhitelist) {this;}
function conversionFee() public view returns (uint32) {this;}
function maxConversionFee() public view returns (uint32) {this;}
function reserveBalance(IERC20Token _reserveToken) public view returns (uint256);
function() external payable;
function transferAnchorOwnership(address _newOwner) public;
function acceptAnchorOwnership() public;
function setConversionFee(uint32 _conversionFee) public;
function setConversionWhitelist(IWhitelist _whitelist) public;
function withdrawTokens(IERC20Token _token, address _to, uint256 _amount) public;
function withdrawETH(address _to) public;
function addReserve(IERC20Token _token, uint32 _ratio) public;
// deprecated, backward compatibility
function token() public view returns (IConverterAnchor);
function transferTokenOwnership(address _newOwner) public;
function acceptTokenOwnership() public;
function connectors(address _address) public view returns (uint256, uint32, bool, bool, bool);
function getConnectorBalance(IERC20Token _connectorToken) public view returns (uint256);
function connectorTokens(uint256 _index) public view returns (IERC20Token);
function connectorTokenCount() public view returns (uint16);
}
// File: solidity\contracts\converter\interfaces\IConverterUpgrader.sol
pragma solidity 0.4.26;
/*
Converter Upgrader interface
*/
contract IConverterUpgrader {
function upgrade(bytes32 _version) public;
function upgrade(uint16 _version) public;
}
// File: solidity\contracts\converter\interfaces\IBancorFormula.sol
pragma solidity 0.4.26;
/*
Bancor Formula interface
*/
contract IBancorFormula {
function purchaseTargetAmount(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveWeight,
uint256 _amount)
public view returns (uint256);
function saleTargetAmount(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveWeight,
uint256 _amount)
public view returns (uint256);
function crossReserveTargetAmount(uint256 _sourceReserveBalance,
uint32 _sourceReserveWeight,
uint256 _targetReserveBalance,
uint32 _targetReserveWeight,
uint256 _amount)
public view returns (uint256);
function fundCost(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveRatio,
uint256 _amount)
public view returns (uint256);
function fundSupplyAmount(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveRatio,
uint256 _amount)
public view returns (uint256);
function liquidateReserveAmount(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveRatio,
uint256 _amount)
public view returns (uint256);
function balancedWeights(uint256 _primaryReserveStakedBalance,
uint256 _primaryReserveBalance,
uint256 _secondaryReserveBalance,
uint256 _reserveRateNumerator,
uint256 _reserveRateDenominator)
public view returns (uint32, uint32);
}
// File: solidity\contracts\IBancorNetwork.sol
pragma solidity 0.4.26;
/*
Bancor Network interface
*/
contract IBancorNetwork {
function convert2(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn,
address _affiliateAccount,
uint256 _affiliateFee
) public payable returns (uint256);
function claimAndConvert2(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn,
address _affiliateAccount,
uint256 _affiliateFee
) public returns (uint256);
function convertFor2(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn,
address _for,
address _affiliateAccount,
uint256 _affiliateFee
) public payable returns (uint256);
function claimAndConvertFor2(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn,
address _for,
address _affiliateAccount,
uint256 _affiliateFee
) public returns (uint256);
// deprecated, backward compatibility
function convert(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn
) public payable returns (uint256);
// deprecated, backward compatibility
function claimAndConvert(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn
) public returns (uint256);
// deprecated, backward compatibility
function convertFor(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn,
address _for
) public payable returns (uint256);
// deprecated, backward compatibility
function claimAndConvertFor(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn,
address _for
) public returns (uint256);
}
// File: solidity\contracts\utility\Owned.sol
pragma solidity 0.4.26;
/**
* @dev Provides support and utilities for contract ownership
*/
contract Owned is IOwned {
address public owner;
address public newOwner;
/**
* @dev triggered when the owner is updated
*
* @param _prevOwner previous owner
* @param _newOwner new owner
*/
event OwnerUpdate(address indexed _prevOwner, address indexed _newOwner);
/**
* @dev initializes a new Owned instance
*/
constructor() public {
owner = msg.sender;
}
// allows execution by the owner only
modifier ownerOnly {
_ownerOnly();
_;
}
// error message binary size optimization
function _ownerOnly() internal view {
require(msg.sender == owner, "ERR_ACCESS_DENIED");
}
/**
* @dev allows transferring the contract ownership
* the new owner still needs to accept the transfer
* can only be called by the contract owner
*
* @param _newOwner new contract owner
*/
function transferOwnership(address _newOwner) public ownerOnly {
require(_newOwner != owner, "ERR_SAME_OWNER");
newOwner = _newOwner;
}
/**
* @dev used by a new owner to accept an ownership transfer
*/
function acceptOwnership() public {
require(msg.sender == newOwner, "ERR_ACCESS_DENIED");
emit OwnerUpdate(owner, newOwner);
owner = newOwner;
newOwner = address(0);
}
}
// File: solidity\contracts\utility\Utils.sol
pragma solidity 0.4.26;
/**
* @dev Utilities & Common Modifiers
*/
contract Utils {
// verifies that a value is greater than zero
modifier greaterThanZero(uint256 _value) {
_greaterThanZero(_value);
_;
}
// error message binary size optimization
function _greaterThanZero(uint256 _value) internal pure {
require(_value > 0, "ERR_ZERO_VALUE");
}
// validates an address - currently only checks that it isn't null
modifier validAddress(address _address) {
_validAddress(_address);
_;
}
// error message binary size optimization
function _validAddress(address _address) internal pure {
require(_address != address(0), "ERR_INVALID_ADDRESS");
}
// verifies that the address is different than this contract address
modifier notThis(address _address) {
_notThis(_address);
_;
}
// error message binary size optimization
function _notThis(address _address) internal view {
require(_address != address(this), "ERR_ADDRESS_IS_SELF");
}
}
// File: solidity\contracts\utility\interfaces\IContractRegistry.sol
pragma solidity 0.4.26;
/*
Contract Registry interface
*/
contract IContractRegistry {
function addressOf(bytes32 _contractName) public view returns (address);
// deprecated, backward compatibility
function getAddress(bytes32 _contractName) public view returns (address);
}
// File: solidity\contracts\utility\ContractRegistryClient.sol
pragma solidity 0.4.26;
/**
* @dev Base contract for ContractRegistry clients
*/
contract ContractRegistryClient is Owned, Utils {
bytes32 internal constant CONTRACT_REGISTRY = "ContractRegistry";
bytes32 internal constant BANCOR_NETWORK = "BancorNetwork";
bytes32 internal constant BANCOR_FORMULA = "BancorFormula";
bytes32 internal constant CONVERTER_FACTORY = "ConverterFactory";
bytes32 internal constant CONVERSION_PATH_FINDER = "ConversionPathFinder";
bytes32 internal constant CONVERTER_UPGRADER = "BancorConverterUpgrader";
bytes32 internal constant CONVERTER_REGISTRY = "BancorConverterRegistry";
bytes32 internal constant CONVERTER_REGISTRY_DATA = "BancorConverterRegistryData";
bytes32 internal constant BNT_TOKEN = "BNTToken";
bytes32 internal constant BANCOR_X = "BancorX";
bytes32 internal constant BANCOR_X_UPGRADER = "BancorXUpgrader";
bytes32 internal constant CHAINLINK_ORACLE_WHITELIST = "ChainlinkOracleWhitelist";
IContractRegistry public registry; // address of the current contract-registry
IContractRegistry public prevRegistry; // address of the previous contract-registry
bool public onlyOwnerCanUpdateRegistry; // only an owner can update the contract-registry
/**
* @dev verifies that the caller is mapped to the given contract name
*
* @param _contractName contract name
*/
modifier only(bytes32 _contractName) {
_only(_contractName);
_;
}
// error message binary size optimization
function _only(bytes32 _contractName) internal view {
require(msg.sender == addressOf(_contractName), "ERR_ACCESS_DENIED");
}
/**
* @dev initializes a new ContractRegistryClient instance
*
* @param _registry address of a contract-registry contract
*/
constructor(IContractRegistry _registry) internal validAddress(_registry) {
registry = IContractRegistry(_registry);
prevRegistry = IContractRegistry(_registry);
}
/**
* @dev updates to the new contract-registry
*/
function updateRegistry() public {
// verify that this function is permitted
require(msg.sender == owner || !onlyOwnerCanUpdateRegistry, "ERR_ACCESS_DENIED");
// get the new contract-registry
IContractRegistry newRegistry = IContractRegistry(addressOf(CONTRACT_REGISTRY));
// verify that the new contract-registry is different and not zero
require(newRegistry != address(registry) && newRegistry != address(0), "ERR_INVALID_REGISTRY");
// verify that the new contract-registry is pointing to a non-zero contract-registry
require(newRegistry.addressOf(CONTRACT_REGISTRY) != address(0), "ERR_INVALID_REGISTRY");
// save a backup of the current contract-registry before replacing it
prevRegistry = registry;
// replace the current contract-registry with the new contract-registry
registry = newRegistry;
}
/**
* @dev restores the previous contract-registry
*/
function restoreRegistry() public ownerOnly {
// restore the previous contract-registry
registry = prevRegistry;
}
/**
* @dev restricts the permission to update the contract-registry
*
* @param _onlyOwnerCanUpdateRegistry indicates whether or not permission is restricted to owner only
*/
function restrictRegistryUpdate(bool _onlyOwnerCanUpdateRegistry) public ownerOnly {
// change the permission to update the contract-registry
onlyOwnerCanUpdateRegistry = _onlyOwnerCanUpdateRegistry;
}
/**
* @dev returns the address associated with the given contract name
*
* @param _contractName contract name
*
* @return contract address
*/
function addressOf(bytes32 _contractName) internal view returns (address) {
return registry.addressOf(_contractName);
}
}
// File: solidity\contracts\utility\ReentrancyGuard.sol
pragma solidity 0.4.26;
/**
* @dev ReentrancyGuard
*
* The contract provides protection against re-entrancy - calling a function (directly or
* indirectly) from within itself.
*/
contract ReentrancyGuard {
// true while protected code is being executed, false otherwise
bool private locked = false;
/**
* @dev ensures instantiation only by sub-contracts
*/
constructor() internal {}
// protects a function against reentrancy attacks
modifier protected() {
_protected();
locked = true;
_;
locked = false;
}
// error message binary size optimization
function _protected() internal view {
require(!locked, "ERR_REENTRANCY");
}
}
// File: solidity\contracts\utility\SafeMath.sol
pragma solidity 0.4.26;
/**
* @dev Library for basic math operations with overflow/underflow protection
*/
library SafeMath {
/**
* @dev returns the sum of _x and _y, reverts if the calculation overflows
*
* @param _x value 1
* @param _y value 2
*
* @return sum
*/
function add(uint256 _x, uint256 _y) internal pure returns (uint256) {
uint256 z = _x + _y;
require(z >= _x, "ERR_OVERFLOW");
return z;
}
/**
* @dev returns the difference of _x minus _y, reverts if the calculation underflows
*
* @param _x minuend
* @param _y subtrahend
*
* @return difference
*/
function sub(uint256 _x, uint256 _y) internal pure returns (uint256) {
require(_x >= _y, "ERR_UNDERFLOW");
return _x - _y;
}
/**
* @dev returns the product of multiplying _x by _y, reverts if the calculation overflows
*
* @param _x factor 1
* @param _y factor 2
*
* @return product
*/
function mul(uint256 _x, uint256 _y) internal pure returns (uint256) {
// gas optimization
if (_x == 0)
return 0;
uint256 z = _x * _y;
require(z / _x == _y, "ERR_OVERFLOW");
return z;
}
/**
* @dev Integer division of two numbers truncating the quotient, reverts on division by zero.
*
* @param _x dividend
* @param _y divisor
*
* @return quotient
*/
function div(uint256 _x, uint256 _y) internal pure returns (uint256) {
require(_y > 0, "ERR_DIVIDE_BY_ZERO");
uint256 c = _x / _y;
return c;
}
}
// File: solidity\contracts\utility\TokenHandler.sol
pragma solidity 0.4.26;
contract TokenHandler {
bytes4 private constant APPROVE_FUNC_SELECTOR = bytes4(keccak256("approve(address,uint256)"));
bytes4 private constant TRANSFER_FUNC_SELECTOR = bytes4(keccak256("transfer(address,uint256)"));
bytes4 private constant TRANSFER_FROM_FUNC_SELECTOR = bytes4(keccak256("transferFrom(address,address,uint256)"));
/**
* @dev executes the ERC20 token's `approve` function and reverts upon failure
* the main purpose of this function is to prevent a non standard ERC20 token
* from failing silently
*
* @param _token ERC20 token address
* @param _spender approved address
* @param _value allowance amount
*/
function safeApprove(IERC20Token _token, address _spender, uint256 _value) internal {
execute(_token, abi.encodeWithSelector(APPROVE_FUNC_SELECTOR, _spender, _value));
}
/**
* @dev executes the ERC20 token's `transfer` function and reverts upon failure
* the main purpose of this function is to prevent a non standard ERC20 token
* from failing silently
*
* @param _token ERC20 token address
* @param _to target address
* @param _value transfer amount
*/
function safeTransfer(IERC20Token _token, address _to, uint256 _value) internal {
execute(_token, abi.encodeWithSelector(TRANSFER_FUNC_SELECTOR, _to, _value));
}
/**
* @dev executes the ERC20 token's `transferFrom` function and reverts upon failure
* the main purpose of this function is to prevent a non standard ERC20 token
* from failing silently
*
* @param _token ERC20 token address
* @param _from source address
* @param _to target address
* @param _value transfer amount
*/
function safeTransferFrom(IERC20Token _token, address _from, address _to, uint256 _value) internal {
execute(_token, abi.encodeWithSelector(TRANSFER_FROM_FUNC_SELECTOR, _from, _to, _value));
}
/**
* @dev executes a function on the ERC20 token and reverts upon failure
* the main purpose of this function is to prevent a non standard ERC20 token
* from failing silently
*
* @param _token ERC20 token address
* @param _data data to pass in to the token's contract for execution
*/
function execute(IERC20Token _token, bytes memory _data) private {
uint256[1] memory ret = [uint256(1)];
assembly {
let success := call(
gas, // gas remaining
_token, // destination address
0, // no ether
add(_data, 32), // input buffer (starts after the first 32 bytes in the `data` array)
mload(_data), // input length (loaded from the first 32 bytes in the `data` array)
ret, // output buffer
32 // output length
)
if iszero(success) {
revert(0, 0)
}
}
require(ret[0] != 0, "ERR_TRANSFER_FAILED");
}
}
// File: solidity\contracts\utility\TokenHolder.sol
pragma solidity 0.4.26;
/**
* @dev We consider every contract to be a 'token holder' since it's currently not possible
* for a contract to deny receiving tokens.
*
* The TokenHolder's contract sole purpose is to provide a safety mechanism that allows
* the owner to send tokens that were sent to the contract by mistake back to their sender.
*
* Note that we use the non standard ERC-20 interface which has no return value for transfer
* in order to support both non standard as well as standard token contracts.
* see https://github.com/ethereum/solidity/issues/4116
*/
contract TokenHolder is ITokenHolder, TokenHandler, Owned, Utils {
/**
* @dev withdraws tokens held by the contract and sends them to an account
* can only be called by the owner
*
* @param _token ERC20 token contract address
* @param _to account to receive the new amount
* @param _amount amount to withdraw
*/
function withdrawTokens(IERC20Token _token, address _to, uint256 _amount)
public
ownerOnly
validAddress(_token)
validAddress(_to)
notThis(_to)
{
safeTransfer(_token, _to, _amount);
}
}
// File: solidity\contracts\token\interfaces\IEtherToken.sol
pragma solidity 0.4.26;
/*
Ether Token interface
*/
contract IEtherToken is IERC20Token {
function deposit() public payable;
function withdraw(uint256 _amount) public;
function depositTo(address _to) public payable;
function withdrawTo(address _to, uint256 _amount) public;
}
// File: solidity\contracts\bancorx\interfaces\IBancorX.sol
pragma solidity 0.4.26;
contract IBancorX {
function token() public view returns (IERC20Token) {this;}
function xTransfer(bytes32 _toBlockchain, bytes32 _to, uint256 _amount, uint256 _id) public;
function getXTransferAmount(uint256 _xTransferId, address _for) public view returns (uint256);
}
// File: solidity\contracts\converter\ConverterBase.sol
pragma solidity 0.4.26;
/**
* @dev ConverterBase
*
* The converter contains the main logic for conversions between different ERC20 tokens.
*
* It is also the upgradable part of the mechanism (note that upgrades are opt-in).
*
* The anchor must be set on construction and cannot be changed afterwards.
* Wrappers are provided for some of the anchor's functions, for easier access.
*
* Once the converter accepts ownership of the anchor, it becomes the anchor's sole controller
* and can execute any of its functions.
*
* To upgrade the converter, anchor ownership must be transferred to a new converter, along with
* any relevant data.
*
* Note that the converter can transfer anchor ownership to a new converter that
* doesn't allow upgrades anymore, for finalizing the relationship between the converter
* and the anchor.
*
* Converter types (defined as uint16 type) -
* 0 = liquid token converter
* 1 = liquidity pool v1 converter
* 2 = liquidity pool v2 converter
*
* Note that converters don't currently support tokens with transfer fees.
*/
contract ConverterBase is IConverter, TokenHandler, TokenHolder, ContractRegistryClient, ReentrancyGuard {
using SafeMath for uint256;
uint32 internal constant WEIGHT_RESOLUTION = 1000000;
uint64 internal constant CONVERSION_FEE_RESOLUTION = 1000000;
address internal constant ETH_RESERVE_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
struct Reserve {
uint256 balance; // reserve balance
uint32 weight; // reserve weight, represented in ppm, 1-1000000
bool deprecated1; // deprecated
bool deprecated2; // deprecated
bool isSet; // true if the reserve is valid, false otherwise
}
/**
* @dev version number
*/
uint16 public constant version = 31;
IConverterAnchor public anchor; // converter anchor contract
IWhitelist public conversionWhitelist; // whitelist contract with list of addresses that are allowed to use the converter
IERC20Token[] public reserveTokens; // ERC20 standard token addresses (prior version 17, use 'connectorTokens' instead)
mapping (address => Reserve) public reserves; // reserve token addresses -> reserve data (prior version 17, use 'connectors' instead)
uint32 public reserveRatio = 0; // ratio between the reserves and the market cap, equal to the total reserve weights
uint32 public maxConversionFee = 0; // maximum conversion fee for the lifetime of the contract,
// represented in ppm, 0...1000000 (0 = no fee, 100 = 0.01%, 1000000 = 100%)
uint32 public conversionFee = 0; // current conversion fee, represented in ppm, 0...maxConversionFee
bool public constant conversionsEnabled = true; // deprecated, backward compatibility
/**
* @dev triggered when the converter is activated
*
* @param _type converter type
* @param _anchor converter anchor
* @param _activated true if the converter was activated, false if it was deactivated
*/
event Activation(uint16 indexed _type, IConverterAnchor indexed _anchor, bool indexed _activated);
/**
* @dev triggered when a conversion between two tokens occurs
*
* @param _fromToken source ERC20 token
* @param _toToken target ERC20 token
* @param _trader wallet that initiated the trade
* @param _amount amount converted, in the source token
* @param _return amount returned, minus conversion fee
* @param _conversionFee conversion fee
*/
event Conversion(
address indexed _fromToken,
address indexed _toToken,
address indexed _trader,
uint256 _amount,
uint256 _return,
int256 _conversionFee
);
/**
* @dev triggered when the rate between two tokens in the converter changes
* note that the event might be dispatched for rate updates between any two tokens in the converter
* note that prior to version 28, you should use the 'PriceDataUpdate' event instead
*
* @param _token1 address of the first token
* @param _token2 address of the second token
* @param _rateN rate of 1 unit of `_token1` in `_token2` (numerator)
* @param _rateD rate of 1 unit of `_token1` in `_token2` (denominator)
*/
event TokenRateUpdate(
address indexed _token1,
address indexed _token2,
uint256 _rateN,
uint256 _rateD
);
/**
* @dev triggered when the conversion fee is updated
*
* @param _prevFee previous fee percentage, represented in ppm
* @param _newFee new fee percentage, represented in ppm
*/
event ConversionFeeUpdate(uint32 _prevFee, uint32 _newFee);
/**
* @dev used by sub-contracts to initialize a new converter
*
* @param _anchor anchor governed by the converter
* @param _registry address of a contract registry contract
* @param _maxConversionFee maximum conversion fee, represented in ppm
*/
constructor(
IConverterAnchor _anchor,
IContractRegistry _registry,
uint32 _maxConversionFee
)
validAddress(_anchor)
ContractRegistryClient(_registry)
internal
validConversionFee(_maxConversionFee)
{
anchor = _anchor;
maxConversionFee = _maxConversionFee;
}
// ensures that the converter is active
modifier active() {
_active();
_;
}
// error message binary size optimization
function _active() internal view {
require(isActive(), "ERR_INACTIVE");
}
// ensures that the converter is not active
modifier inactive() {
_inactive();
_;
}
// error message binary size optimization
function _inactive() internal view {
require(!isActive(), "ERR_ACTIVE");
}
// validates a reserve token address - verifies that the address belongs to one of the reserve tokens
modifier validReserve(IERC20Token _address) {
_validReserve(_address);
_;
}
// error message binary size optimization
function _validReserve(IERC20Token _address) internal view {
require(reserves[_address].isSet, "ERR_INVALID_RESERVE");
}
// validates conversion fee
modifier validConversionFee(uint32 _conversionFee) {
_validConversionFee(_conversionFee);
_;
}
// error message binary size optimization
function _validConversionFee(uint32 _conversionFee) internal pure {
require(_conversionFee <= CONVERSION_FEE_RESOLUTION, "ERR_INVALID_CONVERSION_FEE");
}
// validates reserve weight
modifier validReserveWeight(uint32 _weight) {
_validReserveWeight(_weight);
_;
}
// error message binary size optimization
function _validReserveWeight(uint32 _weight) internal pure {
require(_weight > 0 && _weight <= WEIGHT_RESOLUTION, "ERR_INVALID_RESERVE_WEIGHT");
}
/**
* @dev deposits ether
* can only be called if the converter has an ETH reserve
*/
function() external payable {
require(reserves[ETH_RESERVE_ADDRESS].isSet, "ERR_INVALID_RESERVE"); // require(hasETHReserve(), "ERR_INVALID_RESERVE");
// a workaround for a problem when running solidity-coverage
// see https://github.com/sc-forks/solidity-coverage/issues/487
}
/**
* @dev withdraws ether
* can only be called by the owner if the converter is inactive or by upgrader contract
* can only be called after the upgrader contract has accepted the ownership of this contract
* can only be called if the converter has an ETH reserve
*
* @param _to address to send the ETH to
*/
function withdrawETH(address _to)
public
protected
ownerOnly
validReserve(IERC20Token(ETH_RESERVE_ADDRESS))
{
address converterUpgrader = addressOf(CONVERTER_UPGRADER);
// verify that the converter is inactive or that the owner is the upgrader contract
require(!isActive() || owner == converterUpgrader, "ERR_ACCESS_DENIED");
_to.transfer(address(this).balance);
// sync the ETH reserve balance
syncReserveBalance(IERC20Token(ETH_RESERVE_ADDRESS));
}
/**
* @dev checks whether or not the converter version is 28 or higher
*
* @return true, since the converter version is 28 or higher
*/
function isV28OrHigher() public pure returns (bool) {
return true;
}
/**
* @dev allows the owner to update & enable the conversion whitelist contract address
* when set, only addresses that are whitelisted are actually allowed to use the converter
* note that the whitelist check is actually done by the BancorNetwork contract
*
* @param _whitelist address of a whitelist contract
*/
function setConversionWhitelist(IWhitelist _whitelist)
public
ownerOnly
notThis(_whitelist)
{
conversionWhitelist = _whitelist;
}
/**
* @dev returns true if the converter is active, false otherwise
*
* @return true if the converter is active, false otherwise
*/
function isActive() public view returns (bool) {
return anchor.owner() == address(this);
}
/**
* @dev transfers the anchor ownership
* the new owner needs to accept the transfer
* can only be called by the converter upgrder while the upgrader is the owner
* note that prior to version 28, you should use 'transferAnchorOwnership' instead
*
* @param _newOwner new token owner
*/
function transferAnchorOwnership(address _newOwner)
public
ownerOnly
only(CONVERTER_UPGRADER)
{
anchor.transferOwnership(_newOwner);
}
/**
* @dev accepts ownership of the anchor after an ownership transfer
* most converters are also activated as soon as they accept the anchor ownership
* can only be called by the contract owner
* note that prior to version 28, you should use 'acceptTokenOwnership' instead
*/
function acceptAnchorOwnership() public ownerOnly {
// verify the the converter has at least one reserve
require(reserveTokenCount() > 0, "ERR_INVALID_RESERVE_COUNT");
anchor.acceptOwnership();
syncReserveBalances();
}
/**
* @dev withdraws tokens held by the anchor and sends them to an account
* can only be called by the owner
*
* @param _token ERC20 token contract address
* @param _to account to receive the new amount
* @param _amount amount to withdraw
*/
function withdrawFromAnchor(IERC20Token _token, address _to, uint256 _amount) public ownerOnly {
anchor.withdrawTokens(_token, _to, _amount);
}
/**
* @dev updates the current conversion fee
* can only be called by the contract owner
*
* @param _conversionFee new conversion fee, represented in ppm
*/
function setConversionFee(uint32 _conversionFee) public ownerOnly {
require(_conversionFee <= maxConversionFee, "ERR_INVALID_CONVERSION_FEE");
emit ConversionFeeUpdate(conversionFee, _conversionFee);
conversionFee = _conversionFee;
}
/**
* @dev withdraws tokens held by the converter and sends them to an account
* can only be called by the owner
* note that reserve tokens can only be withdrawn by the owner while the converter is inactive
* unless the owner is the converter upgrader contract
*
* @param _token ERC20 token contract address
* @param _to account to receive the new amount
* @param _amount amount to withdraw
*/
function withdrawTokens(IERC20Token _token, address _to, uint256 _amount) public protected ownerOnly {
address converterUpgrader = addressOf(CONVERTER_UPGRADER);
// if the token is not a reserve token, allow withdrawal
// otherwise verify that the converter is inactive or that the owner is the upgrader contract
require(!reserves[_token].isSet || !isActive() || owner == converterUpgrader, "ERR_ACCESS_DENIED");
super.withdrawTokens(_token, _to, _amount);
// if the token is a reserve token, sync the reserve balance
if (reserves[_token].isSet)
syncReserveBalance(_token);
}
/**
* @dev upgrades the converter to the latest version
* can only be called by the owner
* note that the owner needs to call acceptOwnership on the new converter after the upgrade
*/
function upgrade() public ownerOnly {
IConverterUpgrader converterUpgrader = IConverterUpgrader(addressOf(CONVERTER_UPGRADER));
// trigger de-activation event
emit Activation(converterType(), anchor, false);
transferOwnership(converterUpgrader);
converterUpgrader.upgrade(version);
acceptOwnership();
}
/**
* @dev returns the number of reserve tokens defined
* note that prior to version 17, you should use 'connectorTokenCount' instead
*
* @return number of reserve tokens
*/
function reserveTokenCount() public view returns (uint16) {
return uint16(reserveTokens.length);
}
/**
* @dev defines a new reserve token for the converter
* can only be called by the owner while the converter is inactive
*
* @param _token address of the reserve token
* @param _weight reserve weight, represented in ppm, 1-1000000
*/
function addReserve(IERC20Token _token, uint32 _weight)
public
ownerOnly
inactive
validAddress(_token)
notThis(_token)
validReserveWeight(_weight)
{
// validate input
require(_token != address(anchor) && !reserves[_token].isSet, "ERR_INVALID_RESERVE");
require(_weight <= WEIGHT_RESOLUTION - reserveRatio, "ERR_INVALID_RESERVE_WEIGHT");
require(reserveTokenCount() < uint16(-1), "ERR_INVALID_RESERVE_COUNT");
Reserve storage newReserve = reserves[_token];
newReserve.balance = 0;
newReserve.weight = _weight;
newReserve.isSet = true;
reserveTokens.push(_token);
reserveRatio += _weight;
}
/**
* @dev returns the reserve's weight
* added in version 28
*
* @param _reserveToken reserve token contract address
*
* @return reserve weight
*/
function reserveWeight(IERC20Token _reserveToken)
public
view
validReserve(_reserveToken)
returns (uint32)
{
return reserves[_reserveToken].weight;
}
/**
* @dev returns the reserve's balance
* note that prior to version 17, you should use 'getConnectorBalance' instead
*
* @param _reserveToken reserve token contract address
*
* @return reserve balance
*/
function reserveBalance(IERC20Token _reserveToken)
public
view
validReserve(_reserveToken)
returns (uint256)
{
return reserves[_reserveToken].balance;
}
/**
* @dev checks whether or not the converter has an ETH reserve
*
* @return true if the converter has an ETH reserve, false otherwise
*/
function hasETHReserve() public view returns (bool) {
return reserves[ETH_RESERVE_ADDRESS].isSet;
}
/**
* @dev converts a specific amount of source tokens to target tokens
* can only be called by the bancor network contract
*
* @param _sourceToken source ERC20 token
* @param _targetToken target ERC20 token
* @param _amount amount of tokens to convert (in units of the source token)
* @param _trader address of the caller who executed the conversion
* @param _beneficiary wallet to receive the conversion result
*
* @return amount of tokens received (in units of the target token)
*/
function convert(IERC20Token _sourceToken, IERC20Token _targetToken, uint256 _amount, address _trader, address _beneficiary)
public
payable
protected
only(BANCOR_NETWORK)
returns (uint256)
{
// validate input
require(_sourceToken != _targetToken, "ERR_SAME_SOURCE_TARGET");
// if a whitelist is set, verify that both and trader and the beneficiary are whitelisted
require(conversionWhitelist == address(0) ||
(conversionWhitelist.isWhitelisted(_trader) && conversionWhitelist.isWhitelisted(_beneficiary)),
"ERR_NOT_WHITELISTED");
return doConvert(_sourceToken, _targetToken, _amount, _trader, _beneficiary);
}
/**
* @dev converts a specific amount of source tokens to target tokens
* called by ConverterBase and allows the inherited contracts to implement custom conversion logic
*
* @param _sourceToken source ERC20 token
* @param _targetToken target ERC20 token
* @param _amount amount of tokens to convert (in units of the source token)
* @param _trader address of the caller who executed the conversion
* @param _beneficiary wallet to receive the conversion result
*
* @return amount of tokens received (in units of the target token)
*/
function doConvert(IERC20Token _sourceToken, IERC20Token _targetToken, uint256 _amount, address _trader, address _beneficiary) internal returns (uint256);
/**
* @dev returns the conversion fee for a given target amount
*
* @param _targetAmount target amount
*
* @return conversion fee
*/
function calculateFee(uint256 _targetAmount) internal view returns (uint256) {
return _targetAmount.mul(conversionFee).div(CONVERSION_FEE_RESOLUTION);
}
/**
* @dev syncs the stored reserve balance for a given reserve with the real reserve balance
*
* @param _reserveToken address of the reserve token
*/
function syncReserveBalance(IERC20Token _reserveToken) internal validReserve(_reserveToken) {
if (_reserveToken == ETH_RESERVE_ADDRESS)
reserves[_reserveToken].balance = address(this).balance;
else
reserves[_reserveToken].balance = _reserveToken.balanceOf(this);
}
/**
* @dev syncs all stored reserve balances
*/
function syncReserveBalances() internal {
uint256 reserveCount = reserveTokens.length;
for (uint256 i = 0; i < reserveCount; i++)
syncReserveBalance(reserveTokens[i]);
}
/**
* @dev helper, dispatches the Conversion event
*
* @param _sourceToken source ERC20 token
* @param _targetToken target ERC20 token
* @param _trader address of the caller who executed the conversion
* @param _amount amount purchased/sold (in the source token)
* @param _returnAmount amount returned (in the target token)
*/
function dispatchConversionEvent(
IERC20Token _sourceToken,
IERC20Token _targetToken,
address _trader,
uint256 _amount,
uint256 _returnAmount,
uint256 _feeAmount)
internal
{
// fee amount is converted to 255 bits -
// negative amount means the fee is taken from the source token, positive amount means its taken from the target token
// currently the fee is always taken from the target token
// since we convert it to a signed number, we first ensure that it's capped at 255 bits to prevent overflow
assert(_feeAmount < 2 ** 255);
emit Conversion(_sourceToken, _targetToken, _trader, _amount, _returnAmount, int256(_feeAmount));
}
/**
* @dev deprecated since version 28, backward compatibility - use only for earlier versions
*/
function token() public view returns (IConverterAnchor) {
return anchor;
}
/**
* @dev deprecated, backward compatibility
*/
function transferTokenOwnership(address _newOwner) public ownerOnly {
transferAnchorOwnership(_newOwner);
}
/**
* @dev deprecated, backward compatibility
*/
function acceptTokenOwnership() public ownerOnly {
acceptAnchorOwnership();
}
/**
* @dev deprecated, backward compatibility
*/
function connectors(address _address) public view returns (uint256, uint32, bool, bool, bool) {
Reserve memory reserve = reserves[_address];
return(reserve.balance, reserve.weight, false, false, reserve.isSet);
}
/**
* @dev deprecated, backward compatibility
*/
function connectorTokens(uint256 _index) public view returns (IERC20Token) {
return ConverterBase.reserveTokens[_index];
}
/**
* @dev deprecated, backward compatibility
*/
function connectorTokenCount() public view returns (uint16) {
return reserveTokenCount();
}
/**
* @dev deprecated, backward compatibility
*/
function getConnectorBalance(IERC20Token _connectorToken) public view returns (uint256) {
return reserveBalance(_connectorToken);
}
/**
* @dev deprecated, backward compatibility
*/
function getReturn(IERC20Token _sourceToken, IERC20Token _targetToken, uint256 _amount) public view returns (uint256, uint256) {
return targetAmountAndFee(_sourceToken, _targetToken, _amount);
}
}
// File: solidity\contracts\converter\LiquidityPoolConverter.sol
pragma solidity 0.4.26;
/**
* @dev Liquidity Pool Converter
*
* The liquidity pool converter is the base contract for specific types of converters that
* manage liquidity pools.
*
* Liquidity pools have 2 reserves or more and they allow converting between them.
*
* Note that TokenRateUpdate events are dispatched for pool tokens as well.
* The pool token is the first token in the event in that case.
*/
contract LiquidityPoolConverter is ConverterBase {
/**
* @dev triggered after liquidity is added
*
* @param _provider liquidity provider
* @param _reserveToken reserve token address
* @param _amount reserve token amount
* @param _newBalance reserve token new balance
* @param _newSupply pool token new supply
*/
event LiquidityAdded(
address indexed _provider,
address indexed _reserveToken,
uint256 _amount,
uint256 _newBalance,
uint256 _newSupply
);
/**
* @dev triggered after liquidity is removed
*
* @param _provider liquidity provider
* @param _reserveToken reserve token address
* @param _amount reserve token amount
* @param _newBalance reserve token new balance
* @param _newSupply pool token new supply
*/
event LiquidityRemoved(
address indexed _provider,
address indexed _reserveToken,
uint256 _amount,
uint256 _newBalance,
uint256 _newSupply
);
/**
* @dev initializes a new LiquidityPoolConverter instance
*
* @param _anchor anchor governed by the converter
* @param _registry address of a contract registry contract
* @param _maxConversionFee maximum conversion fee, represented in ppm
*/
constructor(
IConverterAnchor _anchor,
IContractRegistry _registry,
uint32 _maxConversionFee
)
ConverterBase(_anchor, _registry, _maxConversionFee)
internal
{
}
/**
* @dev accepts ownership of the anchor after an ownership transfer
* also activates the converter
* can only be called by the contract owner
* note that prior to version 28, you should use 'acceptTokenOwnership' instead
*/
function acceptAnchorOwnership() public {
// verify that the converter has at least 2 reserves
require(reserveTokenCount() > 1, "ERR_INVALID_RESERVE_COUNT");
super.acceptAnchorOwnership();
}
}
// File: solidity\contracts\token\interfaces\ISmartToken.sol
pragma solidity 0.4.26;
/*
Smart Token interface
*/
contract ISmartToken is IConverterAnchor, IERC20Token {
function disableTransfers(bool _disable) public;
function issue(address _to, uint256 _amount) public;
function destroy(address _from, uint256 _amount) public;
}
// File: solidity\contracts\converter\types\liquidity-pool-v1\LiquidityPoolV1Converter.sol
pragma solidity 0.4.26;
/**
* @dev Liquidity Pool v1 Converter
*
* The liquidity pool v1 converter is a specialized version of a converter that manages
* a classic bancor liquidity pool.
*
* Even though classic pools can have many reserves, the most common configuration of
* the pool has 2 reserves with 50%/50% weights.
*/
contract LiquidityPoolV1Converter is LiquidityPoolConverter {
IEtherToken internal etherToken = IEtherToken(0xc0829421C1d260BD3cB3E0F06cfE2D52db2cE315);
/**
* @dev triggered after a conversion with new price data
* deprecated, use `TokenRateUpdate` from version 28 and up
*
* @param _connectorToken reserve token
* @param _tokenSupply smart token supply
* @param _connectorBalance reserve balance
* @param _connectorWeight reserve weight
*/
event PriceDataUpdate(
address indexed _connectorToken,
uint256 _tokenSupply,
uint256 _connectorBalance,
uint32 _connectorWeight
);
/**
* @dev initializes a new LiquidityPoolV1Converter instance
*
* @param _token pool token governed by the converter
* @param _registry address of a contract registry contract
* @param _maxConversionFee maximum conversion fee, represented in ppm
*/
constructor(
ISmartToken _token,
IContractRegistry _registry,
uint32 _maxConversionFee
)
LiquidityPoolConverter(_token, _registry, _maxConversionFee)
public
{
}
/**
* @dev returns the converter type
*
* @return see the converter types in the the main contract doc
*/
function converterType() public pure returns (uint16) {
return 1;
}
/**
* @dev accepts ownership of the anchor after an ownership transfer
* also activates the converter
* can only be called by the contract owner
* note that prior to version 28, you should use 'acceptTokenOwnership' instead
*/
function acceptAnchorOwnership() public ownerOnly {
super.acceptAnchorOwnership();
emit Activation(converterType(), anchor, true);
}
/**
* @dev returns the expected target amount of converting one reserve to another along with the fee
*
* @param _sourceToken contract address of the source reserve token
* @param _targetToken contract address of the target reserve token
* @param _amount amount of tokens received from the user
*
* @return expected target amount
* @return expected fee
*/
function targetAmountAndFee(IERC20Token _sourceToken, IERC20Token _targetToken, uint256 _amount)
public
view
active
validReserve(_sourceToken)
validReserve(_targetToken)
returns (uint256, uint256)
{
// validate input
require(_sourceToken != _targetToken, "ERR_SAME_SOURCE_TARGET");
uint256 amount = IBancorFormula(addressOf(BANCOR_FORMULA)).crossReserveTargetAmount(
reserveBalance(_sourceToken),
reserves[_sourceToken].weight,
reserveBalance(_targetToken),
reserves[_targetToken].weight,
_amount
);
// return the amount minus the conversion fee and the conversion fee
uint256 fee = calculateFee(amount);
return (amount - fee, fee);
}
/**
* @dev converts a specific amount of source tokens to target tokens
* can only be called by the bancor network contract
*
* @param _sourceToken source ERC20 token
* @param _targetToken target ERC20 token
* @param _amount amount of tokens to convert (in units of the source token)
* @param _trader address of the caller who executed the conversion
* @param _beneficiary wallet to receive the conversion result
*
* @return amount of tokens received (in units of the target token)
*/
function doConvert(IERC20Token _sourceToken, IERC20Token _targetToken, uint256 _amount, address _trader, address _beneficiary)
internal
returns (uint256)
{
// get expected target amount and fee
(uint256 amount, uint256 fee) = targetAmountAndFee(_sourceToken, _targetToken, _amount);
// ensure that the trade gives something in return
require(amount != 0, "ERR_ZERO_TARGET_AMOUNT");
// ensure that the trade won't deplete the reserve balance
uint256 targetReserveBalance = reserveBalance(_targetToken);
assert(amount < targetReserveBalance);
// ensure that the input amount was already deposited
if (_sourceToken == ETH_RESERVE_ADDRESS)
require(msg.value == _amount, "ERR_ETH_AMOUNT_MISMATCH");
else
require(msg.value == 0 && _sourceToken.balanceOf(this).sub(reserveBalance(_sourceToken)) >= _amount, "ERR_INVALID_AMOUNT");
// sync the reserve balances
syncReserveBalance(_sourceToken);
reserves[_targetToken].balance = reserves[_targetToken].balance.sub(amount);
// transfer funds to the beneficiary in the to reserve token
if (_targetToken == ETH_RESERVE_ADDRESS)
_beneficiary.transfer(amount);
else
safeTransfer(_targetToken, _beneficiary, amount);
// dispatch the conversion event
dispatchConversionEvent(_sourceToken, _targetToken, _trader, _amount, amount, fee);
// dispatch rate updates
dispatchRateEvents(_sourceToken, _targetToken);
return amount;
}
/**
* @dev increases the pool's liquidity and mints new shares in the pool to the caller
* note that prior to version 28, you should use 'fund' instead
*
* @param _reserveTokens address of each reserve token
* @param _reserveAmounts amount of each reserve token
* @param _minReturn token minimum return-amount
*/
function addLiquidity(IERC20Token[] memory _reserveTokens, uint256[] memory _reserveAmounts, uint256 _minReturn)
public
payable
protected
active
{
// verify the user input
verifyLiquidityInput(_reserveTokens, _reserveAmounts, _minReturn);
// if one of the reserves is ETH, then verify that the input amount of ETH is equal to the input value of ETH
for (uint256 i = 0; i < _reserveTokens.length; i++)
if (_reserveTokens[i] == ETH_RESERVE_ADDRESS)
require(_reserveAmounts[i] == msg.value, "ERR_ETH_AMOUNT_MISMATCH");
// if the input value of ETH is larger than zero, then verify that one of the reserves is ETH
if (msg.value > 0)
require(reserves[ETH_RESERVE_ADDRESS].isSet, "ERR_NO_ETH_RESERVE");
// get the total supply
uint256 totalSupply = ISmartToken(anchor).totalSupply();
// transfer from the user an equally-worth amount of each one of the reserve tokens
uint256 amount = addLiquidityToPool(_reserveTokens, _reserveAmounts, totalSupply);
// verify that the equivalent amount of tokens is equal to or larger than the user's expectation
require(amount >= _minReturn, "ERR_RETURN_TOO_LOW");
// issue the tokens to the user
ISmartToken(anchor).issue(msg.sender, amount);
}
/**
* @dev decreases the pool's liquidity and burns the caller's shares in the pool
* note that prior to version 28, you should use 'liquidate' instead
*
* @param _amount token amount
* @param _reserveTokens address of each reserve token
* @param _reserveMinReturnAmounts minimum return-amount of each reserve token
*/
function removeLiquidity(uint256 _amount, IERC20Token[] memory _reserveTokens, uint256[] memory _reserveMinReturnAmounts)
public
protected
active
{
// verify the user input
verifyLiquidityInput(_reserveTokens, _reserveMinReturnAmounts, _amount);
// get the total supply BEFORE destroying the user tokens
uint256 totalSupply = ISmartToken(anchor).totalSupply();
// destroy the user tokens
ISmartToken(anchor).destroy(msg.sender, _amount);
// transfer to the user an equivalent amount of each one of the reserve tokens
removeLiquidityFromPool(_reserveTokens, _reserveMinReturnAmounts, totalSupply, _amount);
}
/**
* @dev increases the pool's liquidity and mints new shares in the pool to the caller
* for example, if the caller increases the supply by 10%,
* then it will cost an amount equal to 10% of each reserve token balance
* note that starting from version 28, you should use 'addLiquidity' instead
*
* @param _amount amount to increase the supply by (in the pool token)
*/
function fund(uint256 _amount) public payable protected {
syncReserveBalances();
reserves[ETH_RESERVE_ADDRESS].balance = reserves[ETH_RESERVE_ADDRESS].balance.sub(msg.value);
uint256 supply = ISmartToken(anchor).totalSupply();
IBancorFormula formula = IBancorFormula(addressOf(BANCOR_FORMULA));
// iterate through the reserve tokens and transfer a percentage equal to the weight between
// _amount and the total supply in each reserve from the caller to the converter
uint256 reserveCount = reserveTokens.length;
for (uint256 i = 0; i < reserveCount; i++) {
IERC20Token reserveToken = reserveTokens[i];
uint256 rsvBalance = reserves[reserveToken].balance;
uint256 reserveAmount = formula.fundCost(supply, rsvBalance, reserveRatio, _amount);
// transfer funds from the caller in the reserve token
if (reserveToken == ETH_RESERVE_ADDRESS) {
if (msg.value > reserveAmount) {
msg.sender.transfer(msg.value - reserveAmount);
}
else if (msg.value < reserveAmount) {
require(msg.value == 0, "ERR_INVALID_ETH_VALUE");
safeTransferFrom(etherToken, msg.sender, this, reserveAmount);
etherToken.withdraw(reserveAmount);
}
}
else {
safeTransferFrom(reserveToken, msg.sender, this, reserveAmount);
}
// sync the reserve balance
uint256 newReserveBalance = rsvBalance.add(reserveAmount);
reserves[reserveToken].balance = newReserveBalance;
uint256 newPoolTokenSupply = supply.add(_amount);
// dispatch liquidity update for the pool token/reserve
emit LiquidityAdded(msg.sender, reserveToken, reserveAmount, newReserveBalance, newPoolTokenSupply);
// dispatch the `TokenRateUpdate` event for the pool token
uint32 reserveWeight = reserves[reserveToken].weight;
dispatchPoolTokenRateEvent(newPoolTokenSupply, reserveToken, newReserveBalance, reserveWeight);
}
// issue new funds to the caller in the pool token
ISmartToken(anchor).issue(msg.sender, _amount);
}
/**
* @dev decreases the pool's liquidity and burns the caller's shares in the pool
* for example, if the holder sells 10% of the supply,
* then they will receive 10% of each reserve token balance in return
* note that starting from version 28, you should use 'removeLiquidity' instead
*
* @param _amount amount to liquidate (in the pool token)
*/
function liquidate(uint256 _amount) public protected {
require(_amount > 0, "ERR_ZERO_AMOUNT");
uint256 totalSupply = ISmartToken(anchor).totalSupply();
ISmartToken(anchor).destroy(msg.sender, _amount);
uint256[] memory reserveMinReturnAmounts = new uint256[](reserveTokens.length);
for (uint256 i = 0; i < reserveMinReturnAmounts.length; i++)
reserveMinReturnAmounts[i] = 1;
removeLiquidityFromPool(reserveTokens, reserveMinReturnAmounts, totalSupply, _amount);
}
/**
* @dev verifies that a given array of tokens is identical to the converter's array of reserve tokens
* we take this input in order to allow specifying the corresponding reserve amounts in any order
*
* @param _reserveTokens array of reserve tokens
* @param _reserveAmounts array of reserve amounts
* @param _amount token amount
*/
function verifyLiquidityInput(IERC20Token[] memory _reserveTokens, uint256[] memory _reserveAmounts, uint256 _amount) private view {
uint256 i;
uint256 j;
uint256 length = reserveTokens.length;
require(length == _reserveTokens.length, "ERR_INVALID_RESERVE");
require(length == _reserveAmounts.length, "ERR_INVALID_AMOUNT");
for (i = 0; i < length; i++) {
// verify that every input reserve token is included in the reserve tokens
require(reserves[_reserveTokens[i]].isSet, "ERR_INVALID_RESERVE");
for (j = 0; j < length; j++) {
if (reserveTokens[i] == _reserveTokens[j])
break;
}
// verify that every reserve token is included in the input reserve tokens
require(j < length, "ERR_INVALID_RESERVE");
// verify that every input reserve token amount is larger than zero
require(_reserveAmounts[i] > 0, "ERR_INVALID_AMOUNT");
}
// verify that the input token amount is larger than zero
require(_amount > 0, "ERR_ZERO_AMOUNT");
}
/**
* @dev adds liquidity (reserve) to the pool
*
* @param _reserveTokens address of each reserve token
* @param _reserveAmounts amount of each reserve token
* @param _totalSupply token total supply
*/
function addLiquidityToPool(IERC20Token[] memory _reserveTokens, uint256[] memory _reserveAmounts, uint256 _totalSupply)
private
returns (uint256)
{
if (_totalSupply == 0)
return addLiquidityToEmptyPool(_reserveTokens, _reserveAmounts);
return addLiquidityToNonEmptyPool(_reserveTokens, _reserveAmounts, _totalSupply);
}
/**
* @dev adds liquidity (reserve) to the pool when it's empty
*
* @param _reserveTokens address of each reserve token
* @param _reserveAmounts amount of each reserve token
*/
function addLiquidityToEmptyPool(IERC20Token[] memory _reserveTokens, uint256[] memory _reserveAmounts)
private
returns (uint256)
{
// calculate the geometric-mean of the reserve amounts approved by the user
uint256 amount = geometricMean(_reserveAmounts);
// transfer each one of the reserve amounts from the user to the pool
for (uint256 i = 0; i < _reserveTokens.length; i++) {
if (_reserveTokens[i] != ETH_RESERVE_ADDRESS) // ETH has already been transferred as part of the transaction
safeTransferFrom(_reserveTokens[i], msg.sender, this, _reserveAmounts[i]);
reserves[_reserveTokens[i]].balance = _reserveAmounts[i];
emit LiquidityAdded(msg.sender, _reserveTokens[i], _reserveAmounts[i], _reserveAmounts[i], amount);
// dispatch the `TokenRateUpdate` event for the pool token
uint32 reserveWeight = reserves[_reserveTokens[i]].weight;
dispatchPoolTokenRateEvent(amount, _reserveTokens[i], _reserveAmounts[i], reserveWeight);
}
return amount;
}
/**
* @dev adds liquidity (reserve) to the pool when it's not empty
*
* @param _reserveTokens address of each reserve token
* @param _reserveAmounts amount of each reserve token
* @param _totalSupply token total supply
*/
function addLiquidityToNonEmptyPool(IERC20Token[] memory _reserveTokens, uint256[] memory _reserveAmounts, uint256 _totalSupply)
private
returns (uint256)
{
syncReserveBalances();
reserves[ETH_RESERVE_ADDRESS].balance = reserves[ETH_RESERVE_ADDRESS].balance.sub(msg.value);
IBancorFormula formula = IBancorFormula(addressOf(BANCOR_FORMULA));
uint256 amount = getMinShare(formula, _totalSupply, _reserveTokens, _reserveAmounts);
uint256 newPoolTokenSupply = _totalSupply.add(amount);
for (uint256 i = 0; i < _reserveTokens.length; i++) {
IERC20Token reserveToken = _reserveTokens[i];
uint256 rsvBalance = reserves[reserveToken].balance;
uint256 reserveAmount = formula.fundCost(_totalSupply, rsvBalance, reserveRatio, amount);
require(reserveAmount > 0, "ERR_ZERO_TARGET_AMOUNT");
assert(reserveAmount <= _reserveAmounts[i]);
// transfer each one of the reserve amounts from the user to the pool
if (reserveToken != ETH_RESERVE_ADDRESS) // ETH has already been transferred as part of the transaction
safeTransferFrom(reserveToken, msg.sender, this, reserveAmount);
else if (_reserveAmounts[i] > reserveAmount) // transfer the extra amount of ETH back to the user
msg.sender.transfer(_reserveAmounts[i] - reserveAmount);
uint256 newReserveBalance = rsvBalance.add(reserveAmount);
reserves[reserveToken].balance = newReserveBalance;
emit LiquidityAdded(msg.sender, reserveToken, reserveAmount, newReserveBalance, newPoolTokenSupply);
// dispatch the `TokenRateUpdate` event for the pool token
uint32 reserveWeight = reserves[_reserveTokens[i]].weight;
dispatchPoolTokenRateEvent(newPoolTokenSupply, _reserveTokens[i], newReserveBalance, reserveWeight);
}
return amount;
}
/**
* @dev removes liquidity (reserve) from the pool
*
* @param _reserveTokens address of each reserve token
* @param _reserveMinReturnAmounts minimum return-amount of each reserve token
* @param _totalSupply token total supply
* @param _amount token amount
*/
function removeLiquidityFromPool(IERC20Token[] memory _reserveTokens, uint256[] memory _reserveMinReturnAmounts, uint256 _totalSupply, uint256 _amount)
private
{
syncReserveBalances();
IBancorFormula formula = IBancorFormula(addressOf(BANCOR_FORMULA));
uint256 newPoolTokenSupply = _totalSupply.sub(_amount);
for (uint256 i = 0; i < _reserveTokens.length; i++) {
IERC20Token reserveToken = _reserveTokens[i];
uint256 rsvBalance = reserves[reserveToken].balance;
uint256 reserveAmount = formula.liquidateReserveAmount(_totalSupply, rsvBalance, reserveRatio, _amount);
require(reserveAmount >= _reserveMinReturnAmounts[i], "ERR_ZERO_TARGET_AMOUNT");
uint256 newReserveBalance = rsvBalance.sub(reserveAmount);
reserves[reserveToken].balance = newReserveBalance;
// transfer each one of the reserve amounts from the pool to the user
if (reserveToken == ETH_RESERVE_ADDRESS)
msg.sender.transfer(reserveAmount);
else
safeTransfer(reserveToken, msg.sender, reserveAmount);
emit LiquidityRemoved(msg.sender, reserveToken, reserveAmount, newReserveBalance, newPoolTokenSupply);
// dispatch the `TokenRateUpdate` event for the pool token
uint32 reserveWeight = reserves[reserveToken].weight;
dispatchPoolTokenRateEvent(newPoolTokenSupply, reserveToken, newReserveBalance, reserveWeight);
}
}
function getMinShare(IBancorFormula formula, uint256 _totalSupply, IERC20Token[] memory _reserveTokens, uint256[] memory _reserveAmounts) private view returns (uint256) {
uint256 minIndex = 0;
for (uint256 i = 1; i < _reserveTokens.length; i++) {
if (_reserveAmounts[i].mul(reserves[_reserveTokens[minIndex]].balance) < _reserveAmounts[minIndex].mul(reserves[_reserveTokens[i]].balance))
minIndex = i;
}
return formula.fundSupplyAmount(_totalSupply, reserves[_reserveTokens[minIndex]].balance, reserveRatio, _reserveAmounts[minIndex]);
}
/**
* @dev calculates the number of decimal digits in a given value
*
* @param _x value (assumed positive)
* @return the number of decimal digits in the given value
*/
function decimalLength(uint256 _x) public pure returns (uint256) {
uint256 y = 0;
for (uint256 x = _x; x > 0; x /= 10)
y++;
return y;
}
/**
* @dev calculates the nearest integer to a given quotient
*
* @param _n quotient numerator
* @param _d quotient denominator
* @return the nearest integer to the given quotient
*/
function roundDiv(uint256 _n, uint256 _d) public pure returns (uint256) {
return (_n + _d / 2) / _d;
}
/**
* @dev calculates the average number of decimal digits in a given list of values
*
* @param _values list of values (each of which assumed positive)
* @return the average number of decimal digits in the given list of values
*/
function geometricMean(uint256[] memory _values) public pure returns (uint256) {
uint256 numOfDigits = 0;
uint256 length = _values.length;
for (uint256 i = 0; i < length; i++)
numOfDigits += decimalLength(_values[i]);
return uint256(10) ** (roundDiv(numOfDigits, length) - 1);
}
/**
* @dev dispatches rate events for both reserves / pool tokens
* only used to circumvent the `stack too deep` compiler error
*
* @param _sourceToken address of the source reserve token
* @param _targetToken address of the target reserve token
*/
function dispatchRateEvents(IERC20Token _sourceToken, IERC20Token _targetToken) private {
uint256 poolTokenSupply = ISmartToken(anchor).totalSupply();
uint256 sourceReserveBalance = reserveBalance(_sourceToken);
uint256 targetReserveBalance = reserveBalance(_targetToken);
uint32 sourceReserveWeight = reserves[_sourceToken].weight;
uint32 targetReserveWeight = reserves[_targetToken].weight;
// dispatch token rate update event
uint256 rateN = targetReserveBalance.mul(sourceReserveWeight);
uint256 rateD = sourceReserveBalance.mul(targetReserveWeight);
emit TokenRateUpdate(_sourceToken, _targetToken, rateN, rateD);
// dispatch the `TokenRateUpdate` event for the pool token
dispatchPoolTokenRateEvent(poolTokenSupply, _sourceToken, sourceReserveBalance, sourceReserveWeight);
dispatchPoolTokenRateEvent(poolTokenSupply, _targetToken, targetReserveBalance, targetReserveWeight);
// dispatch price data update events (deprecated events)
emit PriceDataUpdate(_sourceToken, poolTokenSupply, sourceReserveBalance, sourceReserveWeight);
emit PriceDataUpdate(_targetToken, poolTokenSupply, targetReserveBalance, targetReserveWeight);
}
/**
* @dev dispatches the `TokenRateUpdate` for the pool token
* only used to circumvent the `stack too deep` compiler error
*
* @param _poolTokenSupply total pool token supply
* @param _reserveToken address of the reserve token
* @param _reserveBalance reserve balance
* @param _reserveWeight reserve weight
*/
function dispatchPoolTokenRateEvent(uint256 _poolTokenSupply, IERC20Token _reserveToken, uint256 _reserveBalance, uint32 _reserveWeight) private {
emit TokenRateUpdate(anchor, _reserveToken, _reserveBalance.mul(WEIGHT_RESOLUTION), _poolTokenSupply.mul(_reserveWeight));
}
}
{
"compilationTarget": {
"LiquidityPoolV1Converter.sol": "LiquidityPoolV1Converter"
},
"evmVersion": "byzantium",
"libraries": {},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"constant":false,"inputs":[{"name":"_onlyOwnerCanUpdateRegistry","type":"bool"}],"name":"restrictRegistryUpdate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"reserveRatio","outputs":[{"name":"","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_address","type":"address"}],"name":"connectors","outputs":[{"name":"","type":"uint256"},{"name":"","type":"uint32"},{"name":"","type":"bool"},{"name":"","type":"bool"},{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"hasETHReserve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_index","type":"uint256"}],"name":"connectorTokens","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_reserveToken","type":"address"}],"name":"reserveWeight","outputs":[{"name":"","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_sourceToken","type":"address"},{"name":"_targetToken","type":"address"},{"name":"_amount","type":"uint256"}],"name":"getReturn","outputs":[{"name":"","type":"uint256"},{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"transferTokenOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"isActive","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"onlyOwnerCanUpdateRegistry","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"acceptTokenOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"},{"name":"_to","type":"address"},{"name":"_amount","type":"uint256"}],"name":"withdrawFromAnchor","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"converterType","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"liquidate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"updateRegistry","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_whitelist","type":"address"}],"name":"setConversionWhitelist","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"version","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"conversionFee","outputs":[{"name":"","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"},{"name":"_to","type":"address"},{"name":"_amount","type":"uint256"}],"name":"withdrawTokens","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"prevRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"transferAnchorOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"}],"name":"withdrawETH","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"},{"name":"_weight","type":"uint32"}],"name":"addReserve","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_x","type":"uint256"}],"name":"decimalLength","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"connectorTokenCount","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"acceptOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"registry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_reserveTokens","type":"address[]"},{"name":"_reserveAmounts","type":"uint256[]"},{"name":"_minReturn","type":"uint256"}],"name":"addLiquidity","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"maxConversionFee","outputs":[{"name":"","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"reserveTokenCount","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_values","type":"uint256[]"}],"name":"geometricMean","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"name":"_sourceToken","type":"address"},{"name":"_targetToken","type":"address"},{"name":"_amount","type":"uint256"}],"name":"targetAmountAndFee","outputs":[{"name":"","type":"uint256"},{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"},{"name":"_reserveTokens","type":"address[]"},{"name":"_reserveMinReturnAmounts","type":"uint256[]"}],"name":"removeLiquidity","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"restoreRegistry","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_n","type":"uint256"},{"name":"_d","type":"uint256"}],"name":"roundDiv","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"conversionsEnabled","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"conversionWhitelist","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"fund","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"acceptAnchorOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"reserveTokens","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isV28OrHigher","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"anchor","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"newOwner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"upgrade","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"reserves","outputs":[{"name":"balance","type":"uint256"},{"name":"weight","type":"uint32"},{"name":"deprecated1","type":"bool"},{"name":"deprecated2","type":"bool"},{"name":"isSet","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_connectorToken","type":"address"}],"name":"getConnectorBalance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_reserveToken","type":"address"}],"name":"reserveBalance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sourceToken","type":"address"},{"name":"_targetToken","type":"address"},{"name":"_amount","type":"uint256"},{"name":"_trader","type":"address"},{"name":"_beneficiary","type":"address"}],"name":"convert","outputs":[{"name":"","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_conversionFee","type":"uint32"}],"name":"setConversionFee","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"token","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_token","type":"address"},{"name":"_registry","type":"address"},{"name":"_maxConversionFee","type":"uint32"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_connectorToken","type":"address"},{"indexed":false,"name":"_tokenSupply","type":"uint256"},{"indexed":false,"name":"_connectorBalance","type":"uint256"},{"indexed":false,"name":"_connectorWeight","type":"uint32"}],"name":"PriceDataUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_provider","type":"address"},{"indexed":true,"name":"_reserveToken","type":"address"},{"indexed":false,"name":"_amount","type":"uint256"},{"indexed":false,"name":"_newBalance","type":"uint256"},{"indexed":false,"name":"_newSupply","type":"uint256"}],"name":"LiquidityAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_provider","type":"address"},{"indexed":true,"name":"_reserveToken","type":"address"},{"indexed":false,"name":"_amount","type":"uint256"},{"indexed":false,"name":"_newBalance","type":"uint256"},{"indexed":false,"name":"_newSupply","type":"uint256"}],"name":"LiquidityRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_type","type":"uint16"},{"indexed":true,"name":"_anchor","type":"address"},{"indexed":true,"name":"_activated","type":"bool"}],"name":"Activation","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_fromToken","type":"address"},{"indexed":true,"name":"_toToken","type":"address"},{"indexed":true,"name":"_trader","type":"address"},{"indexed":false,"name":"_amount","type":"uint256"},{"indexed":false,"name":"_return","type":"uint256"},{"indexed":false,"name":"_conversionFee","type":"int256"}],"name":"Conversion","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_token1","type":"address"},{"indexed":true,"name":"_token2","type":"address"},{"indexed":false,"name":"_rateN","type":"uint256"},{"indexed":false,"name":"_rateD","type":"uint256"}],"name":"TokenRateUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_prevFee","type":"uint32"},{"indexed":false,"name":"_newFee","type":"uint32"}],"name":"ConversionFeeUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_prevOwner","type":"address"},{"indexed":true,"name":"_newOwner","type":"address"}],"name":"OwnerUpdate","type":"event"}]