Transaction Hash:
Block:
11490903 at Dec-20-2020 03:05:32 PM +UTC
Transaction Fee:
0.001273158 ETH
$2.03
Gas Used:
21,951 Gas / 58 Gwei
Emitted Events:
42 |
StandardPoolConverter.OwnerUpdate( _prevOwner=0x92F18a07...57a67D724, _newOwner=[Sender] 0x1647f8480c65528aff347602460c3ff9429cef4d )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x1647F848...9429cEF4d |
19.228986748038527472 Eth
Nonce: 400
|
19.227713590038527472 Eth
Nonce: 401
| 0.001273158 | ||
0x99C85bb6...993Cb89E3
Miner
| (BeePool) | 499.585116229755167266 Eth | 499.586389387755167266 Eth | 0.001273158 | |
0xc1956f06...203645Ba4 | (Bancor: Converter 501) |
Execution Trace
StandardPoolConverter.CALL( )
// File: solidity/contracts/converter/ConverterVersion.sol // SPDX-License-Identifier: SEE LICENSE IN LICENSE pragma solidity 0.6.12; contract ConverterVersion { uint16 public constant version = 44; } // File: solidity/contracts/utility/interfaces/IOwned.sol pragma solidity 0.6.12; /* Owned contract interface */ interface IOwned { // this function isn't since the compiler emits automatically generated getter functions as external function owner() external view returns (address); function transferOwnership(address _newOwner) external; function acceptOwnership() external; } // File: solidity/contracts/converter/interfaces/IConverterAnchor.sol pragma solidity 0.6.12; /* Converter Anchor interface */ interface IConverterAnchor is IOwned { } // File: solidity/contracts/token/interfaces/IERC20Token.sol pragma solidity 0.6.12; /* ERC20 Standard Token interface */ interface IERC20Token { function name() external view returns (string memory); function symbol() external view returns (string memory); function decimals() external view returns (uint8); function totalSupply() external view returns (uint256); function balanceOf(address _owner) external view returns (uint256); function allowance(address _owner, address _spender) external view returns (uint256); function transfer(address _to, uint256 _value) external returns (bool); function transferFrom( address _from, address _to, uint256 _value ) external returns (bool); function approve(address _spender, uint256 _value) external returns (bool); } // File: solidity/contracts/converter/interfaces/IConverter.sol pragma solidity 0.6.12; /* Converter interface */ interface IConverter is IOwned { function converterType() external pure returns (uint16); function anchor() external view returns (IConverterAnchor); function isActive() external view returns (bool); function targetAmountAndFee( IERC20Token _sourceToken, IERC20Token _targetToken, uint256 _amount ) external view returns (uint256, uint256); function convert( IERC20Token _sourceToken, IERC20Token _targetToken, uint256 _amount, address _trader, address payable _beneficiary ) external payable returns (uint256); function conversionFee() external view returns (uint32); function maxConversionFee() external view returns (uint32); function reserveBalance(IERC20Token _reserveToken) external view returns (uint256); receive() external payable; function transferAnchorOwnership(address _newOwner) external; function acceptAnchorOwnership() external; function setConversionFee(uint32 _conversionFee) external; function withdrawTokens( IERC20Token _token, address _to, uint256 _amount ) external; function withdrawETH(address payable _to) external; function addReserve(IERC20Token _token, uint32 _ratio) external; // deprecated, backward compatibility function token() external view returns (IConverterAnchor); function transferTokenOwnership(address _newOwner) external; function acceptTokenOwnership() external; function connectors(IERC20Token _address) external view returns ( uint256, uint32, bool, bool, bool ); function getConnectorBalance(IERC20Token _connectorToken) external view returns (uint256); function connectorTokens(uint256 _index) external view returns (IERC20Token); function connectorTokenCount() external view returns (uint16); /** * @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 input amount in units of the source token * @param _return output amount minus conversion fee in units of the target token * @param _conversionFee conversion fee in units of the target token */ event Conversion( IERC20Token indexed _fromToken, IERC20Token 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 * * @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(IERC20Token indexed _token1, IERC20Token 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); } // File: solidity/contracts/converter/interfaces/IConverterUpgrader.sol pragma solidity 0.6.12; /* Converter Upgrader interface */ interface IConverterUpgrader { function upgrade(bytes32 _version) external; function upgrade(uint16 _version) external; } // File: solidity/contracts/token/interfaces/IDSToken.sol pragma solidity 0.6.12; /* DSToken interface */ interface IDSToken is IConverterAnchor, IERC20Token { function issue(address _to, uint256 _amount) external; function destroy(address _from, uint256 _amount) external; } // File: solidity/contracts/utility/Owned.sol pragma solidity 0.6.12; /** * @dev This contract provides support and utilities for contract ownership. */ contract Owned is IOwned { address public override 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 override ownerOnly { require(_newOwner != owner, "ERR_SAME_OWNER"); newOwner = _newOwner; } /** * @dev used by a new owner to accept an ownership transfer */ function acceptOwnership() public override { 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.6.12; /** * @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.6.12; /* Contract Registry interface */ interface IContractRegistry { function addressOf(bytes32 _contractName) external view returns (address); } // File: solidity/contracts/utility/ContractRegistryClient.sol pragma solidity 0.6.12; /** * @dev This is the 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"; 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(address(_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 != registry && address(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.6.12; /** * @dev This contract provides protection against calling a function * (directly or indirectly) from within itself. */ contract ReentrancyGuard { uint256 private constant UNLOCKED = 1; uint256 private constant LOCKED = 2; // LOCKED while protected code is being executed, UNLOCKED otherwise uint256 private state = UNLOCKED; /** * @dev ensures instantiation only by sub-contracts */ constructor() internal {} // protects a function against reentrancy attacks modifier protected() { _protected(); state = LOCKED; _; state = UNLOCKED; } // error message binary size optimization function _protected() internal view { require(state == UNLOCKED, "ERR_REENTRANCY"); } } // File: solidity/contracts/utility/SafeMath.sol pragma solidity 0.6.12; /** * @dev This library supports 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.6.12; 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 { (bool success, bytes memory data) = address(_token).call( abi.encodeWithSelector(APPROVE_FUNC_SELECTOR, _spender, _value) ); require(success && (data.length == 0 || abi.decode(data, (bool))), "ERR_APPROVE_FAILED"); } /** * @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 { (bool success, bytes memory data) = address(_token).call( abi.encodeWithSelector(TRANSFER_FUNC_SELECTOR, _to, _value) ); require(success && (data.length == 0 || abi.decode(data, (bool))), "ERR_TRANSFER_FAILED"); } /** * @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 { (bool success, bytes memory data) = address(_token).call( abi.encodeWithSelector(TRANSFER_FROM_FUNC_SELECTOR, _from, _to, _value) ); require(success && (data.length == 0 || abi.decode(data, (bool))), "ERR_TRANSFER_FROM_FAILED"); } } // File: solidity/contracts/utility/interfaces/ITokenHolder.sol pragma solidity 0.6.12; /* Token Holder interface */ interface ITokenHolder is IOwned { function withdrawTokens( IERC20Token _token, address _to, uint256 _amount ) external; } // File: solidity/contracts/utility/TokenHolder.sol pragma solidity 0.6.12; /** * @dev This contract provides a safety mechanism for allowing the owner to * send tokens that were sent to the contract by mistake back to the sender. * * We consider every contract to be a 'token holder' since it's currently not possible * for a contract to deny receiving tokens. * * 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 virtual override ownerOnly validAddress(address(_token)) validAddress(_to) notThis(_to) { safeTransfer(_token, _to, _amount); } } // File: solidity/contracts/utility/Math.sol pragma solidity 0.6.12; /** * @dev This library provides a set of complex math operations. */ library Math { /** * @dev returns the largest integer smaller than or equal to the square root of a positive integer * * @param _num a positive integer * * @return the largest integer smaller than or equal to the square root of the positive integer */ function floorSqrt(uint256 _num) internal pure returns (uint256) { uint256 x = _num / 2 + 1; uint256 y = (x + _num / x) / 2; while (x > y) { x = y; y = (x + _num / x) / 2; } return x; } /** * @dev returns the smallest integer larger than or equal to the square root of a positive integer * * @param _num a positive integer * * @return the smallest integer larger than or equal to the square root of the positive integer */ function ceilSqrt(uint256 _num) internal pure returns (uint256) { uint256 x = floorSqrt(_num); return x * x == _num ? x : x + 1; } /** * @dev computes a reduced-scalar ratio * * @param _n ratio numerator * @param _d ratio denominator * @param _max maximum desired scalar * * @return ratio's numerator and denominator */ function reducedRatio( uint256 _n, uint256 _d, uint256 _max ) internal pure returns (uint256, uint256) { (uint256 n, uint256 d) = (_n, _d); if (n > _max || d > _max) { (n, d) = normalizedRatio(n, d, _max); } if (n != d) { return (n, d); } return (1, 1); } /** * @dev computes "scale * a / (a + b)" and "scale * b / (a + b)". */ function normalizedRatio( uint256 _a, uint256 _b, uint256 _scale ) internal pure returns (uint256, uint256) { if (_a <= _b) { return accurateRatio(_a, _b, _scale); } (uint256 y, uint256 x) = accurateRatio(_b, _a, _scale); return (x, y); } /** * @dev computes "scale * a / (a + b)" and "scale * b / (a + b)", assuming that "a <= b". */ function accurateRatio( uint256 _a, uint256 _b, uint256 _scale ) internal pure returns (uint256, uint256) { uint256 maxVal = uint256(-1) / _scale; if (_a > maxVal) { uint256 c = _a / (maxVal + 1) + 1; _a /= c; // we can now safely compute `_a * _scale` _b /= c; } if (_a != _b) { uint256 n = _a * _scale; uint256 d = _a + _b; // can overflow if (d >= _a) { // no overflow in `_a + _b` uint256 x = roundDiv(n, d); // we can now safely compute `_scale - x` uint256 y = _scale - x; return (x, y); } if (n < _b - (_b - _a) / 2) { return (0, _scale); // `_a * _scale < (_a + _b) / 2 < MAX_UINT256 < _a + _b` } return (1, _scale - 1); // `(_a + _b) / 2 < _a * _scale < MAX_UINT256 < _a + _b` } return (_scale / 2, _scale / 2); // allow reduction to `(1, 1)` in the calling function } /** * @dev computes the nearest integer to a given quotient without overflowing or underflowing. */ function roundDiv(uint256 _n, uint256 _d) internal pure returns (uint256) { return _n / _d + (_n % _d) / (_d - _d / 2); } /** * @dev returns the average number of decimal digits in a given list of positive integers * * @param _values list of positive integers * * @return the average number of decimal digits in the given list of positive integers */ function geometricMean(uint256[] memory _values) internal pure returns (uint256) { uint256 numOfDigits = 0; uint256 length = _values.length; for (uint256 i = 0; i < length; i++) { numOfDigits += decimalLength(_values[i]); } return uint256(10)**(roundDivUnsafe(numOfDigits, length) - 1); } /** * @dev returns the number of decimal digits in a given positive integer * * @param _x positive integer * * @return the number of decimal digits in the given positive integer */ function decimalLength(uint256 _x) internal pure returns (uint256) { uint256 y = 0; for (uint256 x = _x; x > 0; x /= 10) { y++; } return y; } /** * @dev returns the nearest integer to a given quotient * the computation is overflow-safe assuming that the input is sufficiently small * * @param _n quotient numerator * @param _d quotient denominator * * @return the nearest integer to the given quotient */ function roundDivUnsafe(uint256 _n, uint256 _d) internal pure returns (uint256) { return (_n + _d / 2) / _d; } /** * @dev returns the larger of two values * * @param _val1 the first value * @param _val2 the second value */ function max(uint256 _val1, uint256 _val2) internal pure returns (uint256) { return _val1 > _val2 ? _val1 : _val2; } } // File: solidity/contracts/utility/Time.sol pragma solidity 0.6.12; /* Time implementing contract */ contract Time { /** * @dev returns the current time */ function time() internal view virtual returns (uint256) { return block.timestamp; } } // File: solidity/contracts/converter/types/standard-pool/StandardPoolConverter.sol pragma solidity 0.6.12; /** * @dev This contract is a specialized version of the converter, which is * optimized for a liquidity pool that has 2 reserves with 50%/50% weights. */ contract StandardPoolConverter is ConverterVersion, IConverter, TokenHandler, TokenHolder, ContractRegistryClient, ReentrancyGuard, Time { using SafeMath for uint256; using Math for *; IERC20Token private constant ETH_RESERVE_ADDRESS = IERC20Token(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); uint256 private constant MAX_UINT128 = 2**128 - 1; uint256 private constant MAX_UINT112 = 2**112 - 1; uint256 private constant MAX_UINT32 = 2**32 - 1; uint256 private constant AVERAGE_RATE_PERIOD = 10 minutes; uint32 private constant PPM_RESOLUTION = 1000000; uint256 private __reserveBalances; IERC20Token[] private __reserveTokens; mapping(IERC20Token => uint256) private __reserveIds; IConverterAnchor public override anchor; // converter anchor contract uint32 public override maxConversionFee; // maximum conversion fee, represented in ppm, 0...1000000 uint32 public override conversionFee; // current conversion fee, represented in ppm, 0...maxConversionFee // average rate details: // bits 0...111 represent the numerator of the rate between reserve token 0 and reserve token 1 // bits 111...223 represent the denominator of the rate between reserve token 0 and reserve token 1 // bits 224...255 represent the update-time of the rate between reserve token 0 and reserve token 1 // where `numerator / denominator` gives the worth of one reserve token 0 in units of reserve token 1 uint256 public averageRateInfo; /** * @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, IERC20Token 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, IERC20Token indexed _reserveToken, uint256 _amount, uint256 _newBalance, uint256 _newSupply ); /** * @dev initializes a new StandardPoolConverter 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 ) public ContractRegistryClient(_registry) validAddress(address(_anchor)) 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(__reserveIds[_address] != 0, "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 <= PPM_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 == PPM_RESOLUTION / 2, "ERR_INVALID_RESERVE_WEIGHT"); } /** * @dev returns the converter type * * @return see the converter types in the the main contract doc */ function converterType() public pure override returns (uint16) { return 3; } /** * @dev deposits ether * can only be called if the converter has an ETH reserve */ receive() external payable override validReserve(ETH_RESERVE_ADDRESS) {} /** * @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 payable _to) public override protected ownerOnly validReserve(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(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 returns true if the converter is active, false otherwise * * @return true if the converter is active, false otherwise */ function isActive() public view virtual override 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 override 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 virtual override ownerOnly { // verify the the converter has exactly two reserves require(reserveTokenCount() == 2, "ERR_INVALID_RESERVE_COUNT"); anchor.acceptOwnership(); syncReserveBalances(); emit Activation(converterType(), anchor, true); } /** * @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 override 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 override(IConverter, TokenHolder) protected ownerOnly { address converterUpgrader = addressOf(CONVERTER_UPGRADER); uint256 reserveId = __reserveIds[_token]; // 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(reserveId == 0 || !isActive() || owner == converterUpgrader, "ERR_ACCESS_DENIED"); super.withdrawTokens(_token, _to, _amount); // if the token is a reserve token, sync the reserve balance if (reserveId != 0) { 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(address(converterUpgrader)); converterUpgrader.upgrade(version); acceptOwnership(); } /** * @dev returns the number of reserve tokens * 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 returns the array of reserve tokens * * @return array of reserve tokens */ function reserveTokens() public view returns (IERC20Token[] memory) { return __reserveTokens; } /** * @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 virtual override ownerOnly inactive validAddress(address(_token)) notThis(address(_token)) validReserveWeight(_weight) { // validate input require(address(_token) != address(anchor) && __reserveIds[_token] == 0, "ERR_INVALID_RESERVE"); require(reserveTokenCount() < 2, "ERR_INVALID_RESERVE_COUNT"); __reserveTokens.push(_token); __reserveIds[_token] = __reserveTokens.length; } /** * @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 PPM_RESOLUTION / 2; } /** * @dev returns the balance of a given reserve token * * @param _reserveToken reserve token contract address * * @return the balance of the given reserve token */ function reserveBalance(IERC20Token _reserveToken) public view override returns (uint256) { uint256 reserveId = __reserveIds[_reserveToken]; require(reserveId != 0, "ERR_INVALID_RESERVE"); return reserveBalance(reserveId); } /** * @dev returns the balances of both reserve tokens * * @return the balances of both reserve tokens */ function reserveBalances() public view returns (uint256, uint256) { return reserveBalances(1, 2); } /** * @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 payable _beneficiary ) public payable override protected only(BANCOR_NETWORK) returns (uint256) { // validate input require(_sourceToken != _targetToken, "ERR_SAME_SOURCE_TARGET"); return doConvert(_sourceToken, _targetToken, _amount, _trader, _beneficiary); } /** * @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) / PPM_RESOLUTION; } /** * @dev loads the stored reserve balance for a given reserve id * * @param _reserveId reserve id */ function reserveBalance(uint256 _reserveId) internal view returns (uint256) { return decodeReserveBalance(__reserveBalances, _reserveId); } /** * @dev loads the stored reserve balances * * @param _sourceId source reserve id * @param _targetId target reserve id */ function reserveBalances(uint256 _sourceId, uint256 _targetId) internal view returns (uint256, uint256) { require((_sourceId == 1 && _targetId == 2) || (_sourceId == 2 && _targetId == 1), "ERR_INVALID_RESERVES"); return decodeReserveBalances(__reserveBalances, _sourceId, _targetId); } /** * @dev stores the stored reserve balance for a given reserve id * * @param _reserveId reserve id * @param _reserveBalance reserve balance */ function setReserveBalance(uint256 _reserveId, uint256 _reserveBalance) internal { require(_reserveBalance <= MAX_UINT128, "ERR_RESERVE_BALANCE_OVERFLOW"); uint256 otherBalance = decodeReserveBalance(__reserveBalances, 3 - _reserveId); __reserveBalances = encodeReserveBalances(_reserveBalance, _reserveId, otherBalance, 3 - _reserveId); } /** * @dev stores the stored reserve balances * * @param _sourceId source reserve id * @param _targetId target reserve id * @param _sourceBalance source reserve balance * @param _targetBalance target reserve balance */ function setReserveBalances( uint256 _sourceId, uint256 _targetId, uint256 _sourceBalance, uint256 _targetBalance ) internal { require(_sourceBalance <= MAX_UINT128 && _targetBalance <= MAX_UINT128, "ERR_RESERVE_BALANCE_OVERFLOW"); __reserveBalances = encodeReserveBalances(_sourceBalance, _sourceId, _targetBalance, _targetId); } /** * @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 { uint256 reserveId = __reserveIds[_reserveToken]; uint256 balance = _reserveToken == ETH_RESERVE_ADDRESS ? address(this).balance : _reserveToken.balanceOf(address(this)); setReserveBalance(reserveId, balance); } /** * @dev syncs all stored reserve balances */ function syncReserveBalances() internal { IERC20Token _reserveToken0 = __reserveTokens[0]; IERC20Token _reserveToken1 = __reserveTokens[1]; uint256 balance0 = _reserveToken0 == ETH_RESERVE_ADDRESS ? address(this).balance : _reserveToken0.balanceOf(address(this)); uint256 balance1 = _reserveToken1 == ETH_RESERVE_ADDRESS ? address(this).balance : _reserveToken1.balanceOf(address(this)); setReserveBalances(1, 2, balance0, balance1); } /** * @dev syncs all stored reserve balances, excluding a given amount of ether from the ether reserve balance (if relevant) * * @param _value amount of ether to exclude from the ether reserve balance (if relevant) */ function syncReserveBalances(uint256 _value) internal { IERC20Token _reserveToken0 = __reserveTokens[0]; IERC20Token _reserveToken1 = __reserveTokens[1]; uint256 balance0 = _reserveToken0 == ETH_RESERVE_ADDRESS ? address(this).balance - _value : _reserveToken0.balanceOf(address(this)); uint256 balance1 = _reserveToken1 == ETH_RESERVE_ADDRESS ? address(this).balance - _value : _reserveToken1.balanceOf(address(this)); setReserveBalances(1, 2, balance0, balance1); } /** * @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 { emit Conversion(_sourceToken, _targetToken, _trader, _amount, _returnAmount, int256(_feeAmount)); } /** * @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 override active returns (uint256, uint256) { uint256 sourceId = __reserveIds[_sourceToken]; uint256 targetId = __reserveIds[_targetToken]; (uint256 sourceBalance, uint256 targetBalance) = reserveBalances(sourceId, targetId); uint256 amount = crossReserveTargetAmount(sourceBalance, targetBalance, _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 * * @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 payable _beneficiary ) internal returns (uint256) { // update the recent average rate updateRecentAverageRate(); uint256 sourceId = __reserveIds[_sourceToken]; uint256 targetId = __reserveIds[_targetToken]; (uint256 sourceBalance, uint256 targetBalance) = reserveBalances(sourceId, targetId); uint256 targetAmount = crossReserveTargetAmount(sourceBalance, targetBalance, _amount); // get the target amount minus the conversion fee and the conversion fee uint256 fee = calculateFee(targetAmount); uint256 amount = targetAmount - fee; // 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 assert(amount < targetBalance); // ensure that the input amount was already deposited uint256 actualSourceBalance; if (_sourceToken == ETH_RESERVE_ADDRESS) { actualSourceBalance = address(this).balance; require(msg.value == _amount, "ERR_ETH_AMOUNT_MISMATCH"); } else { actualSourceBalance = _sourceToken.balanceOf(address(this)); require(msg.value == 0 && actualSourceBalance.sub(sourceBalance) >= _amount, "ERR_INVALID_AMOUNT"); } // sync the reserve balances setReserveBalances(sourceId, targetId, actualSourceBalance, targetBalance - 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 dispatchTokenRateUpdateEvents(_sourceToken, _targetToken, actualSourceBalance, targetBalance - amount); return amount; } /** * @dev returns the recent average rate of 1 `_token` in the other reserve token units * * @param _token token to get the rate for * @return recent average rate between the reserves (numerator) * @return recent average rate between the reserves (denominator) */ function recentAverageRate(IERC20Token _token) external view validReserve(_token) returns (uint256, uint256) { // get the recent average rate of reserve 0 uint256 rate = calcRecentAverageRate(averageRateInfo); uint256 rateN = decodeAverageRateN(rate); uint256 rateD = decodeAverageRateD(rate); if (_token == __reserveTokens[0]) { return (rateN, rateD); } return (rateD, rateN); } /** * @dev updates the recent average rate if needed */ function updateRecentAverageRate() internal { uint256 averageRateInfo1 = averageRateInfo; uint256 averageRateInfo2 = calcRecentAverageRate(averageRateInfo1); if (averageRateInfo1 != averageRateInfo2) { averageRateInfo = averageRateInfo2; } } /** * @dev returns the recent average rate of 1 reserve token 0 in reserve token 1 units * * @param _averageRateInfo a local copy of the `averageRateInfo` state-variable * @return recent average rate between the reserves */ function calcRecentAverageRate(uint256 _averageRateInfo) internal view returns (uint256) { // get the previous average rate and its update-time uint256 prevAverageRateT = decodeAverageRateT(_averageRateInfo); uint256 prevAverageRateN = decodeAverageRateN(_averageRateInfo); uint256 prevAverageRateD = decodeAverageRateD(_averageRateInfo); // get the elapsed time since the previous average rate was calculated uint256 currentTime = time(); uint256 timeElapsed = currentTime - prevAverageRateT; // if the previous average rate was calculated in the current block, the average rate remains unchanged if (timeElapsed == 0) { return _averageRateInfo; } // get the current rate between the reserves (uint256 currentRateD, uint256 currentRateN) = reserveBalances(); // if the previous average rate was calculated a while ago or never, the average rate is equal to the current rate if (timeElapsed >= AVERAGE_RATE_PERIOD || prevAverageRateT == 0) { (currentRateN, currentRateD) = Math.reducedRatio(currentRateN, currentRateD, MAX_UINT112); return encodeAverageRateInfo(currentTime, currentRateN, currentRateD); } uint256 x = prevAverageRateD.mul(currentRateN); uint256 y = prevAverageRateN.mul(currentRateD); // since we know that timeElapsed < AVERAGE_RATE_PERIOD, we can avoid using SafeMath: uint256 newRateN = y.mul(AVERAGE_RATE_PERIOD - timeElapsed).add(x.mul(timeElapsed)); uint256 newRateD = prevAverageRateD.mul(currentRateD).mul(AVERAGE_RATE_PERIOD); (newRateN, newRateD) = Math.reducedRatio(newRateN, newRateD, MAX_UINT112); return encodeAverageRateInfo(currentTime, newRateN, newRateD); } /** * @dev increases the pool's liquidity and mints new shares in the pool to the caller * this version receives the two reserve amounts as separate args * * @param _reserve1Amount amount of the first reserve token * @param _reserve2Amount amount of the second reserve token * @param _minReturn token minimum return-amount * * @return amount of pool tokens issued */ function addLiquidity(uint256 _reserve1Amount, uint256 _reserve2Amount, uint256 _minReturn) public payable returns (uint256) { uint256[] memory reserveAmounts = new uint256[](2); reserveAmounts[0] = _reserve1Amount; reserveAmounts[1] = _reserve2Amount; return addLiquidity(__reserveTokens, reserveAmounts, _minReturn); } /** * @dev increases the pool's liquidity and mints new shares in the pool to the caller * * @param _reserveTokens address of each reserve token * @param _reserveAmounts amount of each reserve token * @param _minReturn token minimum return-amount * * @return amount of pool tokens issued */ function addLiquidity( IERC20Token[] memory _reserveTokens, uint256[] memory _reserveAmounts, uint256 _minReturn ) public payable protected active returns (uint256) { // 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 < 2; 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(__reserveIds[ETH_RESERVE_ADDRESS] != 0, "ERR_NO_ETH_RESERVE"); } // save a local copy of the pool token IDSToken poolToken = IDSToken(address(anchor)); // get the total supply uint256 totalSupply = poolToken.totalSupply(); // sync the balances to ensure no mismatch syncReserveBalances(msg.value); uint256[2] memory oldReserveBalances; uint256[2] memory newReserveBalances; (oldReserveBalances[0], oldReserveBalances[1]) = reserveBalances(); // calculate the amount of pool tokens to mint uint256 amount; uint256[] memory reserveAmounts = new uint256[](2); if (totalSupply == 0) { for (uint256 i = 0; i < 2; i++) { reserveAmounts[i] = _reserveAmounts[i]; } amount = Math.geometricMean(reserveAmounts); } else { uint256 index = (_reserveAmounts[0].mul(oldReserveBalances[1]) < _reserveAmounts[1].mul(oldReserveBalances[0])) ? 0 : 1; amount = fundSupplyAmount(totalSupply, oldReserveBalances[index], _reserveAmounts[index]); for (uint256 i = 0; i < 2; i++) { reserveAmounts[i] = fundCost(totalSupply, oldReserveBalances[i], amount); } } uint256 newPoolTokenSupply = totalSupply.add(amount); for (uint256 i = 0; i < 2; i++) { IERC20Token reserveToken = _reserveTokens[i]; uint256 reserveAmount = reserveAmounts[i]; 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, address(this), reserveAmount); } else if (_reserveAmounts[i] > reserveAmount) { // transfer the extra amount of ETH back to the user msg.sender.transfer(_reserveAmounts[i] - reserveAmount); } // save the new reserve balance newReserveBalances[i] = oldReserveBalances[i].add(reserveAmount); emit LiquidityAdded(msg.sender, reserveToken, reserveAmount, newReserveBalances[i], newPoolTokenSupply); // dispatch the `TokenRateUpdate` event for the pool token emit TokenRateUpdate(poolToken, reserveToken, newReserveBalances[i], newPoolTokenSupply); } // set the reserve balances setReserveBalances(1, 2, newReserveBalances[0], newReserveBalances[1]); // 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 poolToken.issue(msg.sender, amount); // return the amount of pool tokens issued return amount; } /** * @dev decreases the pool's liquidity and burns the caller's shares in the pool * this version receives the two minimum return amounts as separate args * * @param _amount token amount * @param _reserve1MinReturn minimum return for the first reserve token * @param _reserve2MinReturn minimum return for the second reserve token * * @return the first reserve amount returned * @return the second reserve amount returned */ function removeLiquidity(uint256 _amount, uint256 _reserve1MinReturn, uint256 _reserve2MinReturn) public returns (uint256, uint256) { uint256[] memory minReturnAmounts = new uint256[](2); minReturnAmounts[0] = _reserve1MinReturn; minReturnAmounts[1] = _reserve2MinReturn; uint256[] memory reserveAmounts = removeLiquidity(_amount, __reserveTokens, minReturnAmounts); return (reserveAmounts[0], reserveAmounts[1]); } /** * @dev decreases the pool's liquidity and burns the caller's shares in the pool * * @param _amount token amount * @param _reserveTokens address of each reserve token * @param _reserveMinReturnAmounts minimum return-amount of each reserve token * * @return the amount of each reserve token granted for the given amount of pool tokens */ function removeLiquidity( uint256 _amount, IERC20Token[] memory _reserveTokens, uint256[] memory _reserveMinReturnAmounts ) public protected active returns (uint256[] memory) { // verify the user input bool inputRearranged = verifyLiquidityInput(_reserveTokens, _reserveMinReturnAmounts, _amount); // save a local copy of the pool token IDSToken poolToken = IDSToken(address(anchor)); // get the total supply BEFORE destroying the user tokens uint256 totalSupply = poolToken.totalSupply(); // destroy the user tokens poolToken.destroy(msg.sender, _amount); // sync the balances to ensure no mismatch syncReserveBalances(); uint256 newPoolTokenSupply = totalSupply.sub(_amount); uint256[] memory reserveAmounts = removeLiquidityReserveAmounts(_amount, _reserveTokens, totalSupply); uint256[2] memory oldReserveBalances; uint256[2] memory newReserveBalances; (oldReserveBalances[0], oldReserveBalances[1]) = reserveBalances(); for (uint256 i = 0; i < 2; i++) { IERC20Token reserveToken = _reserveTokens[i]; uint256 reserveAmount = reserveAmounts[i]; require(reserveAmount >= _reserveMinReturnAmounts[i], "ERR_ZERO_TARGET_AMOUNT"); // save the new reserve balance newReserveBalances[i] = oldReserveBalances[i].sub(reserveAmount); // 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, newReserveBalances[i], newPoolTokenSupply); // dispatch the `TokenRateUpdate` event for the pool token emit TokenRateUpdate(poolToken, reserveToken, newReserveBalances[i], newPoolTokenSupply); } // set the reserve balances setReserveBalances(1, 2, newReserveBalances[0], newReserveBalances[1]); if (inputRearranged) { uint256 tempReserveAmount = reserveAmounts[0]; reserveAmounts[0] = reserveAmounts[1]; reserveAmounts[1] = tempReserveAmount; } // return the amount of each reserve token granted for the given amount of pool tokens return reserveAmounts; } /** * @dev given the amount of one of the reserve tokens to add liquidity of, * returns the required amount of each one of the other reserve tokens * since an empty pool can be funded with any list of non-zero input amounts, * this function assumes that the pool is not empty (has already been funded) * * @param _reserveTokens address of each reserve token * @param _reserveTokenIndex index of the relevant reserve token * @param _reserveAmount amount of the relevant reserve token * * @return the required amount of each one of the reserve tokens */ function addLiquidityCost( IERC20Token[] memory _reserveTokens, uint256 _reserveTokenIndex, uint256 _reserveAmount ) public view returns (uint256[] memory) { uint256[] memory _reserveAmounts = new uint256[](2); uint256[] memory _reserveBalances = new uint256[](2); uint256 reserve0Id = __reserveIds[_reserveTokens[0]]; uint256 reserve1Id = __reserveIds[_reserveTokens[1]]; (_reserveBalances[0], _reserveBalances[1]) = reserveBalances(reserve0Id, reserve1Id); uint256 totalSupply = IDSToken(address(anchor)).totalSupply(); uint256 amount = fundSupplyAmount(totalSupply, _reserveBalances[_reserveTokenIndex], _reserveAmount); for (uint256 i = 0; i < 2; i++) _reserveAmounts[i] = fundCost(totalSupply, _reserveBalances[i], amount); return _reserveAmounts; } /** * @dev given the amount of one of the reserve tokens to add liquidity of, * returns the amount of pool tokens entitled for it * since an empty pool can be funded with any list of non-zero input amounts, * this function assumes that the pool is not empty (has already been funded) * * @param _reserveToken address of the reserve token * @param _reserveAmount amount of the reserve token * * @return the amount of pool tokens entitled */ function addLiquidityReturn(IERC20Token _reserveToken, uint256 _reserveAmount) public view returns (uint256) { uint256 totalSupply = IDSToken(address(anchor)).totalSupply(); return fundSupplyAmount(totalSupply, reserveBalance(__reserveIds[_reserveToken]), _reserveAmount); } /** * @dev returns the amount of each reserve token entitled for a given amount of pool tokens * * @param _amount amount of pool tokens * @param _reserveTokens address of each reserve token * * @return the amount of each reserve token entitled for the given amount of pool tokens */ function removeLiquidityReturn(uint256 _amount, IERC20Token[] memory _reserveTokens) public view returns (uint256[] memory) { uint256 totalSupply = IDSToken(address(anchor)).totalSupply(); return removeLiquidityReserveAmounts(_amount, _reserveTokens, totalSupply); } /** * @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 * this function rearranges the input arrays according to the converter's array of reserve tokens * * @param _reserveTokens array of reserve tokens * @param _reserveAmounts array of reserve amounts * @param _amount token amount * * @return true if the function has rearranged the input arrays; false otherwise */ function verifyLiquidityInput( IERC20Token[] memory _reserveTokens, uint256[] memory _reserveAmounts, uint256 _amount ) private view returns (bool) { require(_reserveAmounts[0] > 0 && _reserveAmounts[1] > 0 && _amount > 0, "ERR_ZERO_AMOUNT"); uint256 reserve0Id = __reserveIds[_reserveTokens[0]]; uint256 reserve1Id = __reserveIds[_reserveTokens[1]]; if (reserve0Id == 2 && reserve1Id == 1) { IERC20Token tempReserveToken = _reserveTokens[0]; _reserveTokens[0] = _reserveTokens[1]; _reserveTokens[1] = tempReserveToken; uint256 tempReserveAmount = _reserveAmounts[0]; _reserveAmounts[0] = _reserveAmounts[1]; _reserveAmounts[1] = tempReserveAmount; return true; } require(reserve0Id == 1 && reserve1Id == 2, "ERR_INVALID_RESERVE"); return false; } /** * @dev returns the amount of each reserve token entitled for a given amount of pool tokens * * @param _amount amount of pool tokens * @param _reserveTokens address of each reserve token * @param _totalSupply token total supply * * @return the amount of each reserve token entitled for the given amount of pool tokens */ function removeLiquidityReserveAmounts( uint256 _amount, IERC20Token[] memory _reserveTokens, uint256 _totalSupply ) private view returns (uint256[] memory) { uint256[] memory _reserveAmounts = new uint256[](2); uint256[] memory _reserveBalances = new uint256[](2); uint256 reserve0Id = __reserveIds[_reserveTokens[0]]; uint256 reserve1Id = __reserveIds[_reserveTokens[1]]; (_reserveBalances[0], _reserveBalances[1]) = reserveBalances(reserve0Id, reserve1Id); for (uint256 i = 0; i < 2; i++) _reserveAmounts[i] = liquidateReserveAmount(_totalSupply, _reserveBalances[i], _amount); return _reserveAmounts; } /** * @dev dispatches token rate update events for the reserve tokens and the pool token * * @param _sourceToken address of the source reserve token * @param _targetToken address of the target reserve token * @param _sourceBalance balance of the source reserve token * @param _targetBalance balance of the target reserve token */ function dispatchTokenRateUpdateEvents( IERC20Token _sourceToken, IERC20Token _targetToken, uint256 _sourceBalance, uint256 _targetBalance ) private { // save a local copy of the pool token IDSToken poolToken = IDSToken(address(anchor)); // get the total supply of pool tokens uint256 poolTokenSupply = poolToken.totalSupply(); // dispatch token rate update event for the reserve tokens emit TokenRateUpdate(_sourceToken, _targetToken, _targetBalance, _sourceBalance); // dispatch token rate update events for the pool token emit TokenRateUpdate(poolToken, _sourceToken, _sourceBalance, poolTokenSupply); emit TokenRateUpdate(poolToken, _targetToken, _targetBalance, poolTokenSupply); } function encodeReserveBalance(uint256 _balance, uint256 _id) private pure returns (uint256) { assert(_balance <= MAX_UINT128 && (_id == 1 || _id == 2)); return _balance << ((_id - 1) * 128); } function decodeReserveBalance(uint256 _balances, uint256 _id) private pure returns (uint256) { assert(_id == 1 || _id == 2); return (_balances >> ((_id - 1) * 128)) & MAX_UINT128; } function encodeReserveBalances( uint256 _balance0, uint256 _id0, uint256 _balance1, uint256 _id1 ) private pure returns (uint256) { return encodeReserveBalance(_balance0, _id0) | encodeReserveBalance(_balance1, _id1); } function decodeReserveBalances( uint256 _balances, uint256 _id0, uint256 _id1 ) private pure returns (uint256, uint256) { return (decodeReserveBalance(_balances, _id0), decodeReserveBalance(_balances, _id1)); } function encodeAverageRateInfo( uint256 _averageRateT, uint256 _averageRateN, uint256 _averageRateD ) private pure returns (uint256) { assert(_averageRateT <= MAX_UINT32 && _averageRateN <= MAX_UINT112 && _averageRateD <= MAX_UINT112); return (_averageRateT << 224) | (_averageRateN << 112) | _averageRateD; } function decodeAverageRateT(uint256 _averageRateInfo) private pure returns (uint256) { return _averageRateInfo >> 224; } function decodeAverageRateN(uint256 _averageRateInfo) private pure returns (uint256) { return (_averageRateInfo >> 112) & MAX_UINT112; } function decodeAverageRateD(uint256 _averageRateInfo) private pure returns (uint256) { return _averageRateInfo & MAX_UINT112; } function crossReserveTargetAmount( uint256 _sourceReserveBalance, uint256 _targetReserveBalance, uint256 _amount ) private pure returns (uint256) { // validate input require(_sourceReserveBalance > 0 && _targetReserveBalance > 0, "ERR_INVALID_RESERVE_BALANCE"); return _targetReserveBalance.mul(_amount) / _sourceReserveBalance.add(_amount); } function fundCost( uint256 _supply, uint256 _reserveBalance, uint256 _amount ) private pure returns (uint256) { // validate input require(_supply > 0, "ERR_INVALID_SUPPLY"); require(_reserveBalance > 0, "ERR_INVALID_RESERVE_BALANCE"); // special case for 0 amount if (_amount == 0) { return 0; } return (_amount.mul(_reserveBalance) - 1) / _supply + 1; } function fundSupplyAmount( uint256 _supply, uint256 _reserveBalance, uint256 _amount ) private pure returns (uint256) { // validate input require(_supply > 0, "ERR_INVALID_SUPPLY"); require(_reserveBalance > 0, "ERR_INVALID_RESERVE_BALANCE"); // special case for 0 amount if (_amount == 0) { return 0; } return _amount.mul(_supply) / _reserveBalance; } function liquidateReserveAmount( uint256 _supply, uint256 _reserveBalance, uint256 _amount ) private pure returns (uint256) { // validate input require(_supply > 0, "ERR_INVALID_SUPPLY"); require(_reserveBalance > 0, "ERR_INVALID_RESERVE_BALANCE"); require(_amount <= _supply, "ERR_INVALID_AMOUNT"); // special case for 0 amount if (_amount == 0) { return 0; } // special case for liquidating the entire supply if (_amount == _supply) { return _reserveBalance; } return _amount.mul(_reserveBalance) / _supply; } /** * @dev deprecated since version 28, backward compatibility - use only for earlier versions */ function token() public view override returns (IConverterAnchor) { return anchor; } /** * @dev deprecated, backward compatibility */ function transferTokenOwnership(address _newOwner) public override ownerOnly { transferAnchorOwnership(_newOwner); } /** * @dev deprecated, backward compatibility */ function acceptTokenOwnership() public override ownerOnly { acceptAnchorOwnership(); } /** * @dev deprecated, backward compatibility */ function connectors(IERC20Token _address) public view override returns ( uint256, uint32, bool, bool, bool ) { uint256 reserveId = __reserveIds[_address]; if (reserveId != 0) { return (reserveBalance(reserveId), PPM_RESOLUTION / 2, false, false, true); } return (0, 0, false, false, false); } /** * @dev deprecated, backward compatibility */ function connectorTokens(uint256 _index) public view override returns (IERC20Token) { return __reserveTokens[_index]; } /** * @dev deprecated, backward compatibility */ function connectorTokenCount() public view override returns (uint16) { return reserveTokenCount(); } /** * @dev deprecated, backward compatibility */ function getConnectorBalance(IERC20Token _connectorToken) public view override 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); } }