Feature Tip: Add private address tag to any address under My Name Tag !
More Info
Private Name Tags
ContractCreator
TokenTracker
Latest 14 from a total of 14 transactions
Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
---|---|---|---|---|---|---|---|---|---|
Transfer | 11312911 | 1442 days ago | IN | 0 ETH | 0.0018484 | ||||
Transfer | 8317914 | 1913 days ago | IN | 0 ETH | 0.00003646 | ||||
Transfer | 7493877 | 2042 days ago | IN | 0 ETH | 0.00008587 | ||||
Transfer | 7475783 | 2044 days ago | IN | 0 ETH | 0.00073832 | ||||
Approve | 7390104 | 2058 days ago | IN | 0 ETH | 0.00036071 | ||||
Approve | 7360718 | 2062 days ago | IN | 0 ETH | 0.00036522 | ||||
Transfer | 7351199 | 2064 days ago | IN | 0 ETH | 0.00022149 | ||||
Transfer | 7348989 | 2064 days ago | IN | 0 ETH | 0.00025943 | ||||
Approve | 7306743 | 2071 days ago | IN | 0 ETH | 0.00047073 | ||||
Transfer | 7306632 | 2071 days ago | IN | 0 ETH | 0.00051916 | ||||
Transfer | 7303404 | 2071 days ago | IN | 0 ETH | 0.0003698 | ||||
Transfer | 7300786 | 2072 days ago | IN | 0 ETH | 0.00008022 | ||||
Transfer Ownersh... | 7292936 | 2073 days ago | IN | 0 ETH | 0.00018845 | ||||
0x60806040 | 7292933 | 2073 days ago | IN | 0 ETH | 0.03563667 |
View more zero value Internal Transactions in Advanced View mode
Advanced mode:
Loading...
Loading
Similar Match Source Code This contract matches the deployed Bytecode of the Source Code for Contract 0x043115f0...C6C659492 The constructor portion of the code might be different and could alter the actual behaviour of the contract
Contract Name:
ERC20CappedLong
Compiler Version
v0.4.24+commit.e67f0147
Optimization Enabled:
Yes with 10000 runs
Other Settings:
default evmVersion
Contract Source Code (Solidity)
/** *Submitted for verification at Etherscan.io on 2018-11-19 */ pragma solidity 0.4.24; pragma experimental "v0.5.0"; /* Copyright 2018 dYdX Trading Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // File: openzeppelin-solidity/contracts/math/SafeMath.sol /** * @title SafeMath * @dev Math operations with safety checks that throw on error */ library SafeMath { /** * @dev Multiplies two numbers, throws on overflow. */ function mul(uint256 _a, uint256 _b) internal pure returns (uint256 c) { // Gas optimization: this is cheaper than asserting 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 if (_a == 0) { return 0; } c = _a * _b; assert(c / _a == _b); return c; } /** * @dev Integer division of two numbers, truncating the quotient. */ function div(uint256 _a, uint256 _b) internal pure returns (uint256) { // assert(_b > 0); // Solidity automatically throws when dividing by 0 // uint256 c = _a / _b; // assert(_a == _b * c + _a % _b); // There is no case in which this doesn't hold return _a / _b; } /** * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). */ function sub(uint256 _a, uint256 _b) internal pure returns (uint256) { assert(_b <= _a); return _a - _b; } /** * @dev Adds two numbers, throws on overflow. */ function add(uint256 _a, uint256 _b) internal pure returns (uint256 c) { c = _a + _b; assert(c >= _a); return c; } } // File: openzeppelin-solidity/contracts/token/ERC20/ERC20Basic.sol /** * @title ERC20Basic * @dev Simpler version of ERC20 interface * See https://github.com/ethereum/EIPs/issues/179 */ contract ERC20Basic { function totalSupply() public view returns (uint256); function balanceOf(address _who) public view returns (uint256); function transfer(address _to, uint256 _value) public returns (bool); event Transfer(address indexed from, address indexed to, uint256 value); } // File: openzeppelin-solidity/contracts/token/ERC20/ERC20.sol /** * @title ERC20 interface * @dev see https://github.com/ethereum/EIPs/issues/20 */ contract ERC20 is ERC20Basic { function allowance(address _owner, address _spender) public view returns (uint256); function transferFrom(address _from, address _to, uint256 _value) public returns (bool); function approve(address _spender, uint256 _value) public returns (bool); event Approval( address indexed owner, address indexed spender, uint256 value ); } // File: openzeppelin-solidity/contracts/token/ERC20/DetailedERC20.sol /** * @title DetailedERC20 token * @dev The decimals are only for visualization purposes. * All the operations are done using the smallest and indivisible token unit, * just as on Ethereum all the operations are done in wei. */ contract DetailedERC20 is ERC20 { string public name; string public symbol; uint8 public decimals; constructor(string _name, string _symbol, uint8 _decimals) public { name = _name; symbol = _symbol; decimals = _decimals; } } // File: openzeppelin-solidity/contracts/ownership/Ownable.sol /** * @title Ownable * @dev The Ownable contract has an owner address, and provides basic authorization control * functions, this simplifies the implementation of "user permissions". */ contract Ownable { address public owner; event OwnershipRenounced(address indexed previousOwner); event OwnershipTransferred( address indexed previousOwner, address indexed newOwner ); /** * @dev The Ownable constructor sets the original `owner` of the contract to the sender * account. */ constructor() public { owner = msg.sender; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(msg.sender == owner); _; } /** * @dev Allows the current owner to relinquish control of the contract. * @notice Renouncing to ownership will leave the contract without an owner. * It will not be possible to call the functions with the `onlyOwner` * modifier anymore. */ function renounceOwnership() public onlyOwner { emit OwnershipRenounced(owner); owner = address(0); } /** * @dev Allows the current owner to transfer control of the contract to a newOwner. * @param _newOwner The address to transfer ownership to. */ function transferOwnership(address _newOwner) public onlyOwner { _transferOwnership(_newOwner); } /** * @dev Transfers control of the contract to a newOwner. * @param _newOwner The address to transfer ownership to. */ function _transferOwnership(address _newOwner) internal { require(_newOwner != address(0)); emit OwnershipTransferred(owner, _newOwner); owner = _newOwner; } } // File: openzeppelin-solidity/contracts/math/Math.sol /** * @title Math * @dev Assorted math operations */ library Math { function max64(uint64 _a, uint64 _b) internal pure returns (uint64) { return _a >= _b ? _a : _b; } function min64(uint64 _a, uint64 _b) internal pure returns (uint64) { return _a < _b ? _a : _b; } function max256(uint256 _a, uint256 _b) internal pure returns (uint256) { return _a >= _b ? _a : _b; } function min256(uint256 _a, uint256 _b) internal pure returns (uint256) { return _a < _b ? _a : _b; } } // File: openzeppelin-solidity/contracts/token/ERC20/BasicToken.sol /** * @title Basic token * @dev Basic version of StandardToken, with no allowances. */ contract BasicToken is ERC20Basic { using SafeMath for uint256; mapping(address => uint256) internal balances; uint256 internal totalSupply_; /** * @dev Total number of tokens in existence */ function totalSupply() public view returns (uint256) { return totalSupply_; } /** * @dev Transfer token for a specified address * @param _to The address to transfer to. * @param _value The amount to be transferred. */ function transfer(address _to, uint256 _value) public returns (bool) { require(_value <= balances[msg.sender]); require(_to != address(0)); balances[msg.sender] = balances[msg.sender].sub(_value); balances[_to] = balances[_to].add(_value); emit Transfer(msg.sender, _to, _value); return true; } /** * @dev Gets the balance of the specified address. * @param _owner The address to query the the balance of. * @return An uint256 representing the amount owned by the passed address. */ function balanceOf(address _owner) public view returns (uint256) { return balances[_owner]; } } // File: openzeppelin-solidity/contracts/token/ERC20/StandardToken.sol /** * @title Standard ERC20 token * * @dev Implementation of the basic standard token. * https://github.com/ethereum/EIPs/issues/20 * Based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol */ contract StandardToken is ERC20, BasicToken { mapping (address => mapping (address => uint256)) internal allowed; /** * @dev Transfer tokens from one address to another * @param _from address The address which you want to send tokens from * @param _to address The address which you want to transfer to * @param _value uint256 the amount of tokens to be transferred */ function transferFrom( address _from, address _to, uint256 _value ) public returns (bool) { require(_value <= balances[_from]); require(_value <= allowed[_from][msg.sender]); require(_to != address(0)); balances[_from] = balances[_from].sub(_value); balances[_to] = balances[_to].add(_value); allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); emit Transfer(_from, _to, _value); return true; } /** * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. * Beware that changing an allowance with this method brings the risk that someone may use both the old * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * @param _spender The address which will spend the funds. * @param _value The amount of tokens to be spent. */ function approve(address _spender, uint256 _value) public returns (bool) { allowed[msg.sender][_spender] = _value; emit Approval(msg.sender, _spender, _value); return true; } /** * @dev Function to check the amount of tokens that an owner allowed to a spender. * @param _owner address The address which owns the funds. * @param _spender address The address which will spend the funds. * @return A uint256 specifying the amount of tokens still available for the spender. */ function allowance( address _owner, address _spender ) public view returns (uint256) { return allowed[_owner][_spender]; } /** * @dev Increase the amount of tokens that an owner allowed to a spender. * approve should be called when allowed[_spender] == 0. To increment * allowed value is better to use this function to avoid 2 calls (and wait until * the first transaction is mined) * From MonolithDAO Token.sol * @param _spender The address which will spend the funds. * @param _addedValue The amount of tokens to increase the allowance by. */ function increaseApproval( address _spender, uint256 _addedValue ) public returns (bool) { allowed[msg.sender][_spender] = ( allowed[msg.sender][_spender].add(_addedValue)); emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); return true; } /** * @dev Decrease the amount of tokens that an owner allowed to a spender. * approve should be called when allowed[_spender] == 0. To decrement * allowed value is better to use this function to avoid 2 calls (and wait until * the first transaction is mined) * From MonolithDAO Token.sol * @param _spender The address which will spend the funds. * @param _subtractedValue The amount of tokens to decrease the allowance by. */ function decreaseApproval( address _spender, uint256 _subtractedValue ) public returns (bool) { uint256 oldValue = allowed[msg.sender][_spender]; if (_subtractedValue >= oldValue) { allowed[msg.sender][_spender] = 0; } else { allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue); } emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); return true; } } // File: contracts/lib/AccessControlledBase.sol /** * @title AccessControlledBase * @author dYdX * * Base functionality for access control. Requires an implementation to * provide a way to grant and optionally revoke access */ contract AccessControlledBase { // ============ State Variables ============ mapping (address => bool) public authorized; // ============ Events ============ event AccessGranted( address who ); event AccessRevoked( address who ); // ============ Modifiers ============ modifier requiresAuthorization() { require( authorized[msg.sender], "AccessControlledBase#requiresAuthorization: Sender not authorized" ); _; } } // File: contracts/lib/StaticAccessControlled.sol /** * @title StaticAccessControlled * @author dYdX * * Allows for functions to be access controled * Permissions cannot be changed after a grace period */ contract StaticAccessControlled is AccessControlledBase, Ownable { using SafeMath for uint256; // ============ State Variables ============ // Timestamp after which no additional access can be granted uint256 public GRACE_PERIOD_EXPIRATION; // ============ Constructor ============ constructor( uint256 gracePeriod ) public Ownable() { GRACE_PERIOD_EXPIRATION = block.timestamp.add(gracePeriod); } // ============ Owner-Only State-Changing Functions ============ function grantAccess( address who ) external onlyOwner { require( block.timestamp < GRACE_PERIOD_EXPIRATION, "StaticAccessControlled#grantAccess: Cannot grant access after grace period" ); emit AccessGranted(who); authorized[who] = true; } } // File: contracts/lib/GeneralERC20.sol /** * @title GeneralERC20 * @author dYdX * * Interface for using ERC20 Tokens. We have to use a special interface to call ERC20 functions so * that we dont automatically revert when calling non-compliant tokens that have no return value for * transfer(), transferFrom(), or approve(). */ interface GeneralERC20 { function totalSupply( ) external view returns (uint256); function balanceOf( address who ) external view returns (uint256); function allowance( address owner, address spender ) external view returns (uint256); function transfer( address to, uint256 value ) external; function transferFrom( address from, address to, uint256 value ) external; function approve( address spender, uint256 value ) external; } // File: contracts/lib/TokenInteract.sol /** * @title TokenInteract * @author dYdX * * This library contains basic functions for interacting with ERC20 tokens */ library TokenInteract { function balanceOf( address token, address owner ) internal view returns (uint256) { return GeneralERC20(token).balanceOf(owner); } function allowance( address token, address owner, address spender ) internal view returns (uint256) { return GeneralERC20(token).allowance(owner, spender); } function approve( address token, address spender, uint256 amount ) internal { GeneralERC20(token).approve(spender, amount); require( checkSuccess(), "TokenInteract#approve: Approval failed" ); } function transfer( address token, address to, uint256 amount ) internal { address from = address(this); if ( amount == 0 || from == to ) { return; } GeneralERC20(token).transfer(to, amount); require( checkSuccess(), "TokenInteract#transfer: Transfer failed" ); } function transferFrom( address token, address from, address to, uint256 amount ) internal { if ( amount == 0 || from == to ) { return; } GeneralERC20(token).transferFrom(from, to, amount); require( checkSuccess(), "TokenInteract#transferFrom: TransferFrom failed" ); } // ============ Private Helper-Functions ============ /** * Checks the return value of the previous function up to 32 bytes. Returns true if the previous * function returned 0 bytes or 32 bytes that are not all-zero. */ function checkSuccess( ) private pure returns (bool) { uint256 returnValue = 0; /* solium-disable-next-line security/no-inline-assembly */ assembly { // check number of bytes returned from last function call switch returndatasize // no bytes returned: assume success case 0x0 { returnValue := 1 } // 32 bytes returned: check if non-zero case 0x20 { // copy 32 bytes into scratch space returndatacopy(0x0, 0x0, 0x20) // load those bytes into returnValue returnValue := mload(0x0) } // not sure what was returned: dont mark as success default { } } return returnValue != 0; } } // File: contracts/margin/TokenProxy.sol /** * @title TokenProxy * @author dYdX * * Used to transfer tokens between addresses which have set allowance on this contract. */ contract TokenProxy is StaticAccessControlled { using SafeMath for uint256; // ============ Constructor ============ constructor( uint256 gracePeriod ) public StaticAccessControlled(gracePeriod) {} // ============ Authorized-Only State Changing Functions ============ /** * Transfers tokens from an address (that has set allowance on the proxy) to another address. * * @param token The address of the ERC20 token * @param from The address to transfer token from * @param to The address to transfer tokens to * @param value The number of tokens to transfer */ function transferTokens( address token, address from, address to, uint256 value ) external requiresAuthorization { TokenInteract.transferFrom( token, from, to, value ); } // ============ Public Constant Functions ============ /** * Getter function to get the amount of token that the proxy is able to move for a particular * address. The minimum of 1) the balance of that address and 2) the allowance given to proxy. * * @param who The owner of the tokens * @param token The address of the ERC20 token * @return The number of tokens able to be moved by the proxy from the address specified */ function available( address who, address token ) external view returns (uint256) { return Math.min256( TokenInteract.allowance(token, who, address(this)), TokenInteract.balanceOf(token, who) ); } } // File: contracts/margin/Vault.sol /** * @title Vault * @author dYdX * * Holds and transfers tokens in vaults denominated by id * * Vault only supports ERC20 tokens, and will not accept any tokens that require * a tokenFallback or equivalent function (See ERC223, ERC777, etc.) */ contract Vault is StaticAccessControlled { using SafeMath for uint256; // ============ Events ============ event ExcessTokensWithdrawn( address indexed token, address indexed to, address caller ); // ============ State Variables ============ // Address of the TokenProxy contract. Used for moving tokens. address public TOKEN_PROXY; // Map from vault ID to map from token address to amount of that token attributed to the // particular vault ID. mapping (bytes32 => mapping (address => uint256)) public balances; // Map from token address to total amount of that token attributed to some account. mapping (address => uint256) public totalBalances; // ============ Constructor ============ constructor( address proxy, uint256 gracePeriod ) public StaticAccessControlled(gracePeriod) { TOKEN_PROXY = proxy; } // ============ Owner-Only State-Changing Functions ============ /** * Allows the owner to withdraw any excess tokens sent to the vault by unconventional means, * including (but not limited-to) token airdrops. Any tokens moved to the vault by TOKEN_PROXY * will be accounted for and will not be withdrawable by this function. * * @param token ERC20 token address * @param to Address to transfer tokens to * @return Amount of tokens withdrawn */ function withdrawExcessToken( address token, address to ) external onlyOwner returns (uint256) { uint256 actualBalance = TokenInteract.balanceOf(token, address(this)); uint256 accountedBalance = totalBalances[token]; uint256 withdrawableBalance = actualBalance.sub(accountedBalance); require( withdrawableBalance != 0, "Vault#withdrawExcessToken: Withdrawable token amount must be non-zero" ); TokenInteract.transfer(token, to, withdrawableBalance); emit ExcessTokensWithdrawn(token, to, msg.sender); return withdrawableBalance; } // ============ Authorized-Only State-Changing Functions ============ /** * Transfers tokens from an address (that has approved the proxy) to the vault. * * @param id The vault which will receive the tokens * @param token ERC20 token address * @param from Address from which the tokens will be taken * @param amount Number of the token to be sent */ function transferToVault( bytes32 id, address token, address from, uint256 amount ) external requiresAuthorization { // First send tokens to this contract TokenProxy(TOKEN_PROXY).transferTokens( token, from, address(this), amount ); // Then increment balances balances[id][token] = balances[id][token].add(amount); totalBalances[token] = totalBalances[token].add(amount); // This should always be true. If not, something is very wrong assert(totalBalances[token] >= balances[id][token]); validateBalance(token); } /** * Transfers a certain amount of funds to an address. * * @param id The vault from which to send the tokens * @param token ERC20 token address * @param to Address to transfer tokens to * @param amount Number of the token to be sent */ function transferFromVault( bytes32 id, address token, address to, uint256 amount ) external requiresAuthorization { // Next line also asserts that (balances[id][token] >= amount); balances[id][token] = balances[id][token].sub(amount); // Next line also asserts that (totalBalances[token] >= amount); totalBalances[token] = totalBalances[token].sub(amount); // This should always be true. If not, something is very wrong assert(totalBalances[token] >= balances[id][token]); // Do the sending TokenInteract.transfer(token, to, amount); // asserts transfer succeeded // Final validation validateBalance(token); } // ============ Private Helper-Functions ============ /** * Verifies that this contract is in control of at least as many tokens as accounted for * * @param token Address of ERC20 token */ function validateBalance( address token ) private view { // The actual balance could be greater than totalBalances[token] because anyone // can send tokens to the contract's address which cannot be accounted for assert(TokenInteract.balanceOf(token, address(this)) >= totalBalances[token]); } } // File: contracts/lib/ReentrancyGuard.sol /** * @title ReentrancyGuard * @author dYdX * * Optimized version of the well-known ReentrancyGuard contract */ contract ReentrancyGuard { uint256 private _guardCounter = 1; modifier nonReentrant() { uint256 localCounter = _guardCounter + 1; _guardCounter = localCounter; _; require( _guardCounter == localCounter, "Reentrancy check failure" ); } } // File: openzeppelin-solidity/contracts/AddressUtils.sol /** * Utility library of inline functions on addresses */ library AddressUtils { /** * Returns whether the target address is a contract * @dev This function will return false if invoked during the constructor of a contract, * as the code is not actually created until after the constructor finishes. * @param _addr address to check * @return whether the target address is a contract */ function isContract(address _addr) internal view returns (bool) { uint256 size; // XXX Currently there is no better way to check if there is a contract in an address // than to check the size of the code at that address. // See https://ethereum.stackexchange.com/a/14016/36603 // for more details about how this works. // TODO Check this again before the Serenity release, because all addresses will be // contracts then. // solium-disable-next-line security/no-inline-assembly assembly { size := extcodesize(_addr) } return size > 0; } } // File: contracts/lib/Fraction.sol /** * @title Fraction * @author dYdX * * This library contains implementations for fraction structs. */ library Fraction { struct Fraction128 { uint128 num; uint128 den; } } // File: contracts/lib/FractionMath.sol /** * @title FractionMath * @author dYdX * * This library contains safe math functions for manipulating fractions. */ library FractionMath { using SafeMath for uint256; using SafeMath for uint128; /** * Returns a Fraction128 that is equal to a + b * * @param a The first Fraction128 * @param b The second Fraction128 * @return The result (sum) */ function add( Fraction.Fraction128 memory a, Fraction.Fraction128 memory b ) internal pure returns (Fraction.Fraction128 memory) { uint256 left = a.num.mul(b.den); uint256 right = b.num.mul(a.den); uint256 denominator = a.den.mul(b.den); // if left + right overflows, prevent overflow if (left + right < left) { left = left.div(2); right = right.div(2); denominator = denominator.div(2); } return bound(left.add(right), denominator); } /** * Returns a Fraction128 that is equal to a - (1/2)^d * * @param a The Fraction128 * @param d The power of (1/2) * @return The result */ function sub1Over( Fraction.Fraction128 memory a, uint128 d ) internal pure returns (Fraction.Fraction128 memory) { if (a.den % d == 0) { return bound( a.num.sub(a.den.div(d)), a.den ); } return bound( a.num.mul(d).sub(a.den), a.den.mul(d) ); } /** * Returns a Fraction128 that is equal to a / d * * @param a The first Fraction128 * @param d The divisor * @return The result (quotient) */ function div( Fraction.Fraction128 memory a, uint128 d ) internal pure returns (Fraction.Fraction128 memory) { if (a.num % d == 0) { return bound( a.num.div(d), a.den ); } return bound( a.num, a.den.mul(d) ); } /** * Returns a Fraction128 that is equal to a * b. * * @param a The first Fraction128 * @param b The second Fraction128 * @return The result (product) */ function mul( Fraction.Fraction128 memory a, Fraction.Fraction128 memory b ) internal pure returns (Fraction.Fraction128 memory) { return bound( a.num.mul(b.num), a.den.mul(b.den) ); } /** * Returns a fraction from two uint256's. Fits them into uint128 if necessary. * * @param num The numerator * @param den The denominator * @return The Fraction128 that matches num/den most closely */ /* solium-disable-next-line security/no-assign-params */ function bound( uint256 num, uint256 den ) internal pure returns (Fraction.Fraction128 memory) { uint256 max = num > den ? num : den; uint256 first128Bits = (max >> 128); if (first128Bits != 0) { first128Bits += 1; num /= first128Bits; den /= first128Bits; } assert(den != 0); // coverage-enable-line assert(den < 2**128); assert(num < 2**128); return Fraction.Fraction128({ num: uint128(num), den: uint128(den) }); } /** * Returns an in-memory copy of a Fraction128 * * @param a The Fraction128 to copy * @return A copy of the Fraction128 */ function copy( Fraction.Fraction128 memory a ) internal pure returns (Fraction.Fraction128 memory) { validate(a); return Fraction.Fraction128({ num: a.num, den: a.den }); } // ============ Private Helper-Functions ============ /** * Asserts that a Fraction128 is valid (i.e. the denominator is non-zero) * * @param a The Fraction128 to validate */ function validate( Fraction.Fraction128 memory a ) private pure { assert(a.den != 0); // coverage-enable-line } } // File: contracts/lib/Exponent.sol /** * @title Exponent * @author dYdX * * This library contains an implementation for calculating e^X for arbitrary fraction X */ library Exponent { using SafeMath for uint256; using FractionMath for Fraction.Fraction128; // ============ Constants ============ // 2**128 - 1 uint128 constant public MAX_NUMERATOR = 340282366920938463463374607431768211455; // Number of precomputed integers, X, for E^((1/2)^X) uint256 constant public MAX_PRECOMPUTE_PRECISION = 32; // Number of precomputed integers, X, for E^X uint256 constant public NUM_PRECOMPUTED_INTEGERS = 32; // ============ Public Implementation Functions ============ /** * Returns e^X for any fraction X * * @param X The exponent * @param precomputePrecision Accuracy of precomputed terms * @param maclaurinPrecision Accuracy of Maclaurin terms * @return e^X */ function exp( Fraction.Fraction128 memory X, uint256 precomputePrecision, uint256 maclaurinPrecision ) internal pure returns (Fraction.Fraction128 memory) { require( precomputePrecision <= MAX_PRECOMPUTE_PRECISION, "Exponent#exp: Precompute precision over maximum" ); Fraction.Fraction128 memory Xcopy = X.copy(); if (Xcopy.num == 0) { // e^0 = 1 return ONE(); } // get the integer value of the fraction (example: 9/4 is 2.25 so has integerValue of 2) uint256 integerX = uint256(Xcopy.num).div(Xcopy.den); // if X is less than 1, then just calculate X if (integerX == 0) { return expHybrid(Xcopy, precomputePrecision, maclaurinPrecision); } // get e^integerX Fraction.Fraction128 memory expOfInt = getPrecomputedEToThe(integerX % NUM_PRECOMPUTED_INTEGERS); while (integerX >= NUM_PRECOMPUTED_INTEGERS) { expOfInt = expOfInt.mul(getPrecomputedEToThe(NUM_PRECOMPUTED_INTEGERS)); integerX -= NUM_PRECOMPUTED_INTEGERS; } // multiply e^integerX by e^decimalX Fraction.Fraction128 memory decimalX = Fraction.Fraction128({ num: Xcopy.num % Xcopy.den, den: Xcopy.den }); return expHybrid(decimalX, precomputePrecision, maclaurinPrecision).mul(expOfInt); } /** * Returns e^X for any X < 1. Multiplies precomputed values to get close to the real value, then * Maclaurin Series approximation to reduce error. * * @param X Exponent * @param precomputePrecision Accuracy of precomputed terms * @param maclaurinPrecision Accuracy of Maclaurin terms * @return e^X */ function expHybrid( Fraction.Fraction128 memory X, uint256 precomputePrecision, uint256 maclaurinPrecision ) internal pure returns (Fraction.Fraction128 memory) { assert(precomputePrecision <= MAX_PRECOMPUTE_PRECISION); assert(X.num < X.den); // will also throw if precomputePrecision is larger than the array length in getDenominator Fraction.Fraction128 memory Xtemp = X.copy(); if (Xtemp.num == 0) { // e^0 = 1 return ONE(); } Fraction.Fraction128 memory result = ONE(); uint256 d = 1; // 2^i for (uint256 i = 1; i <= precomputePrecision; i++) { d *= 2; // if Fraction > 1/d, subtract 1/d and multiply result by precomputed e^(1/d) if (d.mul(Xtemp.num) >= Xtemp.den) { Xtemp = Xtemp.sub1Over(uint128(d)); result = result.mul(getPrecomputedEToTheHalfToThe(i)); } } return result.mul(expMaclaurin(Xtemp, maclaurinPrecision)); } /** * Returns e^X for any X, using Maclaurin Series approximation * * e^X = SUM(X^n / n!) for n >= 0 * e^X = 1 + X/1! + X^2/2! + X^3/3! ... * * @param X Exponent * @param precision Accuracy of Maclaurin terms * @return e^X */ function expMaclaurin( Fraction.Fraction128 memory X, uint256 precision ) internal pure returns (Fraction.Fraction128 memory) { Fraction.Fraction128 memory Xcopy = X.copy(); if (Xcopy.num == 0) { // e^0 = 1 return ONE(); } Fraction.Fraction128 memory result = ONE(); Fraction.Fraction128 memory Xtemp = ONE(); for (uint256 i = 1; i <= precision; i++) { Xtemp = Xtemp.mul(Xcopy.div(uint128(i))); result = result.add(Xtemp); } return result; } /** * Returns a fraction roughly equaling E^((1/2)^x) for integer x */ function getPrecomputedEToTheHalfToThe( uint256 x ) internal pure returns (Fraction.Fraction128 memory) { assert(x <= MAX_PRECOMPUTE_PRECISION); uint128 denominator = [ 125182886983370532117250726298150828301, 206391688497133195273760705512282642279, 265012173823417992016237332255925138361, 300298134811882980317033350418940119802, 319665700530617779809390163992561606014, 329812979126047300897653247035862915816, 335006777809430963166468914297166288162, 337634268532609249517744113622081347950, 338955731696479810470146282672867036734, 339618401537809365075354109784799900812, 339950222128463181389559457827561204959, 340116253979683015278260491021941090650, 340199300311581465057079429423749235412, 340240831081268226777032180141478221816, 340261598367316729254995498374473399540, 340271982485676106947851156443492415142, 340277174663693808406010255284800906112, 340279770782412691177936847400746725466, 340281068849199706686796915841848278311, 340281717884450116236033378667952410919, 340282042402539547492367191008339680733, 340282204661700319870089970029119685699, 340282285791309720262481214385569134454, 340282326356121674011576912006427792656, 340282346638529464274601981200276914173, 340282356779733812753265346086924801364, 340282361850336100329388676752133324799, 340282364385637272451648746721404212564, 340282365653287865596328444437856608255, 340282366287113163939555716675618384724, 340282366604025813553891209601455838559, 340282366762482138471739420386372790954, 340282366841710300958333641874363209044 ][x]; return Fraction.Fraction128({ num: MAX_NUMERATOR, den: denominator }); } /** * Returns a fraction roughly equaling E^(x) for integer x */ function getPrecomputedEToThe( uint256 x ) internal pure returns (Fraction.Fraction128 memory) { assert(x <= NUM_PRECOMPUTED_INTEGERS); uint128 denominator = [ 340282366920938463463374607431768211455, 125182886983370532117250726298150828301, 46052210507670172419625860892627118820, 16941661466271327126146327822211253888, 6232488952727653950957829210887653621, 2292804553036637136093891217529878878, 843475657686456657683449904934172134, 310297353591408453462393329342695980, 114152017036184782947077973323212575, 41994180235864621538772677139808695, 15448795557622704876497742989562086, 5683294276510101335127414470015662, 2090767122455392675095471286328463, 769150240628514374138961856925097, 282954560699298259527814398449860, 104093165666968799599694528310221, 38293735615330848145349245349513, 14087478058534870382224480725096, 5182493555688763339001418388912, 1906532833141383353974257736699, 701374233231058797338605168652, 258021160973090761055471434334, 94920680509187392077350434438, 34919366901332874995585576427, 12846117181722897538509298435, 4725822410035083116489797150, 1738532907279185132707372378, 639570514388029575350057932, 235284843422800231081973821, 86556456714490055457751527, 31842340925906738090071268, 11714142585413118080082437, 4309392228124372433711936 ][x]; return Fraction.Fraction128({ num: MAX_NUMERATOR, den: denominator }); } // ============ Private Helper-Functions ============ function ONE() private pure returns (Fraction.Fraction128 memory) { return Fraction.Fraction128({ num: 1, den: 1 }); } } // File: contracts/lib/MathHelpers.sol /** * @title MathHelpers * @author dYdX * * This library helps with common math functions in Solidity */ library MathHelpers { using SafeMath for uint256; /** * Calculates partial value given a numerator and denominator. * * @param numerator Numerator * @param denominator Denominator * @param target Value to calculate partial of * @return target * numerator / denominator */ function getPartialAmount( uint256 numerator, uint256 denominator, uint256 target ) internal pure returns (uint256) { return numerator.mul(target).div(denominator); } /** * Calculates partial value given a numerator and denominator, rounded up. * * @param numerator Numerator * @param denominator Denominator * @param target Value to calculate partial of * @return Rounded-up result of target * numerator / denominator */ function getPartialAmountRoundedUp( uint256 numerator, uint256 denominator, uint256 target ) internal pure returns (uint256) { return divisionRoundedUp(numerator.mul(target), denominator); } /** * Calculates division given a numerator and denominator, rounded up. * * @param numerator Numerator. * @param denominator Denominator. * @return Rounded-up result of numerator / denominator */ function divisionRoundedUp( uint256 numerator, uint256 denominator ) internal pure returns (uint256) { assert(denominator != 0); // coverage-enable-line if (numerator == 0) { return 0; } return numerator.sub(1).div(denominator).add(1); } /** * Calculates and returns the maximum value for a uint256 in solidity * * @return The maximum value for uint256 */ function maxUint256( ) internal pure returns (uint256) { return 2 ** 256 - 1; } /** * Calculates and returns the maximum value for a uint256 in solidity * * @return The maximum value for uint256 */ function maxUint32( ) internal pure returns (uint32) { return 2 ** 32 - 1; } /** * Returns the number of bits in a uint256. That is, the lowest number, x, such that n >> x == 0 * * @param n The uint256 to get the number of bits in * @return The number of bits in n */ function getNumBits( uint256 n ) internal pure returns (uint256) { uint256 first = 0; uint256 last = 256; while (first < last) { uint256 check = (first + last) / 2; if ((n >> check) == 0) { last = check; } else { first = check + 1; } } assert(first <= 256); return first; } } // File: contracts/margin/impl/InterestImpl.sol /** * @title InterestImpl * @author dYdX * * A library that calculates continuously compounded interest for principal, time period, and * interest rate. */ library InterestImpl { using SafeMath for uint256; using FractionMath for Fraction.Fraction128; // ============ Constants ============ uint256 constant DEFAULT_PRECOMPUTE_PRECISION = 11; uint256 constant DEFAULT_MACLAURIN_PRECISION = 5; uint256 constant MAXIMUM_EXPONENT = 80; uint128 constant E_TO_MAXIUMUM_EXPONENT = 55406223843935100525711733958316613; // ============ Public Implementation Functions ============ /** * Returns total tokens owed after accruing interest. Continuously compounding and accurate to * roughly 10^18 decimal places. Continuously compounding interest follows the formula: * I = P * e^(R*T) * * @param principal Principal of the interest calculation * @param interestRate Annual nominal interest percentage times 10**6. * (example: 5% = 5e6) * @param secondsOfInterest Number of seconds that interest has been accruing * @return Total amount of tokens owed. Greater than tokenAmount. */ function getCompoundedInterest( uint256 principal, uint256 interestRate, uint256 secondsOfInterest ) public pure returns (uint256) { uint256 numerator = interestRate.mul(secondsOfInterest); uint128 denominator = (10**8) * (365 * 1 days); // interestRate and secondsOfInterest should both be uint32 assert(numerator < 2**128); // fraction representing (Rate * Time) Fraction.Fraction128 memory rt = Fraction.Fraction128({ num: uint128(numerator), den: denominator }); // calculate e^(RT) Fraction.Fraction128 memory eToRT; if (numerator.div(denominator) >= MAXIMUM_EXPONENT) { // degenerate case: cap calculation eToRT = Fraction.Fraction128({ num: E_TO_MAXIUMUM_EXPONENT, den: 1 }); } else { // normal case: calculate e^(RT) eToRT = Exponent.exp( rt, DEFAULT_PRECOMPUTE_PRECISION, DEFAULT_MACLAURIN_PRECISION ); } // e^X for positive X should be greater-than or equal to 1 assert(eToRT.num >= eToRT.den); return safeMultiplyUint256ByFraction(principal, eToRT); } // ============ Private Helper-Functions ============ /** * Returns n * f, trying to prevent overflow as much as possible. Assumes that the numerator * and denominator of f are less than 2**128. */ function safeMultiplyUint256ByFraction( uint256 n, Fraction.Fraction128 memory f ) private pure returns (uint256) { uint256 term1 = n.div(2 ** 128); // first 128 bits uint256 term2 = n % (2 ** 128); // second 128 bits // uncommon scenario, requires n >= 2**128. calculates term1 = term1 * f if (term1 > 0) { term1 = term1.mul(f.num); uint256 numBits = MathHelpers.getNumBits(term1); // reduce rounding error by shifting all the way to the left before dividing term1 = MathHelpers.divisionRoundedUp( term1 << (uint256(256).sub(numBits)), f.den); // continue shifting or reduce shifting to get the right number if (numBits > 128) { term1 = term1 << (numBits.sub(128)); } else if (numBits < 128) { term1 = term1 >> (uint256(128).sub(numBits)); } } // calculates term2 = term2 * f term2 = MathHelpers.getPartialAmountRoundedUp( f.num, f.den, term2 ); return term1.add(term2); } } // File: contracts/margin/impl/MarginState.sol /** * @title MarginState * @author dYdX * * Contains state for the Margin contract. Also used by libraries that implement Margin functions. */ library MarginState { struct State { // Address of the Vault contract address VAULT; // Address of the TokenProxy contract address TOKEN_PROXY; // Mapping from loanHash -> amount, which stores the amount of a loan which has // already been filled. mapping (bytes32 => uint256) loanFills; // Mapping from loanHash -> amount, which stores the amount of a loan which has // already been canceled. mapping (bytes32 => uint256) loanCancels; // Mapping from positionId -> Position, which stores all the open margin positions. mapping (bytes32 => MarginCommon.Position) positions; // Mapping from positionId -> bool, which stores whether the position has previously been // open, but is now closed. mapping (bytes32 => bool) closedPositions; // Mapping from positionId -> uint256, which stores the total amount of owedToken that has // ever been repaid to the lender for each position. Does not reset. mapping (bytes32 => uint256) totalOwedTokenRepaidToLender; } } // File: contracts/margin/interfaces/lender/LoanOwner.sol /** * @title LoanOwner * @author dYdX * * Interface that smart contracts must implement in order to own loans on behalf of other accounts. * * NOTE: Any contract implementing this interface should also use OnlyMargin to control access * to these functions */ interface LoanOwner { // ============ Public Interface functions ============ /** * Function a contract must implement in order to receive ownership of a loan sell via the * transferLoan function or the atomic-assign to the "owner" field in a loan offering. * * @param from Address of the previous owner * @param positionId Unique ID of the position * @return This address to keep ownership, a different address to pass-on ownership */ function receiveLoanOwnership( address from, bytes32 positionId ) external /* onlyMargin */ returns (address); } // File: contracts/margin/interfaces/owner/PositionOwner.sol /** * @title PositionOwner * @author dYdX * * Interface that smart contracts must implement in order to own position on behalf of other * accounts * * NOTE: Any contract implementing this interface should also use OnlyMargin to control access * to these functions */ interface PositionOwner { // ============ Public Interface functions ============ /** * Function a contract must implement in order to receive ownership of a position via the * transferPosition function or the atomic-assign to the "owner" field when opening a position. * * @param from Address of the previous owner * @param positionId Unique ID of the position * @return This address to keep ownership, a different address to pass-on ownership */ function receivePositionOwnership( address from, bytes32 positionId ) external /* onlyMargin */ returns (address); } // File: contracts/margin/impl/TransferInternal.sol /** * @title TransferInternal * @author dYdX * * This library contains the implementation for transferring ownership of loans and positions. */ library TransferInternal { // ============ Events ============ /** * Ownership of a loan was transferred to a new address */ event LoanTransferred( bytes32 indexed positionId, address indexed from, address indexed to ); /** * Ownership of a postion was transferred to a new address */ event PositionTransferred( bytes32 indexed positionId, address indexed from, address indexed to ); // ============ Internal Implementation Functions ============ /** * Returns either the address of the new loan owner, or the address to which they wish to * pass ownership of the loan. This function does not actually set the state of the position * * @param positionId The Unique ID of the position * @param oldOwner The previous owner of the loan * @param newOwner The intended owner of the loan * @return The address that the intended owner wishes to assign the loan to (may be * the same as the intended owner). */ function grantLoanOwnership( bytes32 positionId, address oldOwner, address newOwner ) internal returns (address) { // log event except upon position creation if (oldOwner != address(0)) { emit LoanTransferred(positionId, oldOwner, newOwner); } if (AddressUtils.isContract(newOwner)) { address nextOwner = LoanOwner(newOwner).receiveLoanOwnership(oldOwner, positionId); if (nextOwner != newOwner) { return grantLoanOwnership(positionId, newOwner, nextOwner); } } require( newOwner != address(0), "TransferInternal#grantLoanOwnership: New owner did not consent to owning loan" ); return newOwner; } /** * Returns either the address of the new position owner, or the address to which they wish to * pass ownership of the position. This function does not actually set the state of the position * * @param positionId The Unique ID of the position * @param oldOwner The previous owner of the position * @param newOwner The intended owner of the position * @return The address that the intended owner wishes to assign the position to (may * be the same as the intended owner). */ function grantPositionOwnership( bytes32 positionId, address oldOwner, address newOwner ) internal returns (address) { // log event except upon position creation if (oldOwner != address(0)) { emit PositionTransferred(positionId, oldOwner, newOwner); } if (AddressUtils.isContract(newOwner)) { address nextOwner = PositionOwner(newOwner).receivePositionOwnership(oldOwner, positionId); if (nextOwner != newOwner) { return grantPositionOwnership(positionId, newOwner, nextOwner); } } require( newOwner != address(0), "TransferInternal#grantPositionOwnership: New owner did not consent to owning position" ); return newOwner; } } // File: contracts/lib/TimestampHelper.sol /** * @title TimestampHelper * @author dYdX * * Helper to get block timestamps in other formats */ library TimestampHelper { function getBlockTimestamp32() internal view returns (uint32) { // Should not still be in-use in the year 2106 assert(uint256(uint32(block.timestamp)) == block.timestamp); assert(block.timestamp > 0); return uint32(block.timestamp); } } // File: contracts/margin/impl/MarginCommon.sol /** * @title MarginCommon * @author dYdX * * This library contains common functions for implementations of public facing Margin functions */ library MarginCommon { using SafeMath for uint256; // ============ Structs ============ struct Position { address owedToken; // Immutable address heldToken; // Immutable address lender; address owner; uint256 principal; uint256 requiredDeposit; uint32 callTimeLimit; // Immutable uint32 startTimestamp; // Immutable, cannot be 0 uint32 callTimestamp; uint32 maxDuration; // Immutable uint32 interestRate; // Immutable uint32 interestPeriod; // Immutable } struct LoanOffering { address owedToken; address heldToken; address payer; address owner; address taker; address positionOwner; address feeRecipient; address lenderFeeToken; address takerFeeToken; LoanRates rates; uint256 expirationTimestamp; uint32 callTimeLimit; uint32 maxDuration; uint256 salt; bytes32 loanHash; bytes signature; } struct LoanRates { uint256 maxAmount; uint256 minAmount; uint256 minHeldToken; uint256 lenderFee; uint256 takerFee; uint32 interestRate; uint32 interestPeriod; } // ============ Internal Implementation Functions ============ function storeNewPosition( MarginState.State storage state, bytes32 positionId, Position memory position, address loanPayer ) internal { assert(!positionHasExisted(state, positionId)); assert(position.owedToken != address(0)); assert(position.heldToken != address(0)); assert(position.owedToken != position.heldToken); assert(position.owner != address(0)); assert(position.lender != address(0)); assert(position.maxDuration != 0); assert(position.interestPeriod <= position.maxDuration); assert(position.callTimestamp == 0); assert(position.requiredDeposit == 0); state.positions[positionId].owedToken = position.owedToken; state.positions[positionId].heldToken = position.heldToken; state.positions[positionId].principal = position.principal; state.positions[positionId].callTimeLimit = position.callTimeLimit; state.positions[positionId].startTimestamp = TimestampHelper.getBlockTimestamp32(); state.positions[positionId].maxDuration = position.maxDuration; state.positions[positionId].interestRate = position.interestRate; state.positions[positionId].interestPeriod = position.interestPeriod; state.positions[positionId].owner = TransferInternal.grantPositionOwnership( positionId, (position.owner != msg.sender) ? msg.sender : address(0), position.owner ); state.positions[positionId].lender = TransferInternal.grantLoanOwnership( positionId, (position.lender != loanPayer) ? loanPayer : address(0), position.lender ); } function getPositionIdFromNonce( uint256 nonce ) internal view returns (bytes32) { return keccak256(abi.encodePacked(msg.sender, nonce)); } function getUnavailableLoanOfferingAmountImpl( MarginState.State storage state, bytes32 loanHash ) internal view returns (uint256) { return state.loanFills[loanHash].add(state.loanCancels[loanHash]); } function cleanupPosition( MarginState.State storage state, bytes32 positionId ) internal { delete state.positions[positionId]; state.closedPositions[positionId] = true; } function calculateOwedAmount( Position storage position, uint256 closeAmount, uint256 endTimestamp ) internal view returns (uint256) { uint256 timeElapsed = calculateEffectiveTimeElapsed(position, endTimestamp); return InterestImpl.getCompoundedInterest( closeAmount, position.interestRate, timeElapsed ); } /** * Calculates time elapsed rounded up to the nearest interestPeriod */ function calculateEffectiveTimeElapsed( Position storage position, uint256 timestamp ) internal view returns (uint256) { uint256 elapsed = timestamp.sub(position.startTimestamp); // round up to interestPeriod uint256 period = position.interestPeriod; if (period > 1) { elapsed = MathHelpers.divisionRoundedUp(elapsed, period).mul(period); } // bound by maxDuration return Math.min256( elapsed, position.maxDuration ); } function calculateLenderAmountForIncreasePosition( Position storage position, uint256 principalToAdd, uint256 endTimestamp ) internal view returns (uint256) { uint256 timeElapsed = calculateEffectiveTimeElapsedForNewLender(position, endTimestamp); return InterestImpl.getCompoundedInterest( principalToAdd, position.interestRate, timeElapsed ); } function getLoanOfferingHash( LoanOffering loanOffering ) internal view returns (bytes32) { return keccak256( abi.encodePacked( address(this), loanOffering.owedToken, loanOffering.heldToken, loanOffering.payer, loanOffering.owner, loanOffering.taker, loanOffering.positionOwner, loanOffering.feeRecipient, loanOffering.lenderFeeToken, loanOffering.takerFeeToken, getValuesHash(loanOffering) ) ); } function getPositionBalanceImpl( MarginState.State storage state, bytes32 positionId ) internal view returns(uint256) { return Vault(state.VAULT).balances(positionId, state.positions[positionId].heldToken); } function containsPositionImpl( MarginState.State storage state, bytes32 positionId ) internal view returns (bool) { return state.positions[positionId].startTimestamp != 0; } function positionHasExisted( MarginState.State storage state, bytes32 positionId ) internal view returns (bool) { return containsPositionImpl(state, positionId) || state.closedPositions[positionId]; } function getPositionFromStorage( MarginState.State storage state, bytes32 positionId ) internal view returns (Position storage) { Position storage position = state.positions[positionId]; require( position.startTimestamp != 0, "MarginCommon#getPositionFromStorage: The position does not exist" ); return position; } // ============ Private Helper-Functions ============ /** * Calculates time elapsed rounded down to the nearest interestPeriod */ function calculateEffectiveTimeElapsedForNewLender( Position storage position, uint256 timestamp ) private view returns (uint256) { uint256 elapsed = timestamp.sub(position.startTimestamp); // round down to interestPeriod uint256 period = position.interestPeriod; if (period > 1) { elapsed = elapsed.div(period).mul(period); } // bound by maxDuration return Math.min256( elapsed, position.maxDuration ); } function getValuesHash( LoanOffering loanOffering ) private pure returns (bytes32) { return keccak256( abi.encodePacked( loanOffering.rates.maxAmount, loanOffering.rates.minAmount, loanOffering.rates.minHeldToken, loanOffering.rates.lenderFee, loanOffering.rates.takerFee, loanOffering.expirationTimestamp, loanOffering.salt, loanOffering.callTimeLimit, loanOffering.maxDuration, loanOffering.rates.interestRate, loanOffering.rates.interestPeriod ) ); } } // File: contracts/margin/interfaces/PayoutRecipient.sol /** * @title PayoutRecipient * @author dYdX * * Interface that smart contracts must implement in order to be the payoutRecipient in a * closePosition transaction. * * NOTE: Any contract implementing this interface should also use OnlyMargin to control access * to these functions */ interface PayoutRecipient { // ============ Public Interface functions ============ /** * Function a contract must implement in order to receive payout from being the payoutRecipient * in a closePosition transaction. May redistribute any payout as necessary. Throws on error. * * @param positionId Unique ID of the position * @param closeAmount Amount of the position that was closed * @param closer Address of the account or contract that closed the position * @param positionOwner Address of the owner of the position * @param heldToken Address of the ERC20 heldToken * @param payout Number of tokens received from the payout * @param totalHeldToken Total amount of heldToken removed from vault during close * @param payoutInHeldToken True if payout is in heldToken, false if in owedToken * @return True if approved by the receiver */ function receiveClosePositionPayout( bytes32 positionId, uint256 closeAmount, address closer, address positionOwner, address heldToken, uint256 payout, uint256 totalHeldToken, bool payoutInHeldToken ) external /* onlyMargin */ returns (bool); } // File: contracts/margin/interfaces/lender/CloseLoanDelegator.sol /** * @title CloseLoanDelegator * @author dYdX * * Interface that smart contracts must implement in order to let other addresses close a loan * owned by the smart contract. * * NOTE: Any contract implementing this interface should also use OnlyMargin to control access * to these functions */ interface CloseLoanDelegator { // ============ Public Interface functions ============ /** * Function a contract must implement in order to let other addresses call * closeWithoutCounterparty(). * * NOTE: If not returning zero (or not reverting), this contract must assume that Margin will * either revert the entire transaction or that (at most) the specified amount of the loan was * successfully closed. * * @param closer Address of the caller of closeWithoutCounterparty() * @param payoutRecipient Address of the recipient of tokens paid out from closing * @param positionId Unique ID of the position * @param requestedAmount Requested principal amount of the loan to close * @return 1) This address to accept, a different address to ask that contract * 2) The maximum amount that this contract is allowing */ function closeLoanOnBehalfOf( address closer, address payoutRecipient, bytes32 positionId, uint256 requestedAmount ) external /* onlyMargin */ returns (address, uint256); } // File: contracts/margin/interfaces/owner/ClosePositionDelegator.sol /** * @title ClosePositionDelegator * @author dYdX * * Interface that smart contracts must implement in order to let other addresses close a position * owned by the smart contract, allowing more complex logic to control positions. * * NOTE: Any contract implementing this interface should also use OnlyMargin to control access * to these functions */ interface ClosePositionDelegator { // ============ Public Interface functions ============ /** * Function a contract must implement in order to let other addresses call closePosition(). * * NOTE: If not returning zero (or not reverting), this contract must assume that Margin will * either revert the entire transaction or that (at-most) the specified amount of the position * was successfully closed. * * @param closer Address of the caller of the closePosition() function * @param payoutRecipient Address of the recipient of tokens paid out from closing * @param positionId Unique ID of the position * @param requestedAmount Requested principal amount of the position to close * @return 1) This address to accept, a different address to ask that contract * 2) The maximum amount that this contract is allowing */ function closeOnBehalfOf( address closer, address payoutRecipient, bytes32 positionId, uint256 requestedAmount ) external /* onlyMargin */ returns (address, uint256); } // File: contracts/margin/impl/ClosePositionShared.sol /** * @title ClosePositionShared * @author dYdX * * This library contains shared functionality between ClosePositionImpl and * CloseWithoutCounterpartyImpl */ library ClosePositionShared { using SafeMath for uint256; // ============ Structs ============ struct CloseTx { bytes32 positionId; uint256 originalPrincipal; uint256 closeAmount; uint256 owedTokenOwed; uint256 startingHeldTokenBalance; uint256 availableHeldToken; address payoutRecipient; address owedToken; address heldToken; address positionOwner; address positionLender; address exchangeWrapper; bool payoutInHeldToken; } // ============ Internal Implementation Functions ============ function closePositionStateUpdate( MarginState.State storage state, CloseTx memory transaction ) internal { // Delete the position, or just decrease the principal if (transaction.closeAmount == transaction.originalPrincipal) { MarginCommon.cleanupPosition(state, transaction.positionId); } else { assert( transaction.originalPrincipal == state.positions[transaction.positionId].principal ); state.positions[transaction.positionId].principal = transaction.originalPrincipal.sub(transaction.closeAmount); } } function sendTokensToPayoutRecipient( MarginState.State storage state, ClosePositionShared.CloseTx memory transaction, uint256 buybackCostInHeldToken, uint256 receivedOwedToken ) internal returns (uint256) { uint256 payout; if (transaction.payoutInHeldToken) { // Send remaining heldToken to payoutRecipient payout = transaction.availableHeldToken.sub(buybackCostInHeldToken); Vault(state.VAULT).transferFromVault( transaction.positionId, transaction.heldToken, transaction.payoutRecipient, payout ); } else { assert(transaction.exchangeWrapper != address(0)); payout = receivedOwedToken.sub(transaction.owedTokenOwed); TokenProxy(state.TOKEN_PROXY).transferTokens( transaction.owedToken, transaction.exchangeWrapper, transaction.payoutRecipient, payout ); } if (AddressUtils.isContract(transaction.payoutRecipient)) { require( PayoutRecipient(transaction.payoutRecipient).receiveClosePositionPayout( transaction.positionId, transaction.closeAmount, msg.sender, transaction.positionOwner, transaction.heldToken, payout, transaction.availableHeldToken, transaction.payoutInHeldToken ), "ClosePositionShared#sendTokensToPayoutRecipient: Payout recipient does not consent" ); } // The ending heldToken balance of the vault should be the starting heldToken balance // minus the available heldToken amount assert( MarginCommon.getPositionBalanceImpl(state, transaction.positionId) == transaction.startingHeldTokenBalance.sub(transaction.availableHeldToken) ); return payout; } function createCloseTx( MarginState.State storage state, bytes32 positionId, uint256 requestedAmount, address payoutRecipient, address exchangeWrapper, bool payoutInHeldToken, bool isWithoutCounterparty ) internal returns (CloseTx memory) { // Validate require( payoutRecipient != address(0), "ClosePositionShared#createCloseTx: Payout recipient cannot be 0" ); require( requestedAmount > 0, "ClosePositionShared#createCloseTx: Requested close amount cannot be 0" ); MarginCommon.Position storage position = MarginCommon.getPositionFromStorage(state, positionId); uint256 closeAmount = getApprovedAmount( position, positionId, requestedAmount, payoutRecipient, isWithoutCounterparty ); return parseCloseTx( state, position, positionId, closeAmount, payoutRecipient, exchangeWrapper, payoutInHeldToken, isWithoutCounterparty ); } // ============ Private Helper-Functions ============ function getApprovedAmount( MarginCommon.Position storage position, bytes32 positionId, uint256 requestedAmount, address payoutRecipient, bool requireLenderApproval ) private returns (uint256) { // Ensure enough principal uint256 allowedAmount = Math.min256(requestedAmount, position.principal); // Ensure owner consent allowedAmount = closePositionOnBehalfOfRecurse( position.owner, msg.sender, payoutRecipient, positionId, allowedAmount ); // Ensure lender consent if (requireLenderApproval) { allowedAmount = closeLoanOnBehalfOfRecurse( position.lender, msg.sender, payoutRecipient, positionId, allowedAmount ); } assert(allowedAmount > 0); assert(allowedAmount <= position.principal); assert(allowedAmount <= requestedAmount); return allowedAmount; } function closePositionOnBehalfOfRecurse( address contractAddr, address closer, address payoutRecipient, bytes32 positionId, uint256 closeAmount ) private returns (uint256) { // no need to ask for permission if (closer == contractAddr) { return closeAmount; } ( address newContractAddr, uint256 newCloseAmount ) = ClosePositionDelegator(contractAddr).closeOnBehalfOf( closer, payoutRecipient, positionId, closeAmount ); require( newCloseAmount <= closeAmount, "ClosePositionShared#closePositionRecurse: newCloseAmount is greater than closeAmount" ); require( newCloseAmount > 0, "ClosePositionShared#closePositionRecurse: newCloseAmount is zero" ); if (newContractAddr != contractAddr) { closePositionOnBehalfOfRecurse( newContractAddr, closer, payoutRecipient, positionId, newCloseAmount ); } return newCloseAmount; } function closeLoanOnBehalfOfRecurse( address contractAddr, address closer, address payoutRecipient, bytes32 positionId, uint256 closeAmount ) private returns (uint256) { // no need to ask for permission if (closer == contractAddr) { return closeAmount; } ( address newContractAddr, uint256 newCloseAmount ) = CloseLoanDelegator(contractAddr).closeLoanOnBehalfOf( closer, payoutRecipient, positionId, closeAmount ); require( newCloseAmount <= closeAmount, "ClosePositionShared#closeLoanRecurse: newCloseAmount is greater than closeAmount" ); require( newCloseAmount > 0, "ClosePositionShared#closeLoanRecurse: newCloseAmount is zero" ); if (newContractAddr != contractAddr) { closeLoanOnBehalfOfRecurse( newContractAddr, closer, payoutRecipient, positionId, newCloseAmount ); } return newCloseAmount; } // ============ Parsing Functions ============ function parseCloseTx( MarginState.State storage state, MarginCommon.Position storage position, bytes32 positionId, uint256 closeAmount, address payoutRecipient, address exchangeWrapper, bool payoutInHeldToken, bool isWithoutCounterparty ) private view returns (CloseTx memory) { uint256 startingHeldTokenBalance = MarginCommon.getPositionBalanceImpl(state, positionId); uint256 availableHeldToken = MathHelpers.getPartialAmount( closeAmount, position.principal, startingHeldTokenBalance ); uint256 owedTokenOwed = 0; if (!isWithoutCounterparty) { owedTokenOwed = MarginCommon.calculateOwedAmount( position, closeAmount, block.timestamp ); } return CloseTx({ positionId: positionId, originalPrincipal: position.principal, closeAmount: closeAmount, owedTokenOwed: owedTokenOwed, startingHeldTokenBalance: startingHeldTokenBalance, availableHeldToken: availableHeldToken, payoutRecipient: payoutRecipient, owedToken: position.owedToken, heldToken: position.heldToken, positionOwner: position.owner, positionLender: position.lender, exchangeWrapper: exchangeWrapper, payoutInHeldToken: payoutInHeldToken }); } } // File: contracts/margin/interfaces/ExchangeWrapper.sol /** * @title ExchangeWrapper * @author dYdX * * Contract interface that Exchange Wrapper smart contracts must implement in order to interface * with other smart contracts through a common interface. */ interface ExchangeWrapper { // ============ Public Functions ============ /** * Exchange some amount of takerToken for makerToken. * * @param tradeOriginator Address of the initiator of the trade (however, this value * cannot always be trusted as it is set at the discretion of the * msg.sender) * @param receiver Address to set allowance on once the trade has completed * @param makerToken Address of makerToken, the token to receive * @param takerToken Address of takerToken, the token to pay * @param requestedFillAmount Amount of takerToken being paid * @param orderData Arbitrary bytes data for any information to pass to the exchange * @return The amount of makerToken received */ function exchange( address tradeOriginator, address receiver, address makerToken, address takerToken, uint256 requestedFillAmount, bytes orderData ) external returns (uint256); /** * Get amount of takerToken required to buy a certain amount of makerToken for a given trade. * Should match the takerToken amount used in exchangeForAmount. If the order cannot provide * exactly desiredMakerToken, then it must return the price to buy the minimum amount greater * than desiredMakerToken * * @param makerToken Address of makerToken, the token to receive * @param takerToken Address of takerToken, the token to pay * @param desiredMakerToken Amount of makerToken requested * @param orderData Arbitrary bytes data for any information to pass to the exchange * @return Amount of takerToken the needed to complete the transaction */ function getExchangeCost( address makerToken, address takerToken, uint256 desiredMakerToken, bytes orderData ) external view returns (uint256); } // File: contracts/margin/impl/ClosePositionImpl.sol /** * @title ClosePositionImpl * @author dYdX * * This library contains the implementation for the closePosition function of Margin */ library ClosePositionImpl { using SafeMath for uint256; // ============ Events ============ /** * A position was closed or partially closed */ event PositionClosed( bytes32 indexed positionId, address indexed closer, address indexed payoutRecipient, uint256 closeAmount, uint256 remainingAmount, uint256 owedTokenPaidToLender, uint256 payoutAmount, uint256 buybackCostInHeldToken, bool payoutInHeldToken ); // ============ Public Implementation Functions ============ function closePositionImpl( MarginState.State storage state, bytes32 positionId, uint256 requestedCloseAmount, address payoutRecipient, address exchangeWrapper, bool payoutInHeldToken, bytes memory orderData ) public returns (uint256, uint256, uint256) { ClosePositionShared.CloseTx memory transaction = ClosePositionShared.createCloseTx( state, positionId, requestedCloseAmount, payoutRecipient, exchangeWrapper, payoutInHeldToken, false ); ( uint256 buybackCostInHeldToken, uint256 receivedOwedToken ) = returnOwedTokensToLender( state, transaction, orderData ); uint256 payout = ClosePositionShared.sendTokensToPayoutRecipient( state, transaction, buybackCostInHeldToken, receivedOwedToken ); ClosePositionShared.closePositionStateUpdate(state, transaction); logEventOnClose( transaction, buybackCostInHeldToken, payout ); return ( transaction.closeAmount, payout, transaction.owedTokenOwed ); } // ============ Private Helper-Functions ============ function returnOwedTokensToLender( MarginState.State storage state, ClosePositionShared.CloseTx memory transaction, bytes memory orderData ) private returns (uint256, uint256) { uint256 buybackCostInHeldToken = 0; uint256 receivedOwedToken = 0; uint256 lenderOwedToken = transaction.owedTokenOwed; // Setting exchangeWrapper to 0x000... indicates owedToken should be taken directly // from msg.sender if (transaction.exchangeWrapper == address(0)) { require( transaction.payoutInHeldToken, "ClosePositionImpl#returnOwedTokensToLender: Cannot payout in owedToken" ); // No DEX Order; send owedTokens directly from the closer to the lender TokenProxy(state.TOKEN_PROXY).transferTokens( transaction.owedToken, msg.sender, transaction.positionLender, lenderOwedToken ); } else { // Buy back owedTokens using DEX Order and send to lender (buybackCostInHeldToken, receivedOwedToken) = buyBackOwedToken( state, transaction, orderData ); // If no owedToken needed for payout: give lender all owedToken, even if more than owed if (transaction.payoutInHeldToken) { assert(receivedOwedToken >= lenderOwedToken); lenderOwedToken = receivedOwedToken; } // Transfer owedToken from the exchange wrapper to the lender TokenProxy(state.TOKEN_PROXY).transferTokens( transaction.owedToken, transaction.exchangeWrapper, transaction.positionLender, lenderOwedToken ); } state.totalOwedTokenRepaidToLender[transaction.positionId] = state.totalOwedTokenRepaidToLender[transaction.positionId].add(lenderOwedToken); return (buybackCostInHeldToken, receivedOwedToken); } function buyBackOwedToken( MarginState.State storage state, ClosePositionShared.CloseTx transaction, bytes memory orderData ) private returns (uint256, uint256) { // Ask the exchange wrapper the cost in heldToken to buy back the close // amount of owedToken uint256 buybackCostInHeldToken; if (transaction.payoutInHeldToken) { buybackCostInHeldToken = ExchangeWrapper(transaction.exchangeWrapper) .getExchangeCost( transaction.owedToken, transaction.heldToken, transaction.owedTokenOwed, orderData ); // Require enough available heldToken to pay for the buyback require( buybackCostInHeldToken <= transaction.availableHeldToken, "ClosePositionImpl#buyBackOwedToken: Not enough available heldToken" ); } else { buybackCostInHeldToken = transaction.availableHeldToken; } // Send the requisite heldToken to do the buyback from vault to exchange wrapper Vault(state.VAULT).transferFromVault( transaction.positionId, transaction.heldToken, transaction.exchangeWrapper, buybackCostInHeldToken ); // Trade the heldToken for the owedToken uint256 receivedOwedToken = ExchangeWrapper(transaction.exchangeWrapper).exchange( msg.sender, state.TOKEN_PROXY, transaction.owedToken, transaction.heldToken, buybackCostInHeldToken, orderData ); require( receivedOwedToken >= transaction.owedTokenOwed, "ClosePositionImpl#buyBackOwedToken: Did not receive enough owedToken" ); return (buybackCostInHeldToken, receivedOwedToken); } function logEventOnClose( ClosePositionShared.CloseTx transaction, uint256 buybackCostInHeldToken, uint256 payout ) private { emit PositionClosed( transaction.positionId, msg.sender, transaction.payoutRecipient, transaction.closeAmount, transaction.originalPrincipal.sub(transaction.closeAmount), transaction.owedTokenOwed, payout, buybackCostInHeldToken, transaction.payoutInHeldToken ); } } // File: contracts/margin/impl/CloseWithoutCounterpartyImpl.sol /** * @title CloseWithoutCounterpartyImpl * @author dYdX * * This library contains the implementation for the closeWithoutCounterpartyImpl function of * Margin */ library CloseWithoutCounterpartyImpl { using SafeMath for uint256; // ============ Events ============ /** * A position was closed or partially closed */ event PositionClosed( bytes32 indexed positionId, address indexed closer, address indexed payoutRecipient, uint256 closeAmount, uint256 remainingAmount, uint256 owedTokenPaidToLender, uint256 payoutAmount, uint256 buybackCostInHeldToken, bool payoutInHeldToken ); // ============ Public Implementation Functions ============ function closeWithoutCounterpartyImpl( MarginState.State storage state, bytes32 positionId, uint256 requestedCloseAmount, address payoutRecipient ) public returns (uint256, uint256) { ClosePositionShared.CloseTx memory transaction = ClosePositionShared.createCloseTx( state, positionId, requestedCloseAmount, payoutRecipient, address(0), true, true ); uint256 heldTokenPayout = ClosePositionShared.sendTokensToPayoutRecipient( state, transaction, 0, // No buyback cost 0 // Did not receive any owedToken ); ClosePositionShared.closePositionStateUpdate(state, transaction); logEventOnCloseWithoutCounterparty(transaction); return ( transaction.closeAmount, heldTokenPayout ); } // ============ Private Helper-Functions ============ function logEventOnCloseWithoutCounterparty( ClosePositionShared.CloseTx transaction ) private { emit PositionClosed( transaction.positionId, msg.sender, transaction.payoutRecipient, transaction.closeAmount, transaction.originalPrincipal.sub(transaction.closeAmount), 0, transaction.availableHeldToken, 0, true ); } } // File: contracts/margin/interfaces/owner/DepositCollateralDelegator.sol /** * @title DepositCollateralDelegator * @author dYdX * * Interface that smart contracts must implement in order to let other addresses deposit heldTokens * into a position owned by the smart contract. * * NOTE: Any contract implementing this interface should also use OnlyMargin to control access * to these functions */ interface DepositCollateralDelegator { // ============ Public Interface functions ============ /** * Function a contract must implement in order to let other addresses call depositCollateral(). * * @param depositor Address of the caller of the depositCollateral() function * @param positionId Unique ID of the position * @param amount Requested deposit amount * @return This address to accept, a different address to ask that contract */ function depositCollateralOnBehalfOf( address depositor, bytes32 positionId, uint256 amount ) external /* onlyMargin */ returns (address); } // File: contracts/margin/impl/DepositCollateralImpl.sol /** * @title DepositCollateralImpl * @author dYdX * * This library contains the implementation for the deposit function of Margin */ library DepositCollateralImpl { using SafeMath for uint256; // ============ Events ============ /** * Additional collateral for a position was posted by the owner */ event AdditionalCollateralDeposited( bytes32 indexed positionId, uint256 amount, address depositor ); /** * A margin call was canceled */ event MarginCallCanceled( bytes32 indexed positionId, address indexed lender, address indexed owner, uint256 depositAmount ); // ============ Public Implementation Functions ============ function depositCollateralImpl( MarginState.State storage state, bytes32 positionId, uint256 depositAmount ) public { MarginCommon.Position storage position = MarginCommon.getPositionFromStorage(state, positionId); require( depositAmount > 0, "DepositCollateralImpl#depositCollateralImpl: Deposit amount cannot be 0" ); // Ensure owner consent depositCollateralOnBehalfOfRecurse( position.owner, msg.sender, positionId, depositAmount ); Vault(state.VAULT).transferToVault( positionId, position.heldToken, msg.sender, depositAmount ); // cancel margin call if applicable bool marginCallCanceled = false; uint256 requiredDeposit = position.requiredDeposit; if (position.callTimestamp > 0 && requiredDeposit > 0) { if (depositAmount >= requiredDeposit) { position.requiredDeposit = 0; position.callTimestamp = 0; marginCallCanceled = true; } else { position.requiredDeposit = position.requiredDeposit.sub(depositAmount); } } emit AdditionalCollateralDeposited( positionId, depositAmount, msg.sender ); if (marginCallCanceled) { emit MarginCallCanceled( positionId, position.lender, msg.sender, depositAmount ); } } // ============ Private Helper-Functions ============ function depositCollateralOnBehalfOfRecurse( address contractAddr, address depositor, bytes32 positionId, uint256 amount ) private { // no need to ask for permission if (depositor == contractAddr) { return; } address newContractAddr = DepositCollateralDelegator(contractAddr).depositCollateralOnBehalfOf( depositor, positionId, amount ); // if not equal, recurse if (newContractAddr != contractAddr) { depositCollateralOnBehalfOfRecurse( newContractAddr, depositor, positionId, amount ); } } } // File: contracts/margin/interfaces/lender/ForceRecoverCollateralDelegator.sol /** * @title ForceRecoverCollateralDelegator * @author dYdX * * Interface that smart contracts must implement in order to let other addresses * forceRecoverCollateral() a loan owned by the smart contract. * * NOTE: Any contract implementing this interface should also use OnlyMargin to control access * to these functions */ interface ForceRecoverCollateralDelegator { // ============ Public Interface functions ============ /** * Function a contract must implement in order to let other addresses call * forceRecoverCollateral(). * * NOTE: If not returning zero address (or not reverting), this contract must assume that Margin * will either revert the entire transaction or that the collateral was forcibly recovered. * * @param recoverer Address of the caller of the forceRecoverCollateral() function * @param positionId Unique ID of the position * @param recipient Address to send the recovered tokens to * @return This address to accept, a different address to ask that contract */ function forceRecoverCollateralOnBehalfOf( address recoverer, bytes32 positionId, address recipient ) external /* onlyMargin */ returns (address); } // File: contracts/margin/impl/ForceRecoverCollateralImpl.sol /* solium-disable-next-line max-len*/ /** * @title ForceRecoverCollateralImpl * @author dYdX * * This library contains the implementation for the forceRecoverCollateral function of Margin */ library ForceRecoverCollateralImpl { using SafeMath for uint256; // ============ Events ============ /** * Collateral for a position was forcibly recovered */ event CollateralForceRecovered( bytes32 indexed positionId, address indexed recipient, uint256 amount ); // ============ Public Implementation Functions ============ function forceRecoverCollateralImpl( MarginState.State storage state, bytes32 positionId, address recipient ) public returns (uint256) { MarginCommon.Position storage position = MarginCommon.getPositionFromStorage(state, positionId); // Can only force recover after either: // 1) The loan was called and the call period has elapsed // 2) The maxDuration of the position has elapsed require( /* solium-disable-next-line */ ( position.callTimestamp > 0 && block.timestamp >= uint256(position.callTimestamp).add(position.callTimeLimit) ) || ( block.timestamp >= uint256(position.startTimestamp).add(position.maxDuration) ), "ForceRecoverCollateralImpl#forceRecoverCollateralImpl: Cannot recover yet" ); // Ensure lender consent forceRecoverCollateralOnBehalfOfRecurse( position.lender, msg.sender, positionId, recipient ); // Send the tokens uint256 heldTokenRecovered = MarginCommon.getPositionBalanceImpl(state, positionId); Vault(state.VAULT).transferFromVault( positionId, position.heldToken, recipient, heldTokenRecovered ); // Delete the position // NOTE: Since position is a storage pointer, this will also set all fields on // the position variable to 0 MarginCommon.cleanupPosition( state, positionId ); // Log an event emit CollateralForceRecovered( positionId, recipient, heldTokenRecovered ); return heldTokenRecovered; } // ============ Private Helper-Functions ============ function forceRecoverCollateralOnBehalfOfRecurse( address contractAddr, address recoverer, bytes32 positionId, address recipient ) private { // no need to ask for permission if (recoverer == contractAddr) { return; } address newContractAddr = ForceRecoverCollateralDelegator(contractAddr).forceRecoverCollateralOnBehalfOf( recoverer, positionId, recipient ); if (newContractAddr != contractAddr) { forceRecoverCollateralOnBehalfOfRecurse( newContractAddr, recoverer, positionId, recipient ); } } } // File: contracts/lib/TypedSignature.sol /** * @title TypedSignature * @author dYdX * * Allows for ecrecovery of signed hashes with three different prepended messages: * 1) "" * 2) "\x19Ethereum Signed Message:\n32" * 3) "\x19Ethereum Signed Message:\n\x20" */ library TypedSignature { // Solidity does not offer guarantees about enum values, so we define them explicitly uint8 private constant SIGTYPE_INVALID = 0; uint8 private constant SIGTYPE_ECRECOVER_DEC = 1; uint8 private constant SIGTYPE_ECRECOVER_HEX = 2; uint8 private constant SIGTYPE_UNSUPPORTED = 3; // prepended message with the length of the signed hash in hexadecimal bytes constant private PREPEND_HEX = "\x19Ethereum Signed Message:\n\x20"; // prepended message with the length of the signed hash in decimal bytes constant private PREPEND_DEC = "\x19Ethereum Signed Message:\n32"; /** * Gives the address of the signer of a hash. Allows for three common prepended strings. * * @param hash Hash that was signed (does not include prepended message) * @param signatureWithType Type and ECDSA signature with structure: {1:type}{1:v}{32:r}{32:s} * @return address of the signer of the hash */ function recover( bytes32 hash, bytes signatureWithType ) internal pure returns (address) { require( signatureWithType.length == 66, "SignatureValidator#validateSignature: invalid signature length" ); uint8 sigType = uint8(signatureWithType[0]); require( sigType > uint8(SIGTYPE_INVALID), "SignatureValidator#validateSignature: invalid signature type" ); require( sigType < uint8(SIGTYPE_UNSUPPORTED), "SignatureValidator#validateSignature: unsupported signature type" ); uint8 v = uint8(signatureWithType[1]); bytes32 r; bytes32 s; /* solium-disable-next-line security/no-inline-assembly */ assembly { r := mload(add(signatureWithType, 34)) s := mload(add(signatureWithType, 66)) } bytes32 signedHash; if (sigType == SIGTYPE_ECRECOVER_DEC) { signedHash = keccak256(abi.encodePacked(PREPEND_DEC, hash)); } else { assert(sigType == SIGTYPE_ECRECOVER_HEX); signedHash = keccak256(abi.encodePacked(PREPEND_HEX, hash)); } return ecrecover( signedHash, v, r, s ); } } // File: contracts/margin/interfaces/LoanOfferingVerifier.sol /** * @title LoanOfferingVerifier * @author dYdX * * Interface that smart contracts must implement to be able to make off-chain generated * loan offerings. * * NOTE: Any contract implementing this interface should also use OnlyMargin to control access * to these functions */ interface LoanOfferingVerifier { /** * Function a smart contract must implement to be able to consent to a loan. The loan offering * will be generated off-chain. The "loan owner" address will own the loan-side of the resulting * position. * * If true is returned, and no errors are thrown by the Margin contract, the loan will have * occurred. This means that verifyLoanOffering can also be used to update internal contract * state on a loan. * * @param addresses Array of addresses: * * [0] = owedToken * [1] = heldToken * [2] = loan payer * [3] = loan owner * [4] = loan taker * [5] = loan positionOwner * [6] = loan fee recipient * [7] = loan lender fee token * [8] = loan taker fee token * * @param values256 Values corresponding to: * * [0] = loan maximum amount * [1] = loan minimum amount * [2] = loan minimum heldToken * [3] = loan lender fee * [4] = loan taker fee * [5] = loan expiration timestamp (in seconds) * [6] = loan salt * * @param values32 Values corresponding to: * * [0] = loan call time limit (in seconds) * [1] = loan maxDuration (in seconds) * [2] = loan interest rate (annual nominal percentage times 10**6) * [3] = loan interest update period (in seconds) * * @param positionId Unique ID of the position * @param signature Arbitrary bytes; may or may not be an ECDSA signature * @return This address to accept, a different address to ask that contract */ function verifyLoanOffering( address[9] addresses, uint256[7] values256, uint32[4] values32, bytes32 positionId, bytes signature ) external /* onlyMargin */ returns (address); } // File: contracts/margin/impl/BorrowShared.sol /** * @title BorrowShared * @author dYdX * * This library contains shared functionality between OpenPositionImpl and IncreasePositionImpl. * Both use a Loan Offering and a DEX Order to open or increase a position. */ library BorrowShared { using SafeMath for uint256; // ============ Structs ============ struct Tx { bytes32 positionId; address owner; uint256 principal; uint256 lenderAmount; MarginCommon.LoanOffering loanOffering; address exchangeWrapper; bool depositInHeldToken; uint256 depositAmount; uint256 collateralAmount; uint256 heldTokenFromSell; } // ============ Internal Implementation Functions ============ /** * Validate the transaction before exchanging heldToken for owedToken */ function validateTxPreSell( MarginState.State storage state, Tx memory transaction ) internal { assert(transaction.lenderAmount >= transaction.principal); require( transaction.principal > 0, "BorrowShared#validateTxPreSell: Positions with 0 principal are not allowed" ); // If the taker is 0x0 then any address can take it. Otherwise only the taker can use it. if (transaction.loanOffering.taker != address(0)) { require( msg.sender == transaction.loanOffering.taker, "BorrowShared#validateTxPreSell: Invalid loan offering taker" ); } // If the positionOwner is 0x0 then any address can be set as the position owner. // Otherwise only the specified positionOwner can be set as the position owner. if (transaction.loanOffering.positionOwner != address(0)) { require( transaction.owner == transaction.loanOffering.positionOwner, "BorrowShared#validateTxPreSell: Invalid position owner" ); } // Require the loan offering to be approved by the payer if (AddressUtils.isContract(transaction.loanOffering.payer)) { getConsentFromSmartContractLender(transaction); } else { require( transaction.loanOffering.payer == TypedSignature.recover( transaction.loanOffering.loanHash, transaction.loanOffering.signature ), "BorrowShared#validateTxPreSell: Invalid loan offering signature" ); } // Validate the amount is <= than max and >= min uint256 unavailable = MarginCommon.getUnavailableLoanOfferingAmountImpl( state, transaction.loanOffering.loanHash ); require( transaction.lenderAmount.add(unavailable) <= transaction.loanOffering.rates.maxAmount, "BorrowShared#validateTxPreSell: Loan offering does not have enough available" ); require( transaction.lenderAmount >= transaction.loanOffering.rates.minAmount, "BorrowShared#validateTxPreSell: Lender amount is below loan offering minimum amount" ); require( transaction.loanOffering.owedToken != transaction.loanOffering.heldToken, "BorrowShared#validateTxPreSell: owedToken cannot be equal to heldToken" ); require( transaction.owner != address(0), "BorrowShared#validateTxPreSell: Position owner cannot be 0" ); require( transaction.loanOffering.owner != address(0), "BorrowShared#validateTxPreSell: Loan owner cannot be 0" ); require( transaction.loanOffering.expirationTimestamp > block.timestamp, "BorrowShared#validateTxPreSell: Loan offering is expired" ); require( transaction.loanOffering.maxDuration > 0, "BorrowShared#validateTxPreSell: Loan offering has 0 maximum duration" ); require( transaction.loanOffering.rates.interestPeriod <= transaction.loanOffering.maxDuration, "BorrowShared#validateTxPreSell: Loan offering interestPeriod > maxDuration" ); // The minimum heldToken is validated after executing the sell // Position and loan ownership is validated in TransferInternal } /** * Validate the transaction after exchanging heldToken for owedToken, pay out fees, and store * how much of the loan was used. */ function doPostSell( MarginState.State storage state, Tx memory transaction ) internal { validateTxPostSell(transaction); // Transfer feeTokens from trader and lender transferLoanFees(state, transaction); // Update global amounts for the loan state.loanFills[transaction.loanOffering.loanHash] = state.loanFills[transaction.loanOffering.loanHash].add(transaction.lenderAmount); } /** * Sells the owedToken from the lender (and from the deposit if in owedToken) using the * exchangeWrapper, then puts the resulting heldToken into the vault. Only trades for * maxHeldTokenToBuy of heldTokens at most. */ function doSell( MarginState.State storage state, Tx transaction, bytes orderData, uint256 maxHeldTokenToBuy ) internal returns (uint256) { // Move owedTokens from lender to exchange wrapper pullOwedTokensFromLender(state, transaction); // Sell just the lender's owedToken (if trader deposit is in heldToken) // Otherwise sell both the lender's owedToken and the trader's deposit in owedToken uint256 sellAmount = transaction.depositInHeldToken ? transaction.lenderAmount : transaction.lenderAmount.add(transaction.depositAmount); // Do the trade, taking only the maxHeldTokenToBuy if more is returned uint256 heldTokenFromSell = Math.min256( maxHeldTokenToBuy, ExchangeWrapper(transaction.exchangeWrapper).exchange( msg.sender, state.TOKEN_PROXY, transaction.loanOffering.heldToken, transaction.loanOffering.owedToken, sellAmount, orderData ) ); // Move the tokens to the vault Vault(state.VAULT).transferToVault( transaction.positionId, transaction.loanOffering.heldToken, transaction.exchangeWrapper, heldTokenFromSell ); // Update collateral amount transaction.collateralAmount = transaction.collateralAmount.add(heldTokenFromSell); return heldTokenFromSell; } /** * Take the owedToken deposit from the trader and give it to the exchange wrapper so that it can * be sold for heldToken. */ function doDepositOwedToken( MarginState.State storage state, Tx transaction ) internal { TokenProxy(state.TOKEN_PROXY).transferTokens( transaction.loanOffering.owedToken, msg.sender, transaction.exchangeWrapper, transaction.depositAmount ); } /** * Take the heldToken deposit from the trader and move it to the vault. */ function doDepositHeldToken( MarginState.State storage state, Tx transaction ) internal { Vault(state.VAULT).transferToVault( transaction.positionId, transaction.loanOffering.heldToken, msg.sender, transaction.depositAmount ); // Update collateral amount transaction.collateralAmount = transaction.collateralAmount.add(transaction.depositAmount); } // ============ Private Helper-Functions ============ function validateTxPostSell( Tx transaction ) private pure { uint256 expectedCollateral = transaction.depositInHeldToken ? transaction.heldTokenFromSell.add(transaction.depositAmount) : transaction.heldTokenFromSell; assert(transaction.collateralAmount == expectedCollateral); uint256 loanOfferingMinimumHeldToken = MathHelpers.getPartialAmountRoundedUp( transaction.lenderAmount, transaction.loanOffering.rates.maxAmount, transaction.loanOffering.rates.minHeldToken ); require( transaction.collateralAmount >= loanOfferingMinimumHeldToken, "BorrowShared#validateTxPostSell: Loan offering minimum held token not met" ); } function getConsentFromSmartContractLender( Tx transaction ) private { verifyLoanOfferingRecurse( transaction.loanOffering.payer, getLoanOfferingAddresses(transaction), getLoanOfferingValues256(transaction), getLoanOfferingValues32(transaction), transaction.positionId, transaction.loanOffering.signature ); } function verifyLoanOfferingRecurse( address contractAddr, address[9] addresses, uint256[7] values256, uint32[4] values32, bytes32 positionId, bytes signature ) private { address newContractAddr = LoanOfferingVerifier(contractAddr).verifyLoanOffering( addresses, values256, values32, positionId, signature ); if (newContractAddr != contractAddr) { verifyLoanOfferingRecurse( newContractAddr, addresses, values256, values32, positionId, signature ); } } function pullOwedTokensFromLender( MarginState.State storage state, Tx transaction ) private { // Transfer owedToken to the exchange wrapper TokenProxy(state.TOKEN_PROXY).transferTokens( transaction.loanOffering.owedToken, transaction.loanOffering.payer, transaction.exchangeWrapper, transaction.lenderAmount ); } function transferLoanFees( MarginState.State storage state, Tx transaction ) private { // 0 fee address indicates no fees if (transaction.loanOffering.feeRecipient == address(0)) { return; } TokenProxy proxy = TokenProxy(state.TOKEN_PROXY); uint256 lenderFee = MathHelpers.getPartialAmount( transaction.lenderAmount, transaction.loanOffering.rates.maxAmount, transaction.loanOffering.rates.lenderFee ); uint256 takerFee = MathHelpers.getPartialAmount( transaction.lenderAmount, transaction.loanOffering.rates.maxAmount, transaction.loanOffering.rates.takerFee ); if (lenderFee > 0) { proxy.transferTokens( transaction.loanOffering.lenderFeeToken, transaction.loanOffering.payer, transaction.loanOffering.feeRecipient, lenderFee ); } if (takerFee > 0) { proxy.transferTokens( transaction.loanOffering.takerFeeToken, msg.sender, transaction.loanOffering.feeRecipient, takerFee ); } } function getLoanOfferingAddresses( Tx transaction ) private pure returns (address[9]) { return [ transaction.loanOffering.owedToken, transaction.loanOffering.heldToken, transaction.loanOffering.payer, transaction.loanOffering.owner, transaction.loanOffering.taker, transaction.loanOffering.positionOwner, transaction.loanOffering.feeRecipient, transaction.loanOffering.lenderFeeToken, transaction.loanOffering.takerFeeToken ]; } function getLoanOfferingValues256( Tx transaction ) private pure returns (uint256[7]) { return [ transaction.loanOffering.rates.maxAmount, transaction.loanOffering.rates.minAmount, transaction.loanOffering.rates.minHeldToken, transaction.loanOffering.rates.lenderFee, transaction.loanOffering.rates.takerFee, transaction.loanOffering.expirationTimestamp, transaction.loanOffering.salt ]; } function getLoanOfferingValues32( Tx transaction ) private pure returns (uint32[4]) { return [ transaction.loanOffering.callTimeLimit, transaction.loanOffering.maxDuration, transaction.loanOffering.rates.interestRate, transaction.loanOffering.rates.interestPeriod ]; } } // File: contracts/margin/interfaces/lender/IncreaseLoanDelegator.sol /** * @title IncreaseLoanDelegator * @author dYdX * * Interface that smart contracts must implement in order to own loans on behalf of other accounts. * * NOTE: Any contract implementing this interface should also use OnlyMargin to control access * to these functions */ interface IncreaseLoanDelegator { // ============ Public Interface functions ============ /** * Function a contract must implement in order to allow additional value to be added onto * an owned loan. Margin will call this on the owner of a loan during increasePosition(). * * NOTE: If not returning zero (or not reverting), this contract must assume that Margin will * either revert the entire transaction or that the loan size was successfully increased. * * @param payer Lender adding additional funds to the position * @param positionId Unique ID of the position * @param principalAdded Principal amount to be added to the position * @param lentAmount Amount of owedToken lent by the lender (principal plus interest, or * zero if increaseWithoutCounterparty() is used). * @return This address to accept, a different address to ask that contract */ function increaseLoanOnBehalfOf( address payer, bytes32 positionId, uint256 principalAdded, uint256 lentAmount ) external /* onlyMargin */ returns (address); } // File: contracts/margin/interfaces/owner/IncreasePositionDelegator.sol /** * @title IncreasePositionDelegator * @author dYdX * * Interface that smart contracts must implement in order to own position on behalf of other * accounts * * NOTE: Any contract implementing this interface should also use OnlyMargin to control access * to these functions */ interface IncreasePositionDelegator { // ============ Public Interface functions ============ /** * Function a contract must implement in order to allow additional value to be added onto * an owned position. Margin will call this on the owner of a position during increasePosition() * * NOTE: If not returning zero (or not reverting), this contract must assume that Margin will * either revert the entire transaction or that the position size was successfully increased. * * @param trader Address initiating the addition of funds to the position * @param positionId Unique ID of the position * @param principalAdded Amount of principal to be added to the position * @return This address to accept, a different address to ask that contract */ function increasePositionOnBehalfOf( address trader, bytes32 positionId, uint256 principalAdded ) external /* onlyMargin */ returns (address); } // File: contracts/margin/impl/IncreasePositionImpl.sol /** * @title IncreasePositionImpl * @author dYdX * * This library contains the implementation for the increasePosition function of Margin */ library IncreasePositionImpl { using SafeMath for uint256; // ============ Events ============ /* * A position was increased */ event PositionIncreased( bytes32 indexed positionId, address indexed trader, address indexed lender, address positionOwner, address loanOwner, bytes32 loanHash, address loanFeeRecipient, uint256 amountBorrowed, uint256 principalAdded, uint256 heldTokenFromSell, uint256 depositAmount, bool depositInHeldToken ); // ============ Public Implementation Functions ============ function increasePositionImpl( MarginState.State storage state, bytes32 positionId, address[7] addresses, uint256[8] values256, uint32[2] values32, bool depositInHeldToken, bytes signature, bytes orderData ) public returns (uint256) { // Also ensures that the position exists MarginCommon.Position storage position = MarginCommon.getPositionFromStorage(state, positionId); BorrowShared.Tx memory transaction = parseIncreasePositionTx( position, positionId, addresses, values256, values32, depositInHeldToken, signature ); validateIncrease(state, transaction, position); doBorrowAndSell(state, transaction, orderData); updateState( position, transaction.positionId, transaction.principal, transaction.lenderAmount, transaction.loanOffering.payer ); // LOG EVENT recordPositionIncreased(transaction, position); return transaction.lenderAmount; } function increaseWithoutCounterpartyImpl( MarginState.State storage state, bytes32 positionId, uint256 principalToAdd ) public returns (uint256) { MarginCommon.Position storage position = MarginCommon.getPositionFromStorage(state, positionId); // Disallow adding 0 principal require( principalToAdd > 0, "IncreasePositionImpl#increaseWithoutCounterpartyImpl: Cannot add 0 principal" ); // Disallow additions after maximum duration require( block.timestamp < uint256(position.startTimestamp).add(position.maxDuration), "IncreasePositionImpl#increaseWithoutCounterpartyImpl: Cannot increase after maxDuration" ); uint256 heldTokenAmount = getCollateralNeededForAddedPrincipal( state, position, positionId, principalToAdd ); Vault(state.VAULT).transferToVault( positionId, position.heldToken, msg.sender, heldTokenAmount ); updateState( position, positionId, principalToAdd, 0, // lent amount msg.sender ); emit PositionIncreased( positionId, msg.sender, msg.sender, position.owner, position.lender, "", address(0), 0, principalToAdd, 0, heldTokenAmount, true ); return heldTokenAmount; } // ============ Private Helper-Functions ============ function doBorrowAndSell( MarginState.State storage state, BorrowShared.Tx memory transaction, bytes orderData ) private { // Calculate the number of heldTokens to add uint256 collateralToAdd = getCollateralNeededForAddedPrincipal( state, state.positions[transaction.positionId], transaction.positionId, transaction.principal ); // Do pre-exchange validations BorrowShared.validateTxPreSell(state, transaction); // Calculate and deposit owedToken uint256 maxHeldTokenFromSell = MathHelpers.maxUint256(); if (!transaction.depositInHeldToken) { transaction.depositAmount = getOwedTokenDeposit(transaction, collateralToAdd, orderData); BorrowShared.doDepositOwedToken(state, transaction); maxHeldTokenFromSell = collateralToAdd; } // Sell owedToken for heldToken using the exchange wrapper transaction.heldTokenFromSell = BorrowShared.doSell( state, transaction, orderData, maxHeldTokenFromSell ); // Calculate and deposit heldToken if (transaction.depositInHeldToken) { require( transaction.heldTokenFromSell <= collateralToAdd, "IncreasePositionImpl#doBorrowAndSell: DEX order gives too much heldToken" ); transaction.depositAmount = collateralToAdd.sub(transaction.heldTokenFromSell); BorrowShared.doDepositHeldToken(state, transaction); } // Make sure the actual added collateral is what is expected assert(transaction.collateralAmount == collateralToAdd); // Do post-exchange validations BorrowShared.doPostSell(state, transaction); } function getOwedTokenDeposit( BorrowShared.Tx transaction, uint256 collateralToAdd, bytes orderData ) private view returns (uint256) { uint256 totalOwedToken = ExchangeWrapper(transaction.exchangeWrapper).getExchangeCost( transaction.loanOffering.heldToken, transaction.loanOffering.owedToken, collateralToAdd, orderData ); require( transaction.lenderAmount <= totalOwedToken, "IncreasePositionImpl#getOwedTokenDeposit: Lender amount is more than required" ); return totalOwedToken.sub(transaction.lenderAmount); } function validateIncrease( MarginState.State storage state, BorrowShared.Tx transaction, MarginCommon.Position storage position ) private view { assert(MarginCommon.containsPositionImpl(state, transaction.positionId)); require( position.callTimeLimit <= transaction.loanOffering.callTimeLimit, "IncreasePositionImpl#validateIncrease: Loan callTimeLimit is less than the position" ); // require the position to end no later than the loanOffering's maximum acceptable end time uint256 positionEndTimestamp = uint256(position.startTimestamp).add(position.maxDuration); uint256 offeringEndTimestamp = block.timestamp.add(transaction.loanOffering.maxDuration); require( positionEndTimestamp <= offeringEndTimestamp, "IncreasePositionImpl#validateIncrease: Loan end timestamp is less than the position" ); require( block.timestamp < positionEndTimestamp, "IncreasePositionImpl#validateIncrease: Position has passed its maximum duration" ); } function getCollateralNeededForAddedPrincipal( MarginState.State storage state, MarginCommon.Position storage position, bytes32 positionId, uint256 principalToAdd ) private view returns (uint256) { uint256 heldTokenBalance = MarginCommon.getPositionBalanceImpl(state, positionId); return MathHelpers.getPartialAmountRoundedUp( principalToAdd, position.principal, heldTokenBalance ); } function updateState( MarginCommon.Position storage position, bytes32 positionId, uint256 principalAdded, uint256 owedTokenLent, address loanPayer ) private { position.principal = position.principal.add(principalAdded); address owner = position.owner; address lender = position.lender; // Ensure owner consent increasePositionOnBehalfOfRecurse( owner, msg.sender, positionId, principalAdded ); // Ensure lender consent increaseLoanOnBehalfOfRecurse( lender, loanPayer, positionId, principalAdded, owedTokenLent ); } function increasePositionOnBehalfOfRecurse( address contractAddr, address trader, bytes32 positionId, uint256 principalAdded ) private { // Assume owner approval if not a smart contract and they increased their own position if (trader == contractAddr && !AddressUtils.isContract(contractAddr)) { return; } address newContractAddr = IncreasePositionDelegator(contractAddr).increasePositionOnBehalfOf( trader, positionId, principalAdded ); if (newContractAddr != contractAddr) { increasePositionOnBehalfOfRecurse( newContractAddr, trader, positionId, principalAdded ); } } function increaseLoanOnBehalfOfRecurse( address contractAddr, address payer, bytes32 positionId, uint256 principalAdded, uint256 amountLent ) private { // Assume lender approval if not a smart contract and they increased their own loan if (payer == contractAddr && !AddressUtils.isContract(contractAddr)) { return; } address newContractAddr = IncreaseLoanDelegator(contractAddr).increaseLoanOnBehalfOf( payer, positionId, principalAdded, amountLent ); if (newContractAddr != contractAddr) { increaseLoanOnBehalfOfRecurse( newContractAddr, payer, positionId, principalAdded, amountLent ); } } function recordPositionIncreased( BorrowShared.Tx transaction, MarginCommon.Position storage position ) private { emit PositionIncreased( transaction.positionId, msg.sender, transaction.loanOffering.payer, position.owner, position.lender, transaction.loanOffering.loanHash, transaction.loanOffering.feeRecipient, transaction.lenderAmount, transaction.principal, transaction.heldTokenFromSell, transaction.depositAmount, transaction.depositInHeldToken ); } // ============ Parsing Functions ============ function parseIncreasePositionTx( MarginCommon.Position storage position, bytes32 positionId, address[7] addresses, uint256[8] values256, uint32[2] values32, bool depositInHeldToken, bytes signature ) private view returns (BorrowShared.Tx memory) { uint256 principal = values256[7]; uint256 lenderAmount = MarginCommon.calculateLenderAmountForIncreasePosition( position, principal, block.timestamp ); assert(lenderAmount >= principal); BorrowShared.Tx memory transaction = BorrowShared.Tx({ positionId: positionId, owner: position.owner, principal: principal, lenderAmount: lenderAmount, loanOffering: parseLoanOfferingFromIncreasePositionTx( position, addresses, values256, values32, signature ), exchangeWrapper: addresses[6], depositInHeldToken: depositInHeldToken, depositAmount: 0, // set later collateralAmount: 0, // set later heldTokenFromSell: 0 // set later }); return transaction; } function parseLoanOfferingFromIncreasePositionTx( MarginCommon.Position storage position, address[7] addresses, uint256[8] values256, uint32[2] values32, bytes signature ) private view returns (MarginCommon.LoanOffering memory) { MarginCommon.LoanOffering memory loanOffering = MarginCommon.LoanOffering({ owedToken: position.owedToken, heldToken: position.heldToken, payer: addresses[0], owner: position.lender, taker: addresses[1], positionOwner: addresses[2], feeRecipient: addresses[3], lenderFeeToken: addresses[4], takerFeeToken: addresses[5], rates: parseLoanOfferingRatesFromIncreasePositionTx(position, values256), expirationTimestamp: values256[5], callTimeLimit: values32[0], maxDuration: values32[1], salt: values256[6], loanHash: 0, signature: signature }); loanOffering.loanHash = MarginCommon.getLoanOfferingHash(loanOffering); return loanOffering; } function parseLoanOfferingRatesFromIncreasePositionTx( MarginCommon.Position storage position, uint256[8] values256 ) private view returns (MarginCommon.LoanRates memory) { MarginCommon.LoanRates memory rates = MarginCommon.LoanRates({ maxAmount: values256[0], minAmount: values256[1], minHeldToken: values256[2], lenderFee: values256[3], takerFee: values256[4], interestRate: position.interestRate, interestPeriod: position.interestPeriod }); return rates; } } // File: contracts/margin/impl/MarginStorage.sol /** * @title MarginStorage * @author dYdX * * This contract serves as the storage for the entire state of MarginStorage */ contract MarginStorage { MarginState.State state; } // File: contracts/margin/impl/LoanGetters.sol /** * @title LoanGetters * @author dYdX * * A collection of public constant getter functions that allows reading of the state of any loan * offering stored in the dYdX protocol. */ contract LoanGetters is MarginStorage { // ============ Public Constant Functions ============ /** * Gets the principal amount of a loan offering that is no longer available. * * @param loanHash Unique hash of the loan offering * @return The total unavailable amount of the loan offering, which is equal to the * filled amount plus the canceled amount. */ function getLoanUnavailableAmount( bytes32 loanHash ) external view returns (uint256) { return MarginCommon.getUnavailableLoanOfferingAmountImpl(state, loanHash); } /** * Gets the total amount of owed token lent for a loan. * * @param loanHash Unique hash of the loan offering * @return The total filled amount of the loan offering. */ function getLoanFilledAmount( bytes32 loanHash ) external view returns (uint256) { return state.loanFills[loanHash]; } /** * Gets the amount of a loan offering that has been canceled. * * @param loanHash Unique hash of the loan offering * @return The total canceled amount of the loan offering. */ function getLoanCanceledAmount( bytes32 loanHash ) external view returns (uint256) { return state.loanCancels[loanHash]; } } // File: contracts/margin/interfaces/lender/CancelMarginCallDelegator.sol /** * @title CancelMarginCallDelegator * @author dYdX * * Interface that smart contracts must implement in order to let other addresses cancel a * margin-call for a loan owned by the smart contract. * * NOTE: Any contract implementing this interface should also use OnlyMargin to control access * to these functions */ interface CancelMarginCallDelegator { // ============ Public Interface functions ============ /** * Function a contract must implement in order to let other addresses call cancelMarginCall(). * * NOTE: If not returning zero (or not reverting), this contract must assume that Margin will * either revert the entire transaction or that the margin-call was successfully canceled. * * @param canceler Address of the caller of the cancelMarginCall function * @param positionId Unique ID of the position * @return This address to accept, a different address to ask that contract */ function cancelMarginCallOnBehalfOf( address canceler, bytes32 positionId ) external /* onlyMargin */ returns (address); } // File: contracts/margin/interfaces/lender/MarginCallDelegator.sol /** * @title MarginCallDelegator * @author dYdX * * Interface that smart contracts must implement in order to let other addresses margin-call a loan * owned by the smart contract. * * NOTE: Any contract implementing this interface should also use OnlyMargin to control access * to these functions */ interface MarginCallDelegator { // ============ Public Interface functions ============ /** * Function a contract must implement in order to let other addresses call marginCall(). * * NOTE: If not returning zero (or not reverting), this contract must assume that Margin will * either revert the entire transaction or that the loan was successfully margin-called. * * @param caller Address of the caller of the marginCall function * @param positionId Unique ID of the position * @param depositAmount Amount of heldToken deposit that will be required to cancel the call * @return This address to accept, a different address to ask that contract */ function marginCallOnBehalfOf( address caller, bytes32 positionId, uint256 depositAmount ) external /* onlyMargin */ returns (address); } // File: contracts/margin/impl/LoanImpl.sol /** * @title LoanImpl * @author dYdX * * This library contains the implementation for the following functions of Margin: * * - marginCall * - cancelMarginCallImpl * - cancelLoanOffering */ library LoanImpl { using SafeMath for uint256; // ============ Events ============ /** * A position was margin-called */ event MarginCallInitiated( bytes32 indexed positionId, address indexed lender, address indexed owner, uint256 requiredDeposit ); /** * A margin call was canceled */ event MarginCallCanceled( bytes32 indexed positionId, address indexed lender, address indexed owner, uint256 depositAmount ); /** * A loan offering was canceled before it was used. Any amount less than the * total for the loan offering can be canceled. */ event LoanOfferingCanceled( bytes32 indexed loanHash, address indexed payer, address indexed feeRecipient, uint256 cancelAmount ); // ============ Public Implementation Functions ============ function marginCallImpl( MarginState.State storage state, bytes32 positionId, uint256 requiredDeposit ) public { MarginCommon.Position storage position = MarginCommon.getPositionFromStorage(state, positionId); require( position.callTimestamp == 0, "LoanImpl#marginCallImpl: The position has already been margin-called" ); // Ensure lender consent marginCallOnBehalfOfRecurse( position.lender, msg.sender, positionId, requiredDeposit ); position.callTimestamp = TimestampHelper.getBlockTimestamp32(); position.requiredDeposit = requiredDeposit; emit MarginCallInitiated( positionId, position.lender, position.owner, requiredDeposit ); } function cancelMarginCallImpl( MarginState.State storage state, bytes32 positionId ) public { MarginCommon.Position storage position = MarginCommon.getPositionFromStorage(state, positionId); require( position.callTimestamp > 0, "LoanImpl#cancelMarginCallImpl: Position has not been margin-called" ); // Ensure lender consent cancelMarginCallOnBehalfOfRecurse( position.lender, msg.sender, positionId ); state.positions[positionId].callTimestamp = 0; state.positions[positionId].requiredDeposit = 0; emit MarginCallCanceled( positionId, position.lender, position.owner, 0 ); } function cancelLoanOfferingImpl( MarginState.State storage state, address[9] addresses, uint256[7] values256, uint32[4] values32, uint256 cancelAmount ) public returns (uint256) { MarginCommon.LoanOffering memory loanOffering = parseLoanOffering( addresses, values256, values32 ); require( msg.sender == loanOffering.payer, "LoanImpl#cancelLoanOfferingImpl: Only loan offering payer can cancel" ); require( loanOffering.expirationTimestamp > block.timestamp, "LoanImpl#cancelLoanOfferingImpl: Loan offering has already expired" ); uint256 remainingAmount = loanOffering.rates.maxAmount.sub( MarginCommon.getUnavailableLoanOfferingAmountImpl(state, loanOffering.loanHash) ); uint256 amountToCancel = Math.min256(remainingAmount, cancelAmount); // If the loan was already fully canceled, then just return 0 amount was canceled if (amountToCancel == 0) { return 0; } state.loanCancels[loanOffering.loanHash] = state.loanCancels[loanOffering.loanHash].add(amountToCancel); emit LoanOfferingCanceled( loanOffering.loanHash, loanOffering.payer, loanOffering.feeRecipient, amountToCancel ); return amountToCancel; } // ============ Private Helper-Functions ============ function marginCallOnBehalfOfRecurse( address contractAddr, address who, bytes32 positionId, uint256 requiredDeposit ) private { // no need to ask for permission if (who == contractAddr) { return; } address newContractAddr = MarginCallDelegator(contractAddr).marginCallOnBehalfOf( msg.sender, positionId, requiredDeposit ); if (newContractAddr != contractAddr) { marginCallOnBehalfOfRecurse( newContractAddr, who, positionId, requiredDeposit ); } } function cancelMarginCallOnBehalfOfRecurse( address contractAddr, address who, bytes32 positionId ) private { // no need to ask for permission if (who == contractAddr) { return; } address newContractAddr = CancelMarginCallDelegator(contractAddr).cancelMarginCallOnBehalfOf( msg.sender, positionId ); if (newContractAddr != contractAddr) { cancelMarginCallOnBehalfOfRecurse( newContractAddr, who, positionId ); } } // ============ Parsing Functions ============ function parseLoanOffering( address[9] addresses, uint256[7] values256, uint32[4] values32 ) private view returns (MarginCommon.LoanOffering memory) { MarginCommon.LoanOffering memory loanOffering = MarginCommon.LoanOffering({ owedToken: addresses[0], heldToken: addresses[1], payer: addresses[2], owner: addresses[3], taker: addresses[4], positionOwner: addresses[5], feeRecipient: addresses[6], lenderFeeToken: addresses[7], takerFeeToken: addresses[8], rates: parseLoanOfferRates(values256, values32), expirationTimestamp: values256[5], callTimeLimit: values32[0], maxDuration: values32[1], salt: values256[6], loanHash: 0, signature: new bytes(0) }); loanOffering.loanHash = MarginCommon.getLoanOfferingHash(loanOffering); return loanOffering; } function parseLoanOfferRates( uint256[7] values256, uint32[4] values32 ) private pure returns (MarginCommon.LoanRates memory) { MarginCommon.LoanRates memory rates = MarginCommon.LoanRates({ maxAmount: values256[0], minAmount: values256[1], minHeldToken: values256[2], interestRate: values32[2], lenderFee: values256[3], takerFee: values256[4], interestPeriod: values32[3] }); return rates; } } // File: contracts/margin/impl/MarginAdmin.sol /** * @title MarginAdmin * @author dYdX * * Contains admin functions for the Margin contract * The owner can put Margin into various close-only modes, which will disallow new position creation */ contract MarginAdmin is Ownable { // ============ Enums ============ // All functionality enabled uint8 private constant OPERATION_STATE_OPERATIONAL = 0; // Only closing functions + cancelLoanOffering allowed (marginCall, closePosition, // cancelLoanOffering, closePositionDirectly, forceRecoverCollateral) uint8 private constant OPERATION_STATE_CLOSE_AND_CANCEL_LOAN_ONLY = 1; // Only closing functions allowed (marginCall, closePosition, closePositionDirectly, // forceRecoverCollateral) uint8 private constant OPERATION_STATE_CLOSE_ONLY = 2; // Only closing functions allowed (marginCall, closePositionDirectly, forceRecoverCollateral) uint8 private constant OPERATION_STATE_CLOSE_DIRECTLY_ONLY = 3; // This operation state (and any higher) is invalid uint8 private constant OPERATION_STATE_INVALID = 4; // ============ Events ============ /** * Event indicating the operation state has changed */ event OperationStateChanged( uint8 from, uint8 to ); // ============ State Variables ============ uint8 public operationState; // ============ Constructor ============ constructor() public Ownable() { operationState = OPERATION_STATE_OPERATIONAL; } // ============ Modifiers ============ modifier onlyWhileOperational() { require( operationState == OPERATION_STATE_OPERATIONAL, "MarginAdmin#onlyWhileOperational: Can only call while operational" ); _; } modifier cancelLoanOfferingStateControl() { require( operationState == OPERATION_STATE_OPERATIONAL || operationState == OPERATION_STATE_CLOSE_AND_CANCEL_LOAN_ONLY, "MarginAdmin#cancelLoanOfferingStateControl: Invalid operation state" ); _; } modifier closePositionStateControl() { require( operationState == OPERATION_STATE_OPERATIONAL || operationState == OPERATION_STATE_CLOSE_AND_CANCEL_LOAN_ONLY || operationState == OPERATION_STATE_CLOSE_ONLY, "MarginAdmin#closePositionStateControl: Invalid operation state" ); _; } modifier closePositionDirectlyStateControl() { _; } // ============ Owner-Only State-Changing Functions ============ function setOperationState( uint8 newState ) external onlyOwner { require( newState < OPERATION_STATE_INVALID, "MarginAdmin#setOperationState: newState is not a valid operation state" ); if (newState != operationState) { emit OperationStateChanged( operationState, newState ); operationState = newState; } } } // File: contracts/margin/impl/MarginEvents.sol /** * @title MarginEvents * @author dYdX * * Contains events for the Margin contract. * * NOTE: Any Margin function libraries that use events will need to both define the event here * and copy the event into the library itself as libraries don't support sharing events */ contract MarginEvents { // ============ Events ============ /** * A position was opened */ event PositionOpened( bytes32 indexed positionId, address indexed trader, address indexed lender, bytes32 loanHash, address owedToken, address heldToken, address loanFeeRecipient, uint256 principal, uint256 heldTokenFromSell, uint256 depositAmount, uint256 interestRate, uint32 callTimeLimit, uint32 maxDuration, bool depositInHeldToken ); /* * A position was increased */ event PositionIncreased( bytes32 indexed positionId, address indexed trader, address indexed lender, address positionOwner, address loanOwner, bytes32 loanHash, address loanFeeRecipient, uint256 amountBorrowed, uint256 principalAdded, uint256 heldTokenFromSell, uint256 depositAmount, bool depositInHeldToken ); /** * A position was closed or partially closed */ event PositionClosed( bytes32 indexed positionId, address indexed closer, address indexed payoutRecipient, uint256 closeAmount, uint256 remainingAmount, uint256 owedTokenPaidToLender, uint256 payoutAmount, uint256 buybackCostInHeldToken, bool payoutInHeldToken ); /** * Collateral for a position was forcibly recovered */ event CollateralForceRecovered( bytes32 indexed positionId, address indexed recipient, uint256 amount ); /** * A position was margin-called */ event MarginCallInitiated( bytes32 indexed positionId, address indexed lender, address indexed owner, uint256 requiredDeposit ); /** * A margin call was canceled */ event MarginCallCanceled( bytes32 indexed positionId, address indexed lender, address indexed owner, uint256 depositAmount ); /** * A loan offering was canceled before it was used. Any amount less than the * total for the loan offering can be canceled. */ event LoanOfferingCanceled( bytes32 indexed loanHash, address indexed payer, address indexed feeRecipient, uint256 cancelAmount ); /** * Additional collateral for a position was posted by the owner */ event AdditionalCollateralDeposited( bytes32 indexed positionId, uint256 amount, address depositor ); /** * Ownership of a loan was transferred to a new address */ event LoanTransferred( bytes32 indexed positionId, address indexed from, address indexed to ); /** * Ownership of a position was transferred to a new address */ event PositionTransferred( bytes32 indexed positionId, address indexed from, address indexed to ); } // File: contracts/margin/impl/OpenPositionImpl.sol /** * @title OpenPositionImpl * @author dYdX * * This library contains the implementation for the openPosition function of Margin */ library OpenPositionImpl { using SafeMath for uint256; // ============ Events ============ /** * A position was opened */ event PositionOpened( bytes32 indexed positionId, address indexed trader, address indexed lender, bytes32 loanHash, address owedToken, address heldToken, address loanFeeRecipient, uint256 principal, uint256 heldTokenFromSell, uint256 depositAmount, uint256 interestRate, uint32 callTimeLimit, uint32 maxDuration, bool depositInHeldToken ); // ============ Public Implementation Functions ============ function openPositionImpl( MarginState.State storage state, address[11] addresses, uint256[10] values256, uint32[4] values32, bool depositInHeldToken, bytes signature, bytes orderData ) public returns (bytes32) { BorrowShared.Tx memory transaction = parseOpenTx( addresses, values256, values32, depositInHeldToken, signature ); require( !MarginCommon.positionHasExisted(state, transaction.positionId), "OpenPositionImpl#openPositionImpl: positionId already exists" ); doBorrowAndSell(state, transaction, orderData); // Before doStoreNewPosition() so that PositionOpened event is before Transferred events recordPositionOpened( transaction ); doStoreNewPosition( state, transaction ); return transaction.positionId; } // ============ Private Helper-Functions ============ function doBorrowAndSell( MarginState.State storage state, BorrowShared.Tx memory transaction, bytes orderData ) private { BorrowShared.validateTxPreSell(state, transaction); if (transaction.depositInHeldToken) { BorrowShared.doDepositHeldToken(state, transaction); } else { BorrowShared.doDepositOwedToken(state, transaction); } transaction.heldTokenFromSell = BorrowShared.doSell( state, transaction, orderData, MathHelpers.maxUint256() ); BorrowShared.doPostSell(state, transaction); } function doStoreNewPosition( MarginState.State storage state, BorrowShared.Tx memory transaction ) private { MarginCommon.storeNewPosition( state, transaction.positionId, MarginCommon.Position({ owedToken: transaction.loanOffering.owedToken, heldToken: transaction.loanOffering.heldToken, lender: transaction.loanOffering.owner, owner: transaction.owner, principal: transaction.principal, requiredDeposit: 0, callTimeLimit: transaction.loanOffering.callTimeLimit, startTimestamp: 0, callTimestamp: 0, maxDuration: transaction.loanOffering.maxDuration, interestRate: transaction.loanOffering.rates.interestRate, interestPeriod: transaction.loanOffering.rates.interestPeriod }), transaction.loanOffering.payer ); } function recordPositionOpened( BorrowShared.Tx transaction ) private { emit PositionOpened( transaction.positionId, msg.sender, transaction.loanOffering.payer, transaction.loanOffering.loanHash, transaction.loanOffering.owedToken, transaction.loanOffering.heldToken, transaction.loanOffering.feeRecipient, transaction.principal, transaction.heldTokenFromSell, transaction.depositAmount, transaction.loanOffering.rates.interestRate, transaction.loanOffering.callTimeLimit, transaction.loanOffering.maxDuration, transaction.depositInHeldToken ); } // ============ Parsing Functions ============ function parseOpenTx( address[11] addresses, uint256[10] values256, uint32[4] values32, bool depositInHeldToken, bytes signature ) private view returns (BorrowShared.Tx memory) { BorrowShared.Tx memory transaction = BorrowShared.Tx({ positionId: MarginCommon.getPositionIdFromNonce(values256[9]), owner: addresses[0], principal: values256[7], lenderAmount: values256[7], loanOffering: parseLoanOffering( addresses, values256, values32, signature ), exchangeWrapper: addresses[10], depositInHeldToken: depositInHeldToken, depositAmount: values256[8], collateralAmount: 0, // set later heldTokenFromSell: 0 // set later }); return transaction; } function parseLoanOffering( address[11] addresses, uint256[10] values256, uint32[4] values32, bytes signature ) private view returns (MarginCommon.LoanOffering memory) { MarginCommon.LoanOffering memory loanOffering = MarginCommon.LoanOffering({ owedToken: addresses[1], heldToken: addresses[2], payer: addresses[3], owner: addresses[4], taker: addresses[5], positionOwner: addresses[6], feeRecipient: addresses[7], lenderFeeToken: addresses[8], takerFeeToken: addresses[9], rates: parseLoanOfferRates(values256, values32), expirationTimestamp: values256[5], callTimeLimit: values32[0], maxDuration: values32[1], salt: values256[6], loanHash: 0, signature: signature }); loanOffering.loanHash = MarginCommon.getLoanOfferingHash(loanOffering); return loanOffering; } function parseLoanOfferRates( uint256[10] values256, uint32[4] values32 ) private pure returns (MarginCommon.LoanRates memory) { MarginCommon.LoanRates memory rates = MarginCommon.LoanRates({ maxAmount: values256[0], minAmount: values256[1], minHeldToken: values256[2], lenderFee: values256[3], takerFee: values256[4], interestRate: values32[2], interestPeriod: values32[3] }); return rates; } } // File: contracts/margin/impl/OpenWithoutCounterpartyImpl.sol /** * @title OpenWithoutCounterpartyImpl * @author dYdX * * This library contains the implementation for the openWithoutCounterparty * function of Margin */ library OpenWithoutCounterpartyImpl { // ============ Structs ============ struct Tx { bytes32 positionId; address positionOwner; address owedToken; address heldToken; address loanOwner; uint256 principal; uint256 deposit; uint32 callTimeLimit; uint32 maxDuration; uint32 interestRate; uint32 interestPeriod; } // ============ Events ============ /** * A position was opened */ event PositionOpened( bytes32 indexed positionId, address indexed trader, address indexed lender, bytes32 loanHash, address owedToken, address heldToken, address loanFeeRecipient, uint256 principal, uint256 heldTokenFromSell, uint256 depositAmount, uint256 interestRate, uint32 callTimeLimit, uint32 maxDuration, bool depositInHeldToken ); // ============ Public Implementation Functions ============ function openWithoutCounterpartyImpl( MarginState.State storage state, address[4] addresses, uint256[3] values256, uint32[4] values32 ) public returns (bytes32) { Tx memory openTx = parseTx( addresses, values256, values32 ); validate( state, openTx ); Vault(state.VAULT).transferToVault( openTx.positionId, openTx.heldToken, msg.sender, openTx.deposit ); recordPositionOpened( openTx ); doStoreNewPosition( state, openTx ); return openTx.positionId; } // ============ Private Helper-Functions ============ function doStoreNewPosition( MarginState.State storage state, Tx memory openTx ) private { MarginCommon.storeNewPosition( state, openTx.positionId, MarginCommon.Position({ owedToken: openTx.owedToken, heldToken: openTx.heldToken, lender: openTx.loanOwner, owner: openTx.positionOwner, principal: openTx.principal, requiredDeposit: 0, callTimeLimit: openTx.callTimeLimit, startTimestamp: 0, callTimestamp: 0, maxDuration: openTx.maxDuration, interestRate: openTx.interestRate, interestPeriod: openTx.interestPeriod }), msg.sender ); } function validate( MarginState.State storage state, Tx memory openTx ) private view { require( !MarginCommon.positionHasExisted(state, openTx.positionId), "openWithoutCounterpartyImpl#validate: positionId already exists" ); require( openTx.principal > 0, "openWithoutCounterpartyImpl#validate: principal cannot be 0" ); require( openTx.owedToken != address(0), "openWithoutCounterpartyImpl#validate: owedToken cannot be 0" ); require( openTx.owedToken != openTx.heldToken, "openWithoutCounterpartyImpl#validate: owedToken cannot be equal to heldToken" ); require( openTx.positionOwner != address(0), "openWithoutCounterpartyImpl#validate: positionOwner cannot be 0" ); require( openTx.loanOwner != address(0), "openWithoutCounterpartyImpl#validate: loanOwner cannot be 0" ); require( openTx.maxDuration > 0, "openWithoutCounterpartyImpl#validate: maxDuration cannot be 0" ); require( openTx.interestPeriod <= openTx.maxDuration, "openWithoutCounterpartyImpl#validate: interestPeriod must be <= maxDuration" ); } function recordPositionOpened( Tx memory openTx ) private { emit PositionOpened( openTx.positionId, msg.sender, msg.sender, bytes32(0), openTx.owedToken, openTx.heldToken, address(0), openTx.principal, 0, openTx.deposit, openTx.interestRate, openTx.callTimeLimit, openTx.maxDuration, true ); } // ============ Parsing Functions ============ function parseTx( address[4] addresses, uint256[3] values256, uint32[4] values32 ) private view returns (Tx memory) { Tx memory openTx = Tx({ positionId: MarginCommon.getPositionIdFromNonce(values256[2]), positionOwner: addresses[0], owedToken: addresses[1], heldToken: addresses[2], loanOwner: addresses[3], principal: values256[0], deposit: values256[1], callTimeLimit: values32[0], maxDuration: values32[1], interestRate: values32[2], interestPeriod: values32[3] }); return openTx; } } // File: contracts/margin/impl/PositionGetters.sol /** * @title PositionGetters * @author dYdX * * A collection of public constant getter functions that allows reading of the state of any position * stored in the dYdX protocol. */ contract PositionGetters is MarginStorage { using SafeMath for uint256; // ============ Public Constant Functions ============ /** * Gets if a position is currently open. * * @param positionId Unique ID of the position * @return True if the position is exists and is open */ function containsPosition( bytes32 positionId ) external view returns (bool) { return MarginCommon.containsPositionImpl(state, positionId); } /** * Gets if a position is currently margin-called. * * @param positionId Unique ID of the position * @return True if the position is margin-called */ function isPositionCalled( bytes32 positionId ) external view returns (bool) { return (state.positions[positionId].callTimestamp > 0); } /** * Gets if a position was previously open and is now closed. * * @param positionId Unique ID of the position * @return True if the position is now closed */ function isPositionClosed( bytes32 positionId ) external view returns (bool) { return state.closedPositions[positionId]; } /** * Gets the total amount of owedToken ever repaid to the lender for a position. * * @param positionId Unique ID of the position * @return Total amount of owedToken ever repaid */ function getTotalOwedTokenRepaidToLender( bytes32 positionId ) external view returns (uint256) { return state.totalOwedTokenRepaidToLender[positionId]; } /** * Gets the amount of heldToken currently locked up in Vault for a particular position. * * @param positionId Unique ID of the position * @return The amount of heldToken */ function getPositionBalance( bytes32 positionId ) external view returns (uint256) { return MarginCommon.getPositionBalanceImpl(state, positionId); } /** * Gets the time until the interest fee charged for the position will increase. * Returns 1 if the interest fee increases every second. * Returns 0 if the interest fee will never increase again. * * @param positionId Unique ID of the position * @return The number of seconds until the interest fee will increase */ function getTimeUntilInterestIncrease( bytes32 positionId ) external view returns (uint256) { MarginCommon.Position storage position = MarginCommon.getPositionFromStorage(state, positionId); uint256 effectiveTimeElapsed = MarginCommon.calculateEffectiveTimeElapsed( position, block.timestamp ); uint256 absoluteTimeElapsed = block.timestamp.sub(position.startTimestamp); if (absoluteTimeElapsed > effectiveTimeElapsed) { // past maxDuration return 0; } else { // nextStep is the final second at which the calculated interest fee is the same as it // is currently, so add 1 to get the correct value return effectiveTimeElapsed.add(1).sub(absoluteTimeElapsed); } } /** * Gets the amount of owedTokens currently needed to close the position completely, including * interest fees. * * @param positionId Unique ID of the position * @return The number of owedTokens */ function getPositionOwedAmount( bytes32 positionId ) external view returns (uint256) { MarginCommon.Position storage position = MarginCommon.getPositionFromStorage(state, positionId); return MarginCommon.calculateOwedAmount( position, position.principal, block.timestamp ); } /** * Gets the amount of owedTokens needed to close a given principal amount of the position at a * given time, including interest fees. * * @param positionId Unique ID of the position * @param principalToClose Amount of principal being closed * @param timestamp Block timestamp in seconds of close * @return The number of owedTokens owed */ function getPositionOwedAmountAtTime( bytes32 positionId, uint256 principalToClose, uint32 timestamp ) external view returns (uint256) { MarginCommon.Position storage position = MarginCommon.getPositionFromStorage(state, positionId); require( timestamp >= position.startTimestamp, "PositionGetters#getPositionOwedAmountAtTime: Requested time before position started" ); return MarginCommon.calculateOwedAmount( position, principalToClose, timestamp ); } /** * Gets the amount of owedTokens that can be borrowed from a lender to add a given principal * amount to the position at a given time. * * @param positionId Unique ID of the position * @param principalToAdd Amount being added to principal * @param timestamp Block timestamp in seconds of addition * @return The number of owedTokens that will be borrowed */ function getLenderAmountForIncreasePositionAtTime( bytes32 positionId, uint256 principalToAdd, uint32 timestamp ) external view returns (uint256) { MarginCommon.Position storage position = MarginCommon.getPositionFromStorage(state, positionId); require( timestamp >= position.startTimestamp, "PositionGetters#getLenderAmountForIncreasePositionAtTime: timestamp < position start" ); return MarginCommon.calculateLenderAmountForIncreasePosition( position, principalToAdd, timestamp ); } // ============ All Properties ============ /** * Get a Position by id. This does not validate the position exists. If the position does not * exist, all 0's will be returned. * * @param positionId Unique ID of the position * @return Addresses corresponding to: * * [0] = owedToken * [1] = heldToken * [2] = lender * [3] = owner * * Values corresponding to: * * [0] = principal * [1] = requiredDeposit * * Values corresponding to: * * [0] = callTimeLimit * [1] = startTimestamp * [2] = callTimestamp * [3] = maxDuration * [4] = interestRate * [5] = interestPeriod */ function getPosition( bytes32 positionId ) external view returns ( address[4], uint256[2], uint32[6] ) { MarginCommon.Position storage position = state.positions[positionId]; return ( [ position.owedToken, position.heldToken, position.lender, position.owner ], [ position.principal, position.requiredDeposit ], [ position.callTimeLimit, position.startTimestamp, position.callTimestamp, position.maxDuration, position.interestRate, position.interestPeriod ] ); } // ============ Individual Properties ============ function getPositionLender( bytes32 positionId ) external view returns (address) { return state.positions[positionId].lender; } function getPositionOwner( bytes32 positionId ) external view returns (address) { return state.positions[positionId].owner; } function getPositionHeldToken( bytes32 positionId ) external view returns (address) { return state.positions[positionId].heldToken; } function getPositionOwedToken( bytes32 positionId ) external view returns (address) { return state.positions[positionId].owedToken; } function getPositionPrincipal( bytes32 positionId ) external view returns (uint256) { return state.positions[positionId].principal; } function getPositionInterestRate( bytes32 positionId ) external view returns (uint256) { return state.positions[positionId].interestRate; } function getPositionRequiredDeposit( bytes32 positionId ) external view returns (uint256) { return state.positions[positionId].requiredDeposit; } function getPositionStartTimestamp( bytes32 positionId ) external view returns (uint32) { return state.positions[positionId].startTimestamp; } function getPositionCallTimestamp( bytes32 positionId ) external view returns (uint32) { return state.positions[positionId].callTimestamp; } function getPositionCallTimeLimit( bytes32 positionId ) external view returns (uint32) { return state.positions[positionId].callTimeLimit; } function getPositionMaxDuration( bytes32 positionId ) external view returns (uint32) { return state.positions[positionId].maxDuration; } function getPositioninterestPeriod( bytes32 positionId ) external view returns (uint32) { return state.positions[positionId].interestPeriod; } } // File: contracts/margin/impl/TransferImpl.sol /** * @title TransferImpl * @author dYdX * * This library contains the implementation for the transferPosition and transferLoan functions of * Margin */ library TransferImpl { // ============ Public Implementation Functions ============ function transferLoanImpl( MarginState.State storage state, bytes32 positionId, address newLender ) public { require( MarginCommon.containsPositionImpl(state, positionId), "TransferImpl#transferLoanImpl: Position does not exist" ); address originalLender = state.positions[positionId].lender; require( msg.sender == originalLender, "TransferImpl#transferLoanImpl: Only lender can transfer ownership" ); require( newLender != originalLender, "TransferImpl#transferLoanImpl: Cannot transfer ownership to self" ); // Doesn't change the state of positionId; figures out the final owner of loan. // That is, newLender may pass ownership to a different address. address finalLender = TransferInternal.grantLoanOwnership( positionId, originalLender, newLender); require( finalLender != originalLender, "TransferImpl#transferLoanImpl: Cannot ultimately transfer ownership to self" ); // Set state only after resolving the new owner (to reduce the number of storage calls) state.positions[positionId].lender = finalLender; } function transferPositionImpl( MarginState.State storage state, bytes32 positionId, address newOwner ) public { require( MarginCommon.containsPositionImpl(state, positionId), "TransferImpl#transferPositionImpl: Position does not exist" ); address originalOwner = state.positions[positionId].owner; require( msg.sender == originalOwner, "TransferImpl#transferPositionImpl: Only position owner can transfer ownership" ); require( newOwner != originalOwner, "TransferImpl#transferPositionImpl: Cannot transfer ownership to self" ); // Doesn't change the state of positionId; figures out the final owner of position. // That is, newOwner may pass ownership to a different address. address finalOwner = TransferInternal.grantPositionOwnership( positionId, originalOwner, newOwner); require( finalOwner != originalOwner, "TransferImpl#transferPositionImpl: Cannot ultimately transfer ownership to self" ); // Set state only after resolving the new owner (to reduce the number of storage calls) state.positions[positionId].owner = finalOwner; } } // File: contracts/margin/Margin.sol /** * @title Margin * @author dYdX * * This contract is used to facilitate margin trading as per the dYdX protocol */ contract Margin is ReentrancyGuard, MarginStorage, MarginEvents, MarginAdmin, LoanGetters, PositionGetters { using SafeMath for uint256; // ============ Constructor ============ constructor( address vault, address proxy ) public MarginAdmin() { state = MarginState.State({ VAULT: vault, TOKEN_PROXY: proxy }); } // ============ Public State Changing Functions ============ /** * Open a margin position. Called by the margin trader who must provide both a * signed loan offering as well as a DEX Order with which to sell the owedToken. * * @param addresses Addresses corresponding to: * * [0] = position owner * [1] = owedToken * [2] = heldToken * [3] = loan payer * [4] = loan owner * [5] = loan taker * [6] = loan position owner * [7] = loan fee recipient * [8] = loan lender fee token * [9] = loan taker fee token * [10] = exchange wrapper address * * @param values256 Values corresponding to: * * [0] = loan maximum amount * [1] = loan minimum amount * [2] = loan minimum heldToken * [3] = loan lender fee * [4] = loan taker fee * [5] = loan expiration timestamp (in seconds) * [6] = loan salt * [7] = position amount of principal * [8] = deposit amount * [9] = nonce (used to calculate positionId) * * @param values32 Values corresponding to: * * [0] = loan call time limit (in seconds) * [1] = loan maxDuration (in seconds) * [2] = loan interest rate (annual nominal percentage times 10**6) * [3] = loan interest update period (in seconds) * * @param depositInHeldToken True if the trader wishes to pay the margin deposit in heldToken. * False if the margin deposit will be in owedToken * and then sold along with the owedToken borrowed from the lender * @param signature If loan payer is an account, then this must be the tightly-packed * ECDSA V/R/S parameters from signing the loan hash. If loan payer * is a smart contract, these are arbitrary bytes that the contract * will recieve when choosing whether to approve the loan. * @param order Order object to be passed to the exchange wrapper * @return Unique ID for the new position */ function openPosition( address[11] addresses, uint256[10] values256, uint32[4] values32, bool depositInHeldToken, bytes signature, bytes order ) external onlyWhileOperational nonReentrant returns (bytes32) { return OpenPositionImpl.openPositionImpl( state, addresses, values256, values32, depositInHeldToken, signature, order ); } /** * Open a margin position without a counterparty. The caller will serve as both the * lender and the position owner * * @param addresses Addresses corresponding to: * * [0] = position owner * [1] = owedToken * [2] = heldToken * [3] = loan owner * * @param values256 Values corresponding to: * * [0] = principal * [1] = deposit amount * [2] = nonce (used to calculate positionId) * * @param values32 Values corresponding to: * * [0] = call time limit (in seconds) * [1] = maxDuration (in seconds) * [2] = interest rate (annual nominal percentage times 10**6) * [3] = interest update period (in seconds) * * @return Unique ID for the new position */ function openWithoutCounterparty( address[4] addresses, uint256[3] values256, uint32[4] values32 ) external onlyWhileOperational nonReentrant returns (bytes32) { return OpenWithoutCounterpartyImpl.openWithoutCounterpartyImpl( state, addresses, values256, values32 ); } /** * Increase the size of a position. Funds will be borrowed from the loan payer and sold as per * the position. The amount of owedToken borrowed from the lender will be >= the amount of * principal added, as it will incorporate interest already earned by the position so far. * * @param positionId Unique ID of the position * @param addresses Addresses corresponding to: * * [0] = loan payer * [1] = loan taker * [2] = loan position owner * [3] = loan fee recipient * [4] = loan lender fee token * [5] = loan taker fee token * [6] = exchange wrapper address * * @param values256 Values corresponding to: * * [0] = loan maximum amount * [1] = loan minimum amount * [2] = loan minimum heldToken * [3] = loan lender fee * [4] = loan taker fee * [5] = loan expiration timestamp (in seconds) * [6] = loan salt * [7] = amount of principal to add to the position (NOTE: the amount pulled from the lender * will be >= this amount) * * @param values32 Values corresponding to: * * [0] = loan call time limit (in seconds) * [1] = loan maxDuration (in seconds) * * @param depositInHeldToken True if the trader wishes to pay the margin deposit in heldToken. * False if the margin deposit will be pulled in owedToken * and then sold along with the owedToken borrowed from the lender * @param signature If loan payer is an account, then this must be the tightly-packed * ECDSA V/R/S parameters from signing the loan hash. If loan payer * is a smart contract, these are arbitrary bytes that the contract * will recieve when choosing whether to approve the loan. * @param order Order object to be passed to the exchange wrapper * @return Amount of owedTokens pulled from the lender */ function increasePosition( bytes32 positionId, address[7] addresses, uint256[8] values256, uint32[2] values32, bool depositInHeldToken, bytes signature, bytes order ) external onlyWhileOperational nonReentrant returns (uint256) { return IncreasePositionImpl.increasePositionImpl( state, positionId, addresses, values256, values32, depositInHeldToken, signature, order ); } /** * Increase a position directly by putting up heldToken. The caller will serve as both the * lender and the position owner * * @param positionId Unique ID of the position * @param principalToAdd Principal amount to add to the position * @return Amount of heldToken pulled from the msg.sender */ function increaseWithoutCounterparty( bytes32 positionId, uint256 principalToAdd ) external onlyWhileOperational nonReentrant returns (uint256) { return IncreasePositionImpl.increaseWithoutCounterpartyImpl( state, positionId, principalToAdd ); } /** * Close a position. May be called by the owner or with the approval of the owner. May provide * an order and exchangeWrapper to facilitate the closing of the position. The payoutRecipient * is sent the resulting payout. * * @param positionId Unique ID of the position * @param requestedCloseAmount Principal amount of the position to close. The actual amount * closed is also bounded by: * 1) The principal of the position * 2) The amount allowed by the owner if closer != owner * @param payoutRecipient Address of the recipient of tokens paid out from closing * @param exchangeWrapper Address of the exchange wrapper * @param payoutInHeldToken True to pay out the payoutRecipient in heldToken, * False to pay out the payoutRecipient in owedToken * @param order Order object to be passed to the exchange wrapper * @return Values corresponding to: * 1) Principal of position closed * 2) Amount of tokens (heldToken if payoutInHeldtoken is true, * owedToken otherwise) received by the payoutRecipient * 3) Amount of owedToken paid (incl. interest fee) to the lender */ function closePosition( bytes32 positionId, uint256 requestedCloseAmount, address payoutRecipient, address exchangeWrapper, bool payoutInHeldToken, bytes order ) external closePositionStateControl nonReentrant returns (uint256, uint256, uint256) { return ClosePositionImpl.closePositionImpl( state, positionId, requestedCloseAmount, payoutRecipient, exchangeWrapper, payoutInHeldToken, order ); } /** * Helper to close a position by paying owedToken directly rather than using an exchangeWrapper. * * @param positionId Unique ID of the position * @param requestedCloseAmount Principal amount of the position to close. The actual amount * closed is also bounded by: * 1) The principal of the position * 2) The amount allowed by the owner if closer != owner * @param payoutRecipient Address of the recipient of tokens paid out from closing * @return Values corresponding to: * 1) Principal amount of position closed * 2) Amount of heldToken received by the payoutRecipient * 3) Amount of owedToken paid (incl. interest fee) to the lender */ function closePositionDirectly( bytes32 positionId, uint256 requestedCloseAmount, address payoutRecipient ) external closePositionDirectlyStateControl nonReentrant returns (uint256, uint256, uint256) { return ClosePositionImpl.closePositionImpl( state, positionId, requestedCloseAmount, payoutRecipient, address(0), true, new bytes(0) ); } /** * Reduce the size of a position and withdraw a proportional amount of heldToken from the vault. * Must be approved by both the position owner and lender. * * @param positionId Unique ID of the position * @param requestedCloseAmount Principal amount of the position to close. The actual amount * closed is also bounded by: * 1) The principal of the position * 2) The amount allowed by the owner if closer != owner * 3) The amount allowed by the lender if closer != lender * @return Values corresponding to: * 1) Principal amount of position closed * 2) Amount of heldToken received by the msg.sender */ function closeWithoutCounterparty( bytes32 positionId, uint256 requestedCloseAmount, address payoutRecipient ) external closePositionStateControl nonReentrant returns (uint256, uint256) { return CloseWithoutCounterpartyImpl.closeWithoutCounterpartyImpl( state, positionId, requestedCloseAmount, payoutRecipient ); } /** * Margin-call a position. Only callable with the approval of the position lender. After the * call, the position owner will have time equal to the callTimeLimit of the position to close * the position. If the owner does not close the position, the lender can recover the collateral * in the position. * * @param positionId Unique ID of the position * @param requiredDeposit Amount of deposit the position owner will have to put up to cancel * the margin-call. Passing in 0 means the margin call cannot be * canceled by depositing */ function marginCall( bytes32 positionId, uint256 requiredDeposit ) external nonReentrant { LoanImpl.marginCallImpl( state, positionId, requiredDeposit ); } /** * Cancel a margin-call. Only callable with the approval of the position lender. * * @param positionId Unique ID of the position */ function cancelMarginCall( bytes32 positionId ) external onlyWhileOperational nonReentrant { LoanImpl.cancelMarginCallImpl(state, positionId); } /** * Used to recover the heldTokens held as collateral. Is callable after the maximum duration of * the loan has expired or the loan has been margin-called for the duration of the callTimeLimit * but remains unclosed. Only callable with the approval of the position lender. * * @param positionId Unique ID of the position * @param recipient Address to send the recovered tokens to * @return Amount of heldToken recovered */ function forceRecoverCollateral( bytes32 positionId, address recipient ) external nonReentrant returns (uint256) { return ForceRecoverCollateralImpl.forceRecoverCollateralImpl( state, positionId, recipient ); } /** * Deposit additional heldToken as collateral for a position. Cancels margin-call if: * 0 < position.requiredDeposit < depositAmount. Only callable by the position owner. * * @param positionId Unique ID of the position * @param depositAmount Additional amount in heldToken to deposit */ function depositCollateral( bytes32 positionId, uint256 depositAmount ) external onlyWhileOperational nonReentrant { DepositCollateralImpl.depositCollateralImpl( state, positionId, depositAmount ); } /** * Cancel an amount of a loan offering. Only callable by the loan offering's payer. * * @param addresses Array of addresses: * * [0] = owedToken * [1] = heldToken * [2] = loan payer * [3] = loan owner * [4] = loan taker * [5] = loan position owner * [6] = loan fee recipient * [7] = loan lender fee token * [8] = loan taker fee token * * @param values256 Values corresponding to: * * [0] = loan maximum amount * [1] = loan minimum amount * [2] = loan minimum heldToken * [3] = loan lender fee * [4] = loan taker fee * [5] = loan expiration timestamp (in seconds) * [6] = loan salt * * @param values32 Values corresponding to: * * [0] = loan call time limit (in seconds) * [1] = loan maxDuration (in seconds) * [2] = loan interest rate (annual nominal percentage times 10**6) * [3] = loan interest update period (in seconds) * * @param cancelAmount Amount to cancel * @return Amount that was canceled */ function cancelLoanOffering( address[9] addresses, uint256[7] values256, uint32[4] values32, uint256 cancelAmount ) external cancelLoanOfferingStateControl nonReentrant returns (uint256) { return LoanImpl.cancelLoanOfferingImpl( state, addresses, values256, values32, cancelAmount ); } /** * Transfer ownership of a loan to a new address. This new address will be entitled to all * payouts for this loan. Only callable by the lender for a position. If "who" is a contract, it * must implement the LoanOwner interface. * * @param positionId Unique ID of the position * @param who New owner of the loan */ function transferLoan( bytes32 positionId, address who ) external nonReentrant { TransferImpl.transferLoanImpl( state, positionId, who); } /** * Transfer ownership of a position to a new address. This new address will be entitled to all * payouts. Only callable by the owner of a position. If "who" is a contract, it must implement * the PositionOwner interface. * * @param positionId Unique ID of the position * @param who New owner of the position */ function transferPosition( bytes32 positionId, address who ) external nonReentrant { TransferImpl.transferPositionImpl( state, positionId, who); } // ============ Public Constant Functions ============ /** * Gets the address of the Vault contract that holds and accounts for tokens. * * @return The address of the Vault contract */ function getVaultAddress() external view returns (address) { return state.VAULT; } /** * Gets the address of the TokenProxy contract that accounts must set allowance on in order to * make loans or open/close positions. * * @return The address of the TokenProxy contract */ function getTokenProxyAddress() external view returns (address) { return state.TOKEN_PROXY; } } // File: contracts/margin/interfaces/OnlyMargin.sol /** * @title OnlyMargin * @author dYdX * * Contract to store the address of the main Margin contract and trust only that address to call * certain functions. */ contract OnlyMargin { // ============ Constants ============ // Address of the known and trusted Margin contract on the blockchain address public DYDX_MARGIN; // ============ Constructor ============ constructor( address margin ) public { DYDX_MARGIN = margin; } // ============ Modifiers ============ modifier onlyMargin() { require( msg.sender == DYDX_MARGIN, "OnlyMargin#onlyMargin: Only Margin can call" ); _; } } // File: contracts/margin/external/interfaces/PositionCustodian.sol /** * @title PositionCustodian * @author dYdX * * Interface to interact with other second-layer contracts. For contracts that own positions as a * proxy for other addresses. */ interface PositionCustodian { /** * Function that is intended to be called by external contracts to see where to pay any fees or * tokens as a result of closing a position on behalf of another contract. * * @param positionId Unique ID of the position * @return Address of the true owner of the position */ function getPositionDeedHolder( bytes32 positionId ) external view returns (address); } // File: contracts/margin/external/lib/MarginHelper.sol /** * @title MarginHelper * @author dYdX * * This library contains helper functions for interacting with Margin */ library MarginHelper { function getPosition( address DYDX_MARGIN, bytes32 positionId ) internal view returns (MarginCommon.Position memory) { ( address[4] memory addresses, uint256[2] memory values256, uint32[6] memory values32 ) = Margin(DYDX_MARGIN).getPosition(positionId); return MarginCommon.Position({ owedToken: addresses[0], heldToken: addresses[1], lender: addresses[2], owner: addresses[3], principal: values256[0], requiredDeposit: values256[1], callTimeLimit: values32[0], startTimestamp: values32[1], callTimestamp: values32[2], maxDuration: values32[3], interestRate: values32[4], interestPeriod: values32[5] }); } } // File: contracts/margin/external/ERC20/ERC20Position.sol /** * @title ERC20Position * @author dYdX * * Shared code for ERC20Short and ERC20Long */ contract ERC20Position is ReentrancyGuard, StandardToken, OnlyMargin, PositionOwner, IncreasePositionDelegator, ClosePositionDelegator, PositionCustodian { using SafeMath for uint256; // ============ Enums ============ enum State { UNINITIALIZED, OPEN, CLOSED } // ============ Events ============ /** * This ERC20 was successfully initialized */ event Initialized( bytes32 positionId, uint256 initialSupply ); /** * The position was completely closed by a trusted third-party and tokens can be withdrawn */ event ClosedByTrustedParty( address closer, uint256 tokenAmount, address payoutRecipient ); /** * The position was completely closed and tokens can be withdrawn */ event CompletelyClosed(); /** * A user burned tokens to withdraw heldTokens from this contract after the position was closed */ event Withdraw( address indexed redeemer, uint256 tokensRedeemed, uint256 heldTokenPayout ); /** * A user burned tokens in order to partially close the position */ event Close( address indexed redeemer, uint256 closeAmount ); // ============ State Variables ============ // All tokens will initially be allocated to this address address public INITIAL_TOKEN_HOLDER; // Unique ID of the position this contract is tokenizing bytes32 public POSITION_ID; // Recipients that will fairly verify and redistribute funds from closing the position mapping (address => bool) public TRUSTED_RECIPIENTS; // Withdrawers that will fairly withdraw funds after the position has been closed mapping (address => bool) public TRUSTED_WITHDRAWERS; // Current State of this contract. See State enum State public state; // Address of the position's heldToken. Cached for convenience and lower-cost withdrawals address public heldToken; // Position has been closed using a trusted recipient bool public closedUsingTrustedRecipient; // ============ Modifiers ============ modifier onlyPosition(bytes32 positionId) { require( POSITION_ID == positionId, "ERC20Position#onlyPosition: Incorrect position" ); _; } modifier onlyState(State specificState) { require( state == specificState, "ERC20Position#onlyState: Incorrect State" ); _; } // ============ Constructor ============ constructor( bytes32 positionId, address margin, address initialTokenHolder, address[] trustedRecipients, address[] trustedWithdrawers ) public OnlyMargin(margin) { POSITION_ID = positionId; state = State.UNINITIALIZED; INITIAL_TOKEN_HOLDER = initialTokenHolder; closedUsingTrustedRecipient = false; uint256 i; for (i = 0; i < trustedRecipients.length; i++) { TRUSTED_RECIPIENTS[trustedRecipients[i]] = true; } for (i = 0; i < trustedWithdrawers.length; i++) { TRUSTED_WITHDRAWERS[trustedWithdrawers[i]] = true; } } // ============ Margin-Only Functions ============ /** * Called by Margin when anyone transfers ownership of a position to this contract. * This function initializes the tokenization of the position given and returns this address to * indicate to Margin that it is willing to take ownership of the position. * * param (unused) * @param positionId Unique ID of the position * @return This address on success, throw otherwise */ function receivePositionOwnership( address /* from */, bytes32 positionId ) external onlyMargin nonReentrant onlyState(State.UNINITIALIZED) onlyPosition(positionId) returns (address) { MarginCommon.Position memory position = MarginHelper.getPosition(DYDX_MARGIN, POSITION_ID); assert(position.principal > 0); // set relevant constants state = State.OPEN; heldToken = position.heldToken; uint256 tokenAmount = getTokenAmountOnAdd(position.principal); emit Initialized(POSITION_ID, tokenAmount); mint(INITIAL_TOKEN_HOLDER, tokenAmount); return address(this); // returning own address retains ownership of position } /** * Called by Margin when additional value is added onto the position this contract * owns. Tokens are minted and assigned to the address that added the value. * * @param trader Address that added the value to the position * @param positionId Unique ID of the position * @param principalAdded Amount that was added to the position * @return This address on success, throw otherwise */ function increasePositionOnBehalfOf( address trader, bytes32 positionId, uint256 principalAdded ) external onlyMargin nonReentrant onlyState(State.OPEN) onlyPosition(positionId) returns (address) { require( !Margin(DYDX_MARGIN).isPositionCalled(POSITION_ID), "ERC20Position#increasePositionOnBehalfOf: Position is margin-called" ); require( !closedUsingTrustedRecipient, "ERC20Position#increasePositionOnBehalfOf: Position closed using trusted recipient" ); uint256 tokenAmount = getTokenAmountOnAdd(principalAdded); mint(trader, tokenAmount); return address(this); } /** * Called by Margin when an owner of this token is attempting to close some of the * position. Implementation is required per PositionOwner contract in order to be used by * Margin to approve closing parts of a position. * * @param closer Address of the caller of the close function * @param payoutRecipient Address of the recipient of tokens paid out from closing * @param positionId Unique ID of the position * @param requestedAmount Amount (in principal) of the position being closed * @return 1) This address to accept, a different address to ask that contract * 2) The maximum amount that this contract is allowing */ function closeOnBehalfOf( address closer, address payoutRecipient, bytes32 positionId, uint256 requestedAmount ) external onlyMargin nonReentrant onlyState(State.OPEN) onlyPosition(positionId) returns (address, uint256) { uint256 positionPrincipal = Margin(DYDX_MARGIN).getPositionPrincipal(positionId); assert(requestedAmount <= positionPrincipal); uint256 allowedAmount; if (TRUSTED_RECIPIENTS[payoutRecipient]) { allowedAmount = closeUsingTrustedRecipient( closer, payoutRecipient, requestedAmount ); } else { allowedAmount = close( closer, requestedAmount, positionPrincipal ); } assert(allowedAmount > 0); assert(allowedAmount <= requestedAmount); if (allowedAmount == positionPrincipal) { state = State.CLOSED; emit CompletelyClosed(); } return (address(this), allowedAmount); } // ============ Public State Changing Functions ============ /** * Withdraw heldTokens from this contract for any of the position that was closed via external * means (such as an auction-closing mechanism) * * NOTE: It is possible that this contract could be sent heldToken by external sources * other than from the Margin contract. In this case the payout for token holders * would be greater than just that from the normal payout. This is fine because * nobody has incentive to send this contract extra funds, and if they do then it's * also fine just to let the token holders have it. * * NOTE: If there are significant rounding errors, then it is possible that withdrawing later is * more advantageous. An "attack" could involve withdrawing for others before withdrawing for * yourself. Likely, rounding error will be small enough to not properly incentivize people to * carry out such an attack. * * @param onBehalfOf Address of the account to withdraw for * @return The amount of heldToken withdrawn */ function withdraw( address onBehalfOf ) external nonReentrant returns (uint256) { setStateClosedIfClosed(); require( state == State.CLOSED, "ERC20Position#withdraw: Position has not yet been closed" ); if (msg.sender != onBehalfOf) { require( TRUSTED_WITHDRAWERS[msg.sender], "ERC20Position#withdraw: Only trusted withdrawers can withdraw on behalf of others" ); } return withdrawImpl(msg.sender, onBehalfOf); } // ============ Public Constant Functions ============ /** * ERC20 name function * * @return The name of the Margin Token */ function name() external view returns (string); /** * ERC20 symbol function * * @return The symbol of the Margin Token */ function symbol() external view returns (string); /** * ERC20 decimals function * * @return The number of decimal places */ function decimals() external view returns (uint8); /** * Implements PositionCustodian functionality. Called by external contracts to see where to pay * tokens as a result of closing a position on behalf of this contract * * @param positionId Unique ID of the position * @return Address of this contract. Indicates funds should be sent to this contract */ function getPositionDeedHolder( bytes32 positionId ) external view onlyPosition(positionId) returns (address) { // Claim ownership of deed and allow token holders to withdraw funds from this contract return address(this); } // ============ Internal Helper-Functions ============ /** * Tokens are not burned when a trusted recipient is used, but we require the position to be * completely closed. All token holders are then entitled to the heldTokens in the contract */ function closeUsingTrustedRecipient( address closer, address payoutRecipient, uint256 requestedAmount ) internal returns (uint256) { assert(requestedAmount > 0); // remember that a trusted recipient was used if (!closedUsingTrustedRecipient) { closedUsingTrustedRecipient = true; } emit ClosedByTrustedParty(closer, requestedAmount, payoutRecipient); return requestedAmount; } // ============ Private Helper-Functions ============ function withdrawImpl( address receiver, address onBehalfOf ) private returns (uint256) { uint256 value = balanceOf(onBehalfOf); if (value == 0) { return 0; } uint256 heldTokenBalance = TokenInteract.balanceOf(heldToken, address(this)); // NOTE the payout must be calculated before decrementing the totalSupply below uint256 heldTokenPayout = MathHelpers.getPartialAmount( value, totalSupply_, heldTokenBalance ); // Destroy the margin tokens burn(onBehalfOf, value); emit Withdraw(onBehalfOf, value, heldTokenPayout); // Send the redeemer their proportion of heldToken TokenInteract.transfer(heldToken, receiver, heldTokenPayout); return heldTokenPayout; } function setStateClosedIfClosed( ) private { // If in OPEN state, but the position is closed, set to CLOSED state if (state == State.OPEN && Margin(DYDX_MARGIN).isPositionClosed(POSITION_ID)) { state = State.CLOSED; emit CompletelyClosed(); } } function close( address closer, uint256 requestedAmount, uint256 positionPrincipal ) private returns (uint256) { uint256 balance = balances[closer]; ( uint256 tokenAmount, uint256 allowedCloseAmount ) = getCloseAmounts( requestedAmount, balance, positionPrincipal ); require( tokenAmount > 0 && allowedCloseAmount > 0, "ERC20Position#close: Cannot close 0 amount" ); assert(allowedCloseAmount <= requestedAmount); burn(closer, tokenAmount); emit Close(closer, tokenAmount); return allowedCloseAmount; } function burn( address from, uint256 amount ) private { assert(from != address(0)); totalSupply_ = totalSupply_.sub(amount); balances[from] = balances[from].sub(amount); emit Transfer(from, address(0), amount); } function mint( address to, uint256 amount ) private { assert(to != address(0)); totalSupply_ = totalSupply_.add(amount); balances[to] = balances[to].add(amount); emit Transfer(address(0), to, amount); } // ============ Private Abstract Functions ============ function getTokenAmountOnAdd( uint256 principalAdded ) internal view returns (uint256); function getCloseAmounts( uint256 requestedCloseAmount, uint256 balance, uint256 positionPrincipal ) private view returns ( uint256 /* tokenAmount */, uint256 /* allowedCloseAmount */ ); } // File: contracts/margin/external/ERC20/ERC20CappedPosition.sol /** * @title ERC20CappedPosition * @author dYdX * * ERC20 Position with a limit on the number of tokens that can be minted, and a restriction on * which addreses can close the position after it is force-recoverable. */ contract ERC20CappedPosition is ERC20Position, Ownable { using SafeMath for uint256; // ============ Events ============ event TokenCapSet( uint256 tokenCap ); event TrustedCloserSet( address closer, bool allowed ); // ============ State Variables ============ mapping(address => bool) public TRUSTED_LATE_CLOSERS; uint256 public tokenCap; // ============ Constructor ============ constructor( address[] trustedLateClosers, uint256 cap ) public Ownable() { for (uint256 i = 0; i < trustedLateClosers.length; i++) { TRUSTED_LATE_CLOSERS[trustedLateClosers[i]] = true; } tokenCap = cap; } // ============ Owner-Only Functions ============ function setTokenCap( uint256 newCap ) external onlyOwner { // We do not need to require that the tokenCap is >= totalSupply_ because the cap is only // checked when increasing the position. It does not prevent any other functionality tokenCap = newCap; emit TokenCapSet(newCap); } function setTrustedLateCloser( address closer, bool allowed ) external onlyOwner { TRUSTED_LATE_CLOSERS[closer] = allowed; emit TrustedCloserSet(closer, allowed); } // ============ Internal Overriding Functions ============ // overrides the function in ERC20Position function closeUsingTrustedRecipient( address closer, address payoutRecipient, uint256 requestedAmount ) internal returns (uint256) { MarginCommon.Position memory position = MarginHelper.getPosition(DYDX_MARGIN, POSITION_ID); bool afterEnd = block.timestamp > uint256(position.startTimestamp).add(position.maxDuration); bool afterCall = position.callTimestamp > 0 && block.timestamp > uint256(position.callTimestamp).add(position.callTimeLimit); if (afterCall || afterEnd) { require ( TRUSTED_LATE_CLOSERS[closer], "ERC20CappedPosition#closeUsingTrustedRecipient: closer not in TRUSTED_LATE_CLOSERS" ); } return super.closeUsingTrustedRecipient(closer, payoutRecipient, requestedAmount); } } // File: contracts/lib/StringHelpers.sol /** * @title StringHelpers * @author dYdX * * This library helps with string manipulation in Solidity */ library StringHelpers { /** * Translates a bytes32 to an ascii hexadecimal representation starting with "0x" * * @param input The bytes to convert to hexadecimal * @return A representation of the bytes in ascii hexadecimal */ function bytes32ToHex( bytes32 input ) internal pure returns (bytes) { uint256 number = uint256(input); bytes memory numberAsString = new bytes(66); // "0x" and then 2 chars per byte numberAsString[0] = byte(48); // '0' numberAsString[1] = byte(120); // 'x' for (uint256 n = 0; n < 32; n++) { uint256 nthByte = number / uint256(uint256(2) ** uint256(248 - 8 * n)); // 1 byte to 2 hexadecimal numbers uint8 hex1 = uint8(nthByte) / uint8(16); uint8 hex2 = uint8(nthByte) % uint8(16); // 87 is ascii for '0', 48 is ascii for 'a' hex1 += (hex1 > 9) ? 87 : 48; // shift into proper ascii value hex2 += (hex2 > 9) ? 87 : 48; // shift into proper ascii value numberAsString[2 * n + 2] = byte(hex1); numberAsString[2 * n + 3] = byte(hex2); } return numberAsString; } } // File: contracts/margin/external/ERC20/ERC20Long.sol /** * @title ERC20Long * @author dYdX * * Contract used to tokenize leveraged long positions and allow them to be used as ERC20-compliant * tokens. Holding the tokens allows the holder to close a piece of the position, or be * entitled to some amount of heldTokens after settlement. * * The total supply of leveraged long tokens is always exactly equal to the number of heldTokens * held in collateral in the backing position */ contract ERC20Long is ERC20Position { constructor( bytes32 positionId, address margin, address initialTokenHolder, address[] trustedRecipients, address[] trustedWithdrawers ) public ERC20Position( positionId, margin, initialTokenHolder, trustedRecipients, trustedWithdrawers ) {} // ============ Public Constant Functions ============ function decimals() external view returns (uint8) { return DetailedERC20(heldToken).decimals(); } function symbol() external view returns (string) { if (state == State.UNINITIALIZED) { return "L[UNINITIALIZED]"; } return string( abi.encodePacked( "L", DetailedERC20(heldToken).symbol() ) ); } function name() external view returns (string) { if (state == State.UNINITIALIZED) { return "dYdX Leveraged Long Token [UNINITIALIZED]"; } return string( abi.encodePacked( "dYdX Leveraged Long Token ", StringHelpers.bytes32ToHex(POSITION_ID) ) ); } // ============ Private Functions ============ function getTokenAmountOnAdd( uint256 /* principalAdded */ ) internal view returns (uint256) { // total supply should always equal position balance, except after closing with trusted // recipient, in which case this function cannot be called. uint256 positionBalance = Margin(DYDX_MARGIN).getPositionBalance(POSITION_ID); return positionBalance.sub(totalSupply_); } function getCloseAmounts( uint256 requestedCloseAmount, uint256 balance, uint256 positionPrincipal ) private view returns ( uint256 /* tokenAmount */, uint256 /* allowedCloseAmount */ ) { uint256 positionBalance = Margin(DYDX_MARGIN).getPositionBalance(POSITION_ID); uint256 requestedTokenAmount = MathHelpers.getPartialAmount( requestedCloseAmount, positionPrincipal, positionBalance ); // if user has enough tokens, allow the close to occur if (requestedTokenAmount <= balance) { return (requestedTokenAmount, requestedCloseAmount); } // The maximum amount of principal able to be closed without using more heldTokens // than balance uint256 allowedCloseAmount = MathHelpers.getPartialAmount( balance, positionBalance, positionPrincipal ); // the new close amount should not be higher than what was requested assert(allowedCloseAmount < requestedCloseAmount); uint256 allowedTokenAmount = MathHelpers.getPartialAmount( allowedCloseAmount, positionPrincipal, positionBalance ); return (allowedTokenAmount, allowedCloseAmount); } } // File: contracts/margin/external/ERC20/ERC20CappedLong.sol /** * @title ERC20CappedLong * @author dYdX * * ERC20Long with a limit on the number of tokens that can be minted, and a restriction on * which addreses can close the position after it is force-recoverable. */ contract ERC20CappedLong is ERC20Long, ERC20CappedPosition, DetailedERC20 { using SafeMath for uint256; // ============ Constructor ============ constructor( bytes32 positionId, address margin, address initialTokenHolder, address[] trustedRecipients, address[] trustedWithdrawers, address[] trustedLateClosers, uint256 cap, string name, string symbol, uint8 decimals ) public ERC20Long( positionId, margin, initialTokenHolder, trustedRecipients, trustedWithdrawers ) ERC20CappedPosition( trustedLateClosers, cap ) DetailedERC20( name, symbol, decimals ) { } // ============ Internal Overriding Functions ============ function getTokenAmountOnAdd( uint256 principalAdded ) internal view returns (uint256) { uint256 tokenAmount = super.getTokenAmountOnAdd(principalAdded); require( totalSupply_.add(tokenAmount) <= tokenCap, "ERC20CappedLong#getTokenAmountOnAdd: Adding tokenAmount would exceed cap" ); return tokenAmount; } }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"DYDX_MARGIN","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"newCap","type":"uint256"}],"name":"setTokenCap","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"closer","type":"address"},{"name":"payoutRecipient","type":"address"},{"name":"positionId","type":"bytes32"},{"name":"requestedAmount","type":"uint256"}],"name":"closeOnBehalfOf","outputs":[{"name":"","type":"address"},{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"TRUSTED_LATE_CLOSERS","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"positionId","type":"bytes32"}],"name":"getPositionDeedHolder","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"onBehalfOf","type":"address"}],"name":"withdraw","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseApproval","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"renounceOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"TRUSTED_RECIPIENTS","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"closedUsingTrustedRecipient","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"INITIAL_TOKEN_HOLDER","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"TRUSTED_WITHDRAWERS","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"state","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseApproval","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"POSITION_ID","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"trader","type":"address"},{"name":"positionId","type":"bytes32"},{"name":"principalAdded","type":"uint256"}],"name":"increasePositionOnBehalfOf","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"tokenCap","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"heldToken","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"","type":"address"},{"name":"positionId","type":"bytes32"}],"name":"receivePositionOwnership","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"closer","type":"address"},{"name":"allowed","type":"bool"}],"name":"setTrustedLateCloser","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"positionId","type":"bytes32"},{"name":"margin","type":"address"},{"name":"initialTokenHolder","type":"address"},{"name":"trustedRecipients","type":"address[]"},{"name":"trustedWithdrawers","type":"address[]"},{"name":"trustedLateClosers","type":"address[]"},{"name":"cap","type":"uint256"},{"name":"name","type":"string"},{"name":"symbol","type":"string"},{"name":"decimals","type":"uint8"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"tokenCap","type":"uint256"}],"name":"TokenCapSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"closer","type":"address"},{"indexed":false,"name":"allowed","type":"bool"}],"name":"TrustedCloserSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previousOwner","type":"address"}],"name":"OwnershipRenounced","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previousOwner","type":"address"},{"indexed":true,"name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"positionId","type":"bytes32"},{"indexed":false,"name":"initialSupply","type":"uint256"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"closer","type":"address"},{"indexed":false,"name":"tokenAmount","type":"uint256"},{"indexed":false,"name":"payoutRecipient","type":"address"}],"name":"ClosedByTrustedParty","type":"event"},{"anonymous":false,"inputs":[],"name":"CompletelyClosed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"redeemer","type":"address"},{"indexed":false,"name":"tokensRedeemed","type":"uint256"},{"indexed":false,"name":"heldTokenPayout","type":"uint256"}],"name":"Withdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"redeemer","type":"address"},{"indexed":false,"name":"closeAmount","type":"uint256"}],"name":"Close","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]
Deployed Bytecode
0x60806040526004361061018a5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166306fdde03811461018f578063095ea7b3146102195780630dda60cc1461025e57806318160ddd1461029c57806323b872dd146102c35780632854bc7e146102fa578063313ce5671461031457806335ba1d0a1461033f5780633cd29c46146103a957806347369a7c146103d757806351cff8d9146103ef578063661884631461041d57806370a082311461044e578063715018a61461047c5780638da5cb5b14610491578063959ca8ab146104a657806395d89b41146104d4578063a9059cbb146104e9578063ae08fa551461051a578063bcfa07861461052f578063bd7456e314610544578063c19d93fb14610572578063d73dd623146105ab578063d7ac71ff146105dc578063db4c528b146105f1578063dd54291b14610625578063dd56898c1461063a578063dd62ed3e1461064f578063e25bd40914610683578063f2fde38b146106b4578063f70b0a07146106e2575b600080fd5b34801561019b57600080fd5b506101a4610715565b6040805160208082528351818301528351919283929083019185019080838360005b838110156101de5781810151838201526020016101c6565b50505050905090810190601f16801561020b5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561022557600080fd5b5061024a73ffffffffffffffffffffffffffffffffffffffff600435166024356107c1565b604080519115158252519081900360200190f35b34801561026a57600080fd5b50610273610835565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b3480156102a857600080fd5b506102b1610851565b60408051918252519081900360200190f35b3480156102cf57600080fd5b5061024a73ffffffffffffffffffffffffffffffffffffffff60043581169060243516604435610857565b34801561030657600080fd5b50610312600435610a29565b005b34801561032057600080fd5b50610329610a88565b6040805160ff9092168252519081900360200190f35b34801561034b57600080fd5b5061037973ffffffffffffffffffffffffffffffffffffffff60043581169060243516604435606435610a91565b6040805173ffffffffffffffffffffffffffffffffffffffff909316835260208301919091528051918290030190f35b3480156103b557600080fd5b5061024a73ffffffffffffffffffffffffffffffffffffffff60043516610e7a565b3480156103e357600080fd5b50610273600435610e8f565b3480156103fb57600080fd5b506102b173ffffffffffffffffffffffffffffffffffffffff60043516610f34565b34801561042957600080fd5b5061024a73ffffffffffffffffffffffffffffffffffffffff60043516602435611150565b34801561045a57600080fd5b506102b173ffffffffffffffffffffffffffffffffffffffff60043516611273565b34801561048857600080fd5b5061031261129b565b34801561049d57600080fd5b5061027361132e565b3480156104b257600080fd5b5061024a73ffffffffffffffffffffffffffffffffffffffff6004351661134a565b3480156104e057600080fd5b506101a461135f565b3480156104f557600080fd5b5061024a73ffffffffffffffffffffffffffffffffffffffff600435166024356113d8565b34801561052657600080fd5b5061024a6114e0565b34801561053b57600080fd5b50610273611502565b34801561055057600080fd5b5061024a73ffffffffffffffffffffffffffffffffffffffff6004351661151e565b34801561057e57600080fd5b50610587611533565b6040518082600281111561059757fe5b60ff16815260200191505060405180910390f35b3480156105b757600080fd5b5061024a73ffffffffffffffffffffffffffffffffffffffff6004351660243561153c565b3480156105e857600080fd5b506102b16115ef565b3480156105fd57600080fd5b5061027373ffffffffffffffffffffffffffffffffffffffff600435166024356044356115f5565b34801561063157600080fd5b506102b1611ab1565b34801561064657600080fd5b50610273611ab7565b34801561065b57600080fd5b506102b173ffffffffffffffffffffffffffffffffffffffff60043581169060243516611ad8565b34801561068f57600080fd5b5061027373ffffffffffffffffffffffffffffffffffffffff60043516602435611b10565b3480156106c057600080fd5b5061031273ffffffffffffffffffffffffffffffffffffffff60043516611ea8565b3480156106ee57600080fd5b5061031273ffffffffffffffffffffffffffffffffffffffff600435166024351515611ed8565b600d805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f810184900484028201840190925281815292918301828280156107b95780601f1061078e576101008083540402835291602001916107b9565b820191906000526020600020905b81548152906001019060200180831161079c57829003601f168201915b505050505081565b33600081815260036020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716808552908352818420869055815186815291519394909390927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925928290030190a35060015b92915050565b60045473ffffffffffffffffffffffffffffffffffffffff1681565b60025490565b73ffffffffffffffffffffffffffffffffffffffff831660009081526001602052604081205482111561088957600080fd5b73ffffffffffffffffffffffffffffffffffffffff841660009081526003602090815260408083203384529091529020548211156108c657600080fd5b73ffffffffffffffffffffffffffffffffffffffff831615156108e857600080fd5b73ffffffffffffffffffffffffffffffffffffffff841660009081526001602052604090205461091e908363ffffffff611f8b16565b73ffffffffffffffffffffffffffffffffffffffff8086166000908152600160205260408082209390935590851681522054610960908363ffffffff611f9d16565b73ffffffffffffffffffffffffffffffffffffffff80851660009081526001602090815260408083209490945591871681526003825282812033825290915220546109b1908363ffffffff611f8b16565b73ffffffffffffffffffffffffffffffffffffffff808616600081815260036020908152604080832033845282529182902094909455805186815290519287169391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a35060019392505050565b600a5473ffffffffffffffffffffffffffffffffffffffff163314610a4d57600080fd5b600c8190556040805182815290517fb8b1d1cfbb7d9887f2842719df47280b1956879afc2e4ecd0cf92fbb5e932fd29181900360200190a150565b600f5460ff1681565b60045460009081908190819073ffffffffffffffffffffffffffffffffffffffff163314610b4657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f4f6e6c794d617267696e236f6e6c794d617267696e3a204f6e6c79204d61726760448201527f696e2063616e2063616c6c000000000000000000000000000000000000000000606482015290519081900360840190fd5b600080546001908101918290558060095460ff166002811115610b6557fe5b14610bf757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602860248201527f4552433230506f736974696f6e236f6e6c7953746174653a20496e636f72726560448201527f6374205374617465000000000000000000000000000000000000000000000000606482015290519081900360840190fd5b60065488908114610c8f57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f4552433230506f736974696f6e236f6e6c79506f736974696f6e3a20496e636f60448201527f727265637420706f736974696f6e000000000000000000000000000000000000606482015290519081900360840190fd5b60048054604080517f0e8a4ac70000000000000000000000000000000000000000000000000000000081529283018c90525173ffffffffffffffffffffffffffffffffffffffff90911691630e8a4ac7916024808301926020929190829003018186803b158015610cff57600080fd5b505afa158015610d13573d6000803e3d6000fd5b505050506040513d6020811015610d2957600080fd5b5051945084881115610d3757fe5b73ffffffffffffffffffffffffffffffffffffffff8a1660009081526007602052604090205460ff1615610d7757610d708b8b8a611faa565b9350610d85565b610d828b8987612156565b93505b60008411610d8f57fe5b87841115610d9957fe5b84841415610df657600980547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660021790556040517fb147077378b3c0311f1c5a608d4cb621068096d16e8b1fdd1f1157ebc146306490600090a15b30849650965050506000548114610e6e57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f5265656e7472616e637920636865636b206661696c7572650000000000000000604482015290519081900360640190fd5b50505094509492505050565b600b6020526000908152604090205460ff1681565b60065460009082908114610f2a57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f4552433230506f736974696f6e236f6e6c79506f736974696f6e3a20496e636f60448201527f727265637420706f736974696f6e000000000000000000000000000000000000606482015290519081900360840190fd5b3091505b50919050565b60008054600101808255610f4661229a565b600260095460ff166002811115610f5957fe5b14610feb57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f4552433230506f736974696f6e2377697468647261773a20506f736974696f6e60448201527f20686173206e6f7420796574206265656e20636c6f7365640000000000000000606482015290519081900360840190fd5b3373ffffffffffffffffffffffffffffffffffffffff8416146110d4573360009081526008602052604090205460ff1615156110d457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152605160248201527f4552433230506f736974696f6e2377697468647261773a204f6e6c792074727560448201527f737465642077697468647261776572732063616e207769746864726177206f6e60648201527f20626568616c66206f66206f7468657273000000000000000000000000000000608482015290519081900360a40190fd5b6110de33846123b2565b91506000548114610f2e57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f5265656e7472616e637920636865636b206661696c7572650000000000000000604482015290519081900360640190fd5b33600090815260036020908152604080832073ffffffffffffffffffffffffffffffffffffffff861684529091528120548083106111be5733600090815260036020908152604080832073ffffffffffffffffffffffffffffffffffffffff88168452909152812055611200565b6111ce818463ffffffff611f8b16565b33600090815260036020908152604080832073ffffffffffffffffffffffffffffffffffffffff891684529091529020555b33600081815260036020908152604080832073ffffffffffffffffffffffffffffffffffffffff89168085529083529281902054815190815290519293927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060019392505050565b73ffffffffffffffffffffffffffffffffffffffff1660009081526001602052604090205490565b600a5473ffffffffffffffffffffffffffffffffffffffff1633146112bf57600080fd5b600a5460405173ffffffffffffffffffffffffffffffffffffffff909116907ff8df31144d9c2f0f6b59d69b8b98abd5459d07f2742c4df920b25aae33c6482090600090a2600a80547fffffffffffffffffffffffff0000000000000000000000000000000000000000169055565b600a5473ffffffffffffffffffffffffffffffffffffffff1681565b60076020526000908152604090205460ff1681565b600e805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f810184900484028201840190925281815292918301828280156107b95780601f1061078e576101008083540402835291602001916107b9565b336000908152600160205260408120548211156113f457600080fd5b73ffffffffffffffffffffffffffffffffffffffff8316151561141657600080fd5b33600090815260016020526040902054611436908363ffffffff611f8b16565b336000908152600160205260408082209290925573ffffffffffffffffffffffffffffffffffffffff851681522054611475908363ffffffff611f9d16565b73ffffffffffffffffffffffffffffffffffffffff84166000818152600160209081526040918290209390935580518581529051919233927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a350600192915050565b6009547501000000000000000000000000000000000000000000900460ff1681565b60055473ffffffffffffffffffffffffffffffffffffffff1681565b60086020526000908152604090205460ff1681565b60095460ff1681565b33600090815260036020908152604080832073ffffffffffffffffffffffffffffffffffffffff8616845290915281205461157d908363ffffffff611f9d16565b33600081815260036020908152604080832073ffffffffffffffffffffffffffffffffffffffff89168085529083529281902085905580519485525191937f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929081900390910190a350600192915050565b60065481565b600454600090819073ffffffffffffffffffffffffffffffffffffffff1633146116a657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f4f6e6c794d617267696e236f6e6c794d617267696e3a204f6e6c79204d61726760448201527f696e2063616e2063616c6c000000000000000000000000000000000000000000606482015290519081900360840190fd5b600080546001908101918290558060095460ff1660028111156116c557fe5b1461175757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602860248201527f4552433230506f736974696f6e236f6e6c7953746174653a20496e636f72726560448201527f6374205374617465000000000000000000000000000000000000000000000000606482015290519081900360840190fd5b600654869081146117ef57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f4552433230506f736974696f6e236f6e6c79506f736974696f6e3a20496e636f60448201527f727265637420706f736974696f6e000000000000000000000000000000000000606482015290519081900360840190fd5b60048054600654604080517f6e0cd415000000000000000000000000000000000000000000000000000000008152938401919091525173ffffffffffffffffffffffffffffffffffffffff90911691636e0cd415916024808301926020929190829003018186803b15801561186357600080fd5b505afa158015611877573d6000803e3d6000fd5b505050506040513d602081101561188d57600080fd5b50511561194757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604360248201527f4552433230506f736974696f6e23696e637265617365506f736974696f6e4f6e60448201527f426568616c664f663a20506f736974696f6e206973206d617267696e2d63616c60648201527f6c65640000000000000000000000000000000000000000000000000000000000608482015290519081900360a40190fd5b6009547501000000000000000000000000000000000000000000900460ff1615611a1e57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152605160248201527f4552433230506f736974696f6e23696e637265617365506f736974696f6e4f6e60448201527f426568616c664f663a20506f736974696f6e20636c6f736564207573696e672060648201527f7472757374656420726563697069656e74000000000000000000000000000000608482015290519081900360a40190fd5b611a2786612498565b9350611a338885612577565b30945050506000548114611aa857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f5265656e7472616e637920636865636b206661696c7572650000000000000000604482015290519081900360640190fd5b50509392505050565b600c5481565b600954610100900473ffffffffffffffffffffffffffffffffffffffff1681565b73ffffffffffffffffffffffffffffffffffffffff918216600090815260036020908152604080832093909416825291909152205490565b6000611b1a612ef2565b60045460009073ffffffffffffffffffffffffffffffffffffffff163314611bc957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f4f6e6c794d617267696e236f6e6c794d617267696e3a204f6e6c79204d61726760448201527f696e2063616e2063616c6c000000000000000000000000000000000000000000606482015290519081900360840190fd5b60008054600101808255908060095460ff166002811115611be657fe5b14611c7857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602860248201527f4552433230506f736974696f6e236f6e6c7953746174653a20496e636f72726560448201527f6374205374617465000000000000000000000000000000000000000000000000606482015290519081900360840190fd5b60065486908114611d1057604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f4552433230506f736974696f6e236f6e6c79506f736974696f6e3a20496e636f60448201527f727265637420706f736974696f6e000000000000000000000000000000000000606482015290519081900360840190fd5b600454600654611d369173ffffffffffffffffffffffffffffffffffffffff1690612647565b945060008560800151111515611d4857fe5b60098054602087015173ffffffffffffffffffffffffffffffffffffffff16610100027fffffffffffffffffffffff0000000000000000000000000000000000000000ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00909216600117919091161790556080850151611dc890612498565b600654604080519182526020820183905280519296507fc885ca8035bb269b79b1906eb7aaeeec6a4713b0976b175c25aa84b49acfa47392918290030190a1600554611e2a9073ffffffffffffffffffffffffffffffffffffffff1685612577565b30955050506000548114611e9f57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f5265656e7472616e637920636865636b206661696c7572650000000000000000604482015290519081900360640190fd5b50505092915050565b600a5473ffffffffffffffffffffffffffffffffffffffff163314611ecc57600080fd5b611ed5816127d5565b50565b600a5473ffffffffffffffffffffffffffffffffffffffff163314611efc57600080fd5b73ffffffffffffffffffffffffffffffffffffffff82166000818152600b602090815260409182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001685151590811790915582519384529083015280517f4f6c69ea5a0543c614b665fb438ce26ed0e5607ecdb69820ac336c815a852d6f9281900390910190a15050565b600082821115611f9757fe5b50900390565b8181018281101561082f57fe5b6000611fb4612ef2565b6004546006546000918291611fdf9173ffffffffffffffffffffffffffffffffffffffff1690612647565b925061200983610120015163ffffffff168460e0015163ffffffff16611f9d90919063ffffffff16565b42119150600083610100015163ffffffff1611801561204e575061204b8360c0015163ffffffff1684610100015163ffffffff16611f9d90919063ffffffff16565b42115b905080806120595750815b156121405773ffffffffffffffffffffffffffffffffffffffff87166000908152600b602052604090205460ff16151561214057604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152605260248201527f4552433230436170706564506f736974696f6e23636c6f73655573696e67547260448201527f7573746564526563697069656e743a20636c6f736572206e6f7420696e20545260648201527f55535445445f4c4154455f434c4f534552530000000000000000000000000000608482015290519081900360a40190fd5b61214b878787612885565b979650505050505050565b73ffffffffffffffffffffffffffffffffffffffff83166000908152600160205260408120548180612189868487612954565b9150915060008211801561219d5750600081115b151561223057604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f4552433230506f736974696f6e23636c6f73653a2043616e6e6f7420636c6f7360448201527f65203020616d6f756e7400000000000000000000000000000000000000000000606482015290519081900360840190fd5b8581111561223a57fe5b6122448783612a58565b60408051838152905173ffffffffffffffffffffffffffffffffffffffff8916917f684222b0069d4a2e5e0d986611cc5182d543904c4e4264bf770d4e51faefc822919081900360200190a29695505050505050565b600160095460ff1660028111156122ad57fe5b148015612356575060048054600654604080517f640075f3000000000000000000000000000000000000000000000000000000008152938401919091525173ffffffffffffffffffffffffffffffffffffffff9091169163640075f3916024808301926020929190829003018186803b15801561232957600080fd5b505afa15801561233d573d6000803e3d6000fd5b505050506040513d602081101561235357600080fd5b50515b156123b057600980547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660021790556040517fb147077378b3c0311f1c5a608d4cb621068096d16e8b1fdd1f1157ebc146306490600090a15b565b6000806000806123c185611273565b92508215156123d35760009350611e9f565b6009546123fb90610100900473ffffffffffffffffffffffffffffffffffffffff1630612b27565b915061240a8360025484612bf5565b90506124168584612a58565b6040805184815260208101839052815173ffffffffffffffffffffffffffffffffffffffff8816927ff279e6a1f5e320cca91135676d9cb6e44ca8a08c0b88342bcdb1144f6511b568928290030190a260095461248f90610100900473ffffffffffffffffffffffffffffffffffffffff168783612c1f565b95945050505050565b6000806124a483612dbe565b9050600c546124be82600254611f9d90919063ffffffff16565b111561082f57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604860248201527f45524332304361707065644c6f6e6723676574546f6b656e416d6f756e744f6e60448201527f4164643a20416464696e6720746f6b656e416d6f756e7420776f756c6420657860648201527f6365656420636170000000000000000000000000000000000000000000000000608482015290519081900360a40190fd5b73ffffffffffffffffffffffffffffffffffffffff8216151561259657fe5b6002546125a9908263ffffffff611f9d16565b60025573ffffffffffffffffffffffffffffffffffffffff82166000908152600160205260409020546125e2908263ffffffff611f9d16565b73ffffffffffffffffffffffffffffffffffffffff831660008181526001602090815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b61264f612ef2565b612657612f56565b61265f612f75565b612667612f90565b604080517f1928b3cb00000000000000000000000000000000000000000000000000000000815260048101879052905173ffffffffffffffffffffffffffffffffffffffff881691631928b3cb91602480830192610180929190829003018186803b1580156126d557600080fd5b505afa1580156126e9573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525061018081101561270f57600080fd5b506040805161018081018252825173ffffffffffffffffffffffffffffffffffffffff908116825260208085015182169083015283830151811692820192909252606080840151909216918101919091526080808301519082015260a0808301519082015260c08083015163ffffffff9081169183019190915260e0808401518216908301526101008084015182169083015261012080840151821690830152610140808401518216908301526101609283015116918101919091529695505050505050565b73ffffffffffffffffffffffffffffffffffffffff811615156127f757600080fd5b600a5460405173ffffffffffffffffffffffffffffffffffffffff8084169216907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3600a80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b600080821161289057fe5b6009547501000000000000000000000000000000000000000000900460ff1615156128f657600980547fffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffff1675010000000000000000000000000000000000000000001790555b6040805173ffffffffffffffffffffffffffffffffffffffff80871682526020820185905285168183015290517f2a73d58687443b7a81f739a2964d5cf5068b313c8756ae6726b20d09926f864b9181900360600190a15092915050565b60048054600654604080517f54d7986800000000000000000000000000000000000000000000000000000000815293840191909152516000928392839283928392839273ffffffffffffffffffffffffffffffffffffffff909116916354d79868916024808301926020929190829003018186803b1580156129d557600080fd5b505afa1580156129e9573d6000803e3d6000fd5b505050506040513d60208110156129ff57600080fd5b50519350612a0e898886612bf5565b9250878311612a2257828995509550612a4c565b612a2d888589612bf5565b9150888210612a3857fe5b612a43828886612bf5565b90508082955095505b50505050935093915050565b73ffffffffffffffffffffffffffffffffffffffff82161515612a7757fe5b600254612a8a908263ffffffff611f8b16565b60025573ffffffffffffffffffffffffffffffffffffffff8216600090815260016020526040902054612ac3908263ffffffff611f8b16565b73ffffffffffffffffffffffffffffffffffffffff83166000818152600160209081526040808320949094558351858152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a35050565b60008273ffffffffffffffffffffffffffffffffffffffff166370a08231836040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b158015612bc257600080fd5b505afa158015612bd6573d6000803e3d6000fd5b505050506040513d6020811015612bec57600080fd5b50519392505050565b6000612c1783612c0b868563ffffffff612e8016565b9063ffffffff612ea916565b949350505050565b30811580612c5857508273ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b15612c6257612db8565b8373ffffffffffffffffffffffffffffffffffffffff1663a9059cbb84846040518363ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050600060405180830381600087803b158015612d0557600080fd5b505af1158015612d19573d6000803e3d6000fd5b50505050612d25612ebe565b1515612db857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602760248201527f546f6b656e496e746572616374237472616e736665723a205472616e7366657260448201527f206661696c656400000000000000000000000000000000000000000000000000606482015290519081900360840190fd5b50505050565b60048054600654604080517f54d798680000000000000000000000000000000000000000000000000000000081529384019190915251600092839273ffffffffffffffffffffffffffffffffffffffff16916354d7986891602480820192602092909190829003018186803b158015612e3657600080fd5b505afa158015612e4a573d6000803e3d6000fd5b505050506040513d6020811015612e6057600080fd5b5051600254909150612e7990829063ffffffff611f8b16565b9392505050565b6000821515612e915750600061082f565b50818102818382811515612ea157fe5b041461082f57fe5b60008183811515612eb657fe5b049392505050565b6000803d8015612ed55760208114612ede57612eea565b60019150612eea565b60206000803e60005191505b501515919050565b6040805161018081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e0810182905261010081018290526101208101829052610140810182905261016081019190915290565b6080604051908101604052806004906020820280388339509192915050565b60408051808201825290600290829080388339509192915050565b60c06040519081016040528060069060208202803883395091929150505600a165627a7a72305820febb5ce4d6cb0de254a2396f5755d91f4ff803dba5cc2b40eda73a2b701e45a00029
Swarm Source
bzzr://febb5ce4d6cb0de254a2396f5755d91f4ff803dba5cc2b40eda73a2b701e45a0
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 30 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|---|---|---|---|---|
ETH | 100.00% | $2,470.94 | 1.7187 | $4,246.9 |
Loading...
Loading
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.