Feature Tip: Add private address tag to any address under My Name Tag !
More Info
Private Name Tags
ContractCreator
Latest 5 from a total of 5 transactions
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 0xf829d87B...73Ca5Bb06 The constructor portion of the code might be different and could alter the actual behaviour of the contract
Contract Name:
BucketLenderWithRecoveryDelay
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-16 */ 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/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/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/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: 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 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/lib/LoanOfferingParser.sol /** * @title LoanOfferingParser * @author dYdX * * Contract for LoanOfferingVerifiers to parse arguments */ contract LoanOfferingParser { // ============ Parsing Functions ============ function parseLoanOffering( address[9] addresses, uint256[7] values256, uint32[4] values32, bytes signature ) internal pure returns (MarginCommon.LoanOffering memory) { MarginCommon.LoanOffering memory loanOffering; fillLoanOfferingAddresses(loanOffering, addresses); fillLoanOfferingValues256(loanOffering, values256); fillLoanOfferingValues32(loanOffering, values32); loanOffering.signature = signature; return loanOffering; } function fillLoanOfferingAddresses( MarginCommon.LoanOffering memory loanOffering, address[9] addresses ) private pure { loanOffering.owedToken = addresses[0]; loanOffering.heldToken = addresses[1]; loanOffering.payer = addresses[2]; loanOffering.owner = addresses[3]; loanOffering.taker = addresses[4]; loanOffering.positionOwner = addresses[5]; loanOffering.feeRecipient = addresses[6]; loanOffering.lenderFeeToken = addresses[7]; loanOffering.takerFeeToken = addresses[8]; } function fillLoanOfferingValues256( MarginCommon.LoanOffering memory loanOffering, uint256[7] values256 ) private pure { loanOffering.rates.maxAmount = values256[0]; loanOffering.rates.minAmount = values256[1]; loanOffering.rates.minHeldToken = values256[2]; loanOffering.rates.lenderFee = values256[3]; loanOffering.rates.takerFee = values256[4]; loanOffering.expirationTimestamp = values256[5]; loanOffering.salt = values256[6]; } function fillLoanOfferingValues32( MarginCommon.LoanOffering memory loanOffering, uint32[4] values32 ) private pure { loanOffering.callTimeLimit = values32[0]; loanOffering.maxDuration = values32[1]; loanOffering.rates.interestRate = values32[2]; loanOffering.rates.interestPeriod = values32[3]; } } // 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/BucketLender/BucketLender.sol /* solium-disable-next-line max-len*/ /** * @title BucketLender * @author dYdX * * On-chain shared lender that allows anyone to deposit tokens into this contract to be used to * lend tokens for a particular margin position. * * - Each bucket has three variables: * - Available Amount * - The available amount of tokens that the bucket has to lend out * - Outstanding Principal * - The amount of principal that the bucket is responsible for in the margin position * - Weight * - Used to keep track of each account's weighted ownership within a bucket * - Relative weight between buckets is meaningless * - Only accounts' relative weight within a bucket matters * * - Token Deposits: * - Go into a particular bucket, determined by time since the start of the position * - If the position has not started: bucket = 0 * - If the position has started: bucket = ceiling(time_since_start / BUCKET_TIME) * - This is always the highest bucket; no higher bucket yet exists * - Increase the bucket's Available Amount * - Increase the bucket's weight and the account's weight in that bucket * * - Token Withdrawals: * - Can be from any bucket with available amount * - Decrease the bucket's Available Amount * - Decrease the bucket's weight and the account's weight in that bucket * * - Increasing the Position (Lending): * - The lowest buckets with Available Amount are used first * - Decreases Available Amount * - Increases Outstanding Principal * * - Decreasing the Position (Being Paid-Back) * - The highest buckets with Outstanding Principal are paid back first * - Decreases Outstanding Principal * - Increases Available Amount * * * - Over time, this gives highest interest rates to earlier buckets, but disallows withdrawals from * those buckets for a longer period of time. * - Deposits in the same bucket earn the same interest rate. * - Lenders can withdraw their funds at any time if they are not being lent (and are therefore not * making the maximum interest). * - The highest bucket with Outstanding Principal is always less-than-or-equal-to the lowest bucket with Available Amount */ contract BucketLender is Ownable, OnlyMargin, LoanOwner, IncreaseLoanDelegator, MarginCallDelegator, CancelMarginCallDelegator, ForceRecoverCollateralDelegator, LoanOfferingParser, LoanOfferingVerifier, ReentrancyGuard { using SafeMath for uint256; using TokenInteract for address; // ============ Events ============ event Deposit( address indexed beneficiary, uint256 bucket, uint256 amount, uint256 weight ); event Withdraw( address indexed withdrawer, uint256 bucket, uint256 weight, uint256 owedTokenWithdrawn, uint256 heldTokenWithdrawn ); event PrincipalIncreased( uint256 principalTotal, uint256 bucketNumber, uint256 principalForBucket, uint256 amount ); event PrincipalDecreased( uint256 principalTotal, uint256 bucketNumber, uint256 principalForBucket, uint256 amount ); event AvailableIncreased( uint256 availableTotal, uint256 bucketNumber, uint256 availableForBucket, uint256 amount ); event AvailableDecreased( uint256 availableTotal, uint256 bucketNumber, uint256 availableForBucket, uint256 amount ); // ============ State Variables ============ /** * Available Amount is the amount of tokens that is available to be lent by each bucket. * These tokens are also available to be withdrawn by the accounts that have weight in the * bucket. */ // Available Amount for each bucket mapping(uint256 => uint256) public availableForBucket; // Total Available Amount uint256 public availableTotal; /** * Outstanding Principal is the share of the margin position's principal that each bucket * is responsible for. That is, each bucket with Outstanding Principal is owed * (Outstanding Principal)*E^(RT) owedTokens in repayment. */ // Outstanding Principal for each bucket mapping(uint256 => uint256) public principalForBucket; // Total Outstanding Principal uint256 public principalTotal; /** * Weight determines an account's proportional share of a bucket. Relative weights have no * meaning if they are not for the same bucket. Likewise, the relative weight of two buckets has * no meaning. However, the relative weight of two accounts within the same bucket is equal to * the accounts' shares in the bucket and are therefore proportional to the payout that they * should expect from withdrawing from that bucket. */ // Weight for each account in each bucket mapping(uint256 => mapping(address => uint256)) public weightForBucketForAccount; // Total Weight for each bucket mapping(uint256 => uint256) public weightForBucket; /** * The critical bucket is: * - Greater-than-or-equal-to The highest bucket with Outstanding Principal * - Less-than-or-equal-to the lowest bucket with Available Amount * * It is equal to both of these values in most cases except in an edge cases where the two * buckets are different. This value is cached to find such a bucket faster than looping through * all possible buckets. */ uint256 public criticalBucket = 0; /** * Latest cached value for totalOwedTokenRepaidToLender. * This number updates on the dYdX Margin base protocol whenever the position is * partially-closed, but this contract is not notified at that time. Therefore, it is updated * upon increasing the position or when depositing/withdrawing */ uint256 public cachedRepaidAmount = 0; // True if the position was closed from force-recovering the collateral bool public wasForceClosed = false; // ============ Constants ============ // Unique ID of the position bytes32 public POSITION_ID; // Address of the token held in the position as collateral address public HELD_TOKEN; // Address of the token being lent address public OWED_TOKEN; // Time between new buckets uint32 public BUCKET_TIME; // Interest rate of the position uint32 public INTEREST_RATE; // Interest period of the position uint32 public INTEREST_PERIOD; // Maximum duration of the position uint32 public MAX_DURATION; // Margin-call time-limit of the position uint32 public CALL_TIMELIMIT; // (NUMERATOR/DENOMINATOR) denotes the minimum collateralization ratio of the position uint32 public MIN_HELD_TOKEN_NUMERATOR; uint32 public MIN_HELD_TOKEN_DENOMINATOR; // Accounts that are permitted to margin-call positions (or cancel the margin call) mapping(address => bool) public TRUSTED_MARGIN_CALLERS; // Accounts that are permitted to withdraw on behalf of any address mapping(address => bool) public TRUSTED_WITHDRAWERS; // ============ Constructor ============ constructor( address margin, bytes32 positionId, address heldToken, address owedToken, uint32[7] parameters, address[] trustedMarginCallers, address[] trustedWithdrawers ) public OnlyMargin(margin) { POSITION_ID = positionId; HELD_TOKEN = heldToken; OWED_TOKEN = owedToken; require( parameters[0] != 0, "BucketLender#constructor: BUCKET_TIME cannot be zero" ); BUCKET_TIME = parameters[0]; INTEREST_RATE = parameters[1]; INTEREST_PERIOD = parameters[2]; MAX_DURATION = parameters[3]; CALL_TIMELIMIT = parameters[4]; MIN_HELD_TOKEN_NUMERATOR = parameters[5]; MIN_HELD_TOKEN_DENOMINATOR = parameters[6]; // Initialize TRUSTED_MARGIN_CALLERS and TRUSTED_WITHDRAWERS uint256 i = 0; for (i = 0; i < trustedMarginCallers.length; i++) { TRUSTED_MARGIN_CALLERS[trustedMarginCallers[i]] = true; } for (i = 0; i < trustedWithdrawers.length; i++) { TRUSTED_WITHDRAWERS[trustedWithdrawers[i]] = true; } // Set maximum allowance on proxy OWED_TOKEN.approve( Margin(margin).getTokenProxyAddress(), MathHelpers.maxUint256() ); } // ============ Modifiers ============ modifier onlyPosition(bytes32 positionId) { require( POSITION_ID == positionId, "BucketLender#onlyPosition: Incorrect position" ); _; } // ============ Margin-Only State-Changing Functions ============ /** * 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. * * @param addresses Loan offering addresses * @param values256 Loan offering uint256s * @param values32 Loan offering uint32s * @param positionId Unique ID of the position * @param signature Arbitrary bytes * @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 nonReentrant onlyPosition(positionId) returns (address) { require( Margin(DYDX_MARGIN).containsPosition(POSITION_ID), "BucketLender#verifyLoanOffering: This contract should not open a new position" ); MarginCommon.LoanOffering memory loanOffering = parseLoanOffering( addresses, values256, values32, signature ); // CHECK ADDRESSES assert(loanOffering.owedToken == OWED_TOKEN); assert(loanOffering.heldToken == HELD_TOKEN); assert(loanOffering.payer == address(this)); assert(loanOffering.owner == address(this)); require( loanOffering.taker == address(0), "BucketLender#verifyLoanOffering: loanOffering.taker is non-zero" ); require( loanOffering.feeRecipient == address(0), "BucketLender#verifyLoanOffering: loanOffering.feeRecipient is non-zero" ); require( loanOffering.positionOwner == address(0), "BucketLender#verifyLoanOffering: loanOffering.positionOwner is non-zero" ); require( loanOffering.lenderFeeToken == address(0), "BucketLender#verifyLoanOffering: loanOffering.lenderFeeToken is non-zero" ); require( loanOffering.takerFeeToken == address(0), "BucketLender#verifyLoanOffering: loanOffering.takerFeeToken is non-zero" ); // CHECK VALUES256 require( loanOffering.rates.maxAmount == MathHelpers.maxUint256(), "BucketLender#verifyLoanOffering: loanOffering.maxAmount is incorrect" ); require( loanOffering.rates.minAmount == 0, "BucketLender#verifyLoanOffering: loanOffering.minAmount is non-zero" ); require( loanOffering.rates.minHeldToken == 0, "BucketLender#verifyLoanOffering: loanOffering.minHeldToken is non-zero" ); require( loanOffering.rates.lenderFee == 0, "BucketLender#verifyLoanOffering: loanOffering.lenderFee is non-zero" ); require( loanOffering.rates.takerFee == 0, "BucketLender#verifyLoanOffering: loanOffering.takerFee is non-zero" ); require( loanOffering.expirationTimestamp == MathHelpers.maxUint256(), "BucketLender#verifyLoanOffering: expirationTimestamp is incorrect" ); require( loanOffering.salt == 0, "BucketLender#verifyLoanOffering: loanOffering.salt is non-zero" ); // CHECK VALUES32 require( loanOffering.callTimeLimit == MathHelpers.maxUint32(), "BucketLender#verifyLoanOffering: loanOffering.callTimelimit is incorrect" ); require( loanOffering.maxDuration == MathHelpers.maxUint32(), "BucketLender#verifyLoanOffering: loanOffering.maxDuration is incorrect" ); assert(loanOffering.rates.interestRate == INTEREST_RATE); assert(loanOffering.rates.interestPeriod == INTEREST_PERIOD); // no need to require anything about loanOffering.signature return address(this); } /** * Called by the Margin contract when anyone transfers ownership of a loan to this contract. * This function initializes this contract and returns this address to indicate to Margin * that it is willing to take ownership of the loan. * * @param from Address of the previous owner * @param positionId Unique ID of the position * @return This address on success, throw otherwise */ function receiveLoanOwnership( address from, bytes32 positionId ) external onlyMargin nonReentrant onlyPosition(positionId) returns (address) { MarginCommon.Position memory position = MarginHelper.getPosition(DYDX_MARGIN, POSITION_ID); uint256 initialPrincipal = position.principal; uint256 minHeldToken = MathHelpers.getPartialAmount( uint256(MIN_HELD_TOKEN_NUMERATOR), uint256(MIN_HELD_TOKEN_DENOMINATOR), initialPrincipal ); assert(initialPrincipal > 0); assert(principalTotal == 0); assert(from != address(this)); // position must be opened without lending from this position require( position.owedToken == OWED_TOKEN, "BucketLender#receiveLoanOwnership: Position owedToken mismatch" ); require( position.heldToken == HELD_TOKEN, "BucketLender#receiveLoanOwnership: Position heldToken mismatch" ); require( position.maxDuration == MAX_DURATION, "BucketLender#receiveLoanOwnership: Position maxDuration mismatch" ); require( position.callTimeLimit == CALL_TIMELIMIT, "BucketLender#receiveLoanOwnership: Position callTimeLimit mismatch" ); require( position.interestRate == INTEREST_RATE, "BucketLender#receiveLoanOwnership: Position interestRate mismatch" ); require( position.interestPeriod == INTEREST_PERIOD, "BucketLender#receiveLoanOwnership: Position interestPeriod mismatch" ); require( Margin(DYDX_MARGIN).getPositionBalance(POSITION_ID) >= minHeldToken, "BucketLender#receiveLoanOwnership: Not enough heldToken as collateral" ); // set relevant constants principalForBucket[0] = initialPrincipal; principalTotal = initialPrincipal; weightForBucket[0] = weightForBucket[0].add(initialPrincipal); weightForBucketForAccount[0][from] = weightForBucketForAccount[0][from].add(initialPrincipal); return address(this); } /** * Called by Margin when additional value is added onto the position this contract * is lending for. Balance is added to the address that loaned the additional tokens. * * @param payer Address that loaned the additional tokens * @param positionId Unique ID of the position * @param principalAdded Amount that was added to the position * @param lentAmount Amount of owedToken lent * @return This address to accept, a different address to ask that contract */ function increaseLoanOnBehalfOf( address payer, bytes32 positionId, uint256 principalAdded, uint256 lentAmount ) external onlyMargin nonReentrant onlyPosition(positionId) returns (address) { Margin margin = Margin(DYDX_MARGIN); require( payer == address(this), "BucketLender#increaseLoanOnBehalfOf: Other lenders cannot lend for this position" ); require( !margin.isPositionCalled(POSITION_ID), "BucketLender#increaseLoanOnBehalfOf: No lending while the position is margin-called" ); // This function is only called after the state has been updated in the base protocol; // thus, the principal in the base protocol will equal the principal after the increase uint256 principalAfterIncrease = margin.getPositionPrincipal(POSITION_ID); uint256 principalBeforeIncrease = principalAfterIncrease.sub(principalAdded); // principalTotal was the principal after the previous increase accountForClose(principalTotal.sub(principalBeforeIncrease)); accountForIncrease(principalAdded, lentAmount); assert(principalTotal == principalAfterIncrease); return address(this); } /** * Function a contract must implement in order to let other addresses call marginCall(). * * @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 nonReentrant onlyPosition(positionId) returns (address) { require( TRUSTED_MARGIN_CALLERS[caller], "BucketLender#marginCallOnBehalfOf: Margin-caller must be trusted" ); require( depositAmount == 0, // prevents depositing from canceling the margin-call "BucketLender#marginCallOnBehalfOf: Deposit amount must be zero" ); return address(this); } /** * Function a contract must implement in order to let other addresses call cancelMarginCall(). * * @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 nonReentrant onlyPosition(positionId) returns (address) { require( TRUSTED_MARGIN_CALLERS[canceler], "BucketLender#cancelMarginCallOnBehalfOf: Margin-call-canceler must be trusted" ); return address(this); } /** * Function a contract must implement in order to let other addresses call * forceRecoverCollateral(). * * 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 nonReentrant onlyPosition(positionId) returns (address) { return forceRecoverCollateralInternal(recipient); } // ============ Public State-Changing Functions ============ /** * Allow anyone to recalculate the Outstanding Principal and Available Amount for the buckets if * part of the position has been closed since the last position increase. */ function rebalanceBuckets() external nonReentrant { rebalanceBucketsInternal(); } /** * Allows users to deposit owedToken into this contract. Allowance must be set on this contract * for "token" in at least the amount "amount". * * @param beneficiary The account that will be entitled to this depoit * @param amount The amount of owedToken to deposit * @return The bucket number that was deposited into */ function deposit( address beneficiary, uint256 amount ) external nonReentrant returns (uint256) { Margin margin = Margin(DYDX_MARGIN); bytes32 positionId = POSITION_ID; require( beneficiary != address(0), "BucketLender#deposit: Beneficiary cannot be the zero address" ); require( amount != 0, "BucketLender#deposit: Cannot deposit zero tokens" ); require( !margin.isPositionClosed(positionId), "BucketLender#deposit: Cannot deposit after the position is closed" ); require( !margin.isPositionCalled(positionId), "BucketLender#deposit: Cannot deposit while the position is margin-called" ); rebalanceBucketsInternal(); OWED_TOKEN.transferFrom( msg.sender, address(this), amount ); uint256 bucket = getCurrentBucket(); uint256 effectiveAmount = availableForBucket[bucket].add(getBucketOwedAmount(bucket)); uint256 weightToAdd = 0; if (effectiveAmount == 0) { weightToAdd = amount; // first deposit in bucket } else { weightToAdd = MathHelpers.getPartialAmount( amount, effectiveAmount, weightForBucket[bucket] ); } require( weightToAdd != 0, "BucketLender#deposit: Cannot deposit for zero weight" ); // update state updateAvailable(bucket, amount, true); weightForBucketForAccount[bucket][beneficiary] = weightForBucketForAccount[bucket][beneficiary].add(weightToAdd); weightForBucket[bucket] = weightForBucket[bucket].add(weightToAdd); emit Deposit( beneficiary, bucket, amount, weightToAdd ); return bucket; } /** * Allows users to withdraw their lent funds. An account can withdraw its weighted share of the * bucket. * * While the position is open, a bucket's share is equal to: * Owed Token: (Available Amount) + (Outstanding Principal) * (1 + interest) * Held Token: 0 * * After the position is closed, a bucket's share is equal to: * Owed Token: (Available Amount) * Held Token: (Held Token Balance) * (Outstanding Principal) / (Total Outstanding Principal) * * @param buckets The bucket numbers to withdraw from * @param maxWeights The maximum weight to withdraw from each bucket. The amount of tokens * withdrawn will be at least this amount, but not necessarily more. * Withdrawing the same weight from different buckets does not necessarily * return the same amounts from those buckets. In order to withdraw as many * tokens as possible, use the maximum uint256. * @param onBehalfOf The address to withdraw on behalf of * @return 1) The number of owedTokens withdrawn * 2) The number of heldTokens withdrawn */ function withdraw( uint256[] buckets, uint256[] maxWeights, address onBehalfOf ) external nonReentrant returns (uint256, uint256) { require( buckets.length == maxWeights.length, "BucketLender#withdraw: The lengths of the input arrays must match" ); if (onBehalfOf != msg.sender) { require( TRUSTED_WITHDRAWERS[msg.sender], "BucketLender#withdraw: Only trusted withdrawers can withdraw on behalf of others" ); } rebalanceBucketsInternal(); // decide if some bucket is unable to be withdrawn from (is locked) // the zero value represents no-lock uint256 lockedBucket = 0; if ( Margin(DYDX_MARGIN).containsPosition(POSITION_ID) && criticalBucket == getCurrentBucket() ) { lockedBucket = criticalBucket; } uint256[2] memory results; // [0] = totalOwedToken, [1] = totalHeldToken uint256 maxHeldToken = 0; if (wasForceClosed) { maxHeldToken = HELD_TOKEN.balanceOf(address(this)); } for (uint256 i = 0; i < buckets.length; i++) { uint256 bucket = buckets[i]; // prevent withdrawing from the current bucket if it is also the critical bucket if ((bucket != 0) && (bucket == lockedBucket)) { continue; } (uint256 owedTokenForBucket, uint256 heldTokenForBucket) = withdrawSingleBucket( onBehalfOf, bucket, maxWeights[i], maxHeldToken ); results[0] = results[0].add(owedTokenForBucket); results[1] = results[1].add(heldTokenForBucket); } // Transfer share of owedToken OWED_TOKEN.transfer(msg.sender, results[0]); HELD_TOKEN.transfer(msg.sender, results[1]); return (results[0], results[1]); } /** * 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 this contract by calling * deposit() 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) { rebalanceBucketsInternal(); uint256 amount = token.balanceOf(address(this)); if (token == OWED_TOKEN) { amount = amount.sub(availableTotal); } else if (token == HELD_TOKEN) { require( !wasForceClosed, "BucketLender#withdrawExcessToken: heldToken cannot be withdrawn if force-closed" ); } token.transfer(to, amount); return amount; } // ============ Public Getter Functions ============ /** * Get the current bucket number that funds will be deposited into. This is also the highest * bucket so far. * * @return The highest bucket and the one that funds will be deposited into */ function getCurrentBucket() public view returns (uint256) { // load variables from storage; Margin margin = Margin(DYDX_MARGIN); bytes32 positionId = POSITION_ID; uint32 bucketTime = BUCKET_TIME; assert(!margin.isPositionClosed(positionId)); // if position not created, allow deposits in the first bucket if (!margin.containsPosition(positionId)) { return 0; } // return the number of BUCKET_TIME periods elapsed since the position start, rounded-up uint256 startTimestamp = margin.getPositionStartTimestamp(positionId); return block.timestamp.sub(startTimestamp).div(bucketTime).add(1); } /** * Gets the outstanding amount of owedToken owed to a bucket. This is the principal amount of * the bucket multiplied by the interest accrued in the position. If the position is closed, * then any outstanding principal will never be repaid in the form of owedToken. * * @param bucket The bucket number * @return The amount of owedToken that this bucket expects to be paid-back if the posi */ function getBucketOwedAmount( uint256 bucket ) public view returns (uint256) { // if the position is completely closed, then the outstanding principal will never be repaid if (Margin(DYDX_MARGIN).isPositionClosed(POSITION_ID)) { return 0; } uint256 lentPrincipal = principalForBucket[bucket]; // the bucket has no outstanding principal if (lentPrincipal == 0) { return 0; } // get the total amount of owedToken that would be paid back at this time uint256 owedAmount = Margin(DYDX_MARGIN).getPositionOwedAmountAtTime( POSITION_ID, principalTotal, uint32(block.timestamp) ); // return the bucket's share return MathHelpers.getPartialAmount( lentPrincipal, principalTotal, owedAmount ); } // ============ Internal Functions ============ function forceRecoverCollateralInternal( address recipient ) internal returns (address) { require( recipient == address(this), "BucketLender#forceRecoverCollateralOnBehalfOf: Recipient must be this contract" ); rebalanceBucketsInternal(); wasForceClosed = true; return address(this); } // ============ Private Helper Functions ============ /** * Recalculates the Outstanding Principal and Available Amount for the buckets. Only changes the * state if part of the position has been closed since the last position increase. */ function rebalanceBucketsInternal() private { // if force-closed, don't update the outstanding principal values; they are needed to repay // lenders with heldToken if (wasForceClosed) { return; } uint256 marginPrincipal = Margin(DYDX_MARGIN).getPositionPrincipal(POSITION_ID); accountForClose(principalTotal.sub(marginPrincipal)); assert(principalTotal == marginPrincipal); } /** * Updates the state variables at any time. Only does anything after the position has been * closed or partially-closed since the last time this function was called. * * - Increases the available amount in the highest buckets with outstanding principal * - Decreases the principal amount in those buckets * * @param principalRemoved Amount of principal closed since the last update */ function accountForClose( uint256 principalRemoved ) private { if (principalRemoved == 0) { return; } uint256 newRepaidAmount = Margin(DYDX_MARGIN).getTotalOwedTokenRepaidToLender(POSITION_ID); assert(newRepaidAmount.sub(cachedRepaidAmount) >= principalRemoved); uint256 principalToSub = principalRemoved; uint256 availableToAdd = newRepaidAmount.sub(cachedRepaidAmount); uint256 criticalBucketTemp = criticalBucket; // loop over buckets in reverse order starting with the critical bucket for ( uint256 bucket = criticalBucketTemp; principalToSub > 0; bucket-- ) { assert(bucket <= criticalBucketTemp); // no underflow on bucket uint256 principalTemp = Math.min256(principalToSub, principalForBucket[bucket]); if (principalTemp == 0) { continue; } uint256 availableTemp = MathHelpers.getPartialAmount( principalTemp, principalToSub, availableToAdd ); updateAvailable(bucket, availableTemp, true); updatePrincipal(bucket, principalTemp, false); principalToSub = principalToSub.sub(principalTemp); availableToAdd = availableToAdd.sub(availableTemp); criticalBucketTemp = bucket; } assert(principalToSub == 0); assert(availableToAdd == 0); setCriticalBucket(criticalBucketTemp); cachedRepaidAmount = newRepaidAmount; } /** * Updates the state variables when a position is increased. * * - Decreases the available amount in the lowest buckets with available token * - Increases the principal amount in those buckets * * @param principalAdded Amount of principal added to the position * @param lentAmount Amount of owedToken lent */ function accountForIncrease( uint256 principalAdded, uint256 lentAmount ) private { require( lentAmount <= availableTotal, "BucketLender#accountForIncrease: No lending not-accounted-for funds" ); uint256 principalToAdd = principalAdded; uint256 availableToSub = lentAmount; uint256 criticalBucketTemp; // loop over buckets in order starting from the critical bucket uint256 lastBucket = getCurrentBucket(); for ( uint256 bucket = criticalBucket; principalToAdd > 0; bucket++ ) { assert(bucket <= lastBucket); // should never go past the last bucket uint256 availableTemp = Math.min256(availableToSub, availableForBucket[bucket]); if (availableTemp == 0) { continue; } uint256 principalTemp = MathHelpers.getPartialAmount( availableTemp, availableToSub, principalToAdd ); updateAvailable(bucket, availableTemp, false); updatePrincipal(bucket, principalTemp, true); principalToAdd = principalToAdd.sub(principalTemp); availableToSub = availableToSub.sub(availableTemp); criticalBucketTemp = bucket; } assert(principalToAdd == 0); assert(availableToSub == 0); setCriticalBucket(criticalBucketTemp); } /** * Withdraw * * @param onBehalfOf The account for which to withdraw for * @param bucket The bucket number to withdraw from * @param maxWeight The maximum weight to withdraw * @param maxHeldToken The total amount of heldToken that has been force-recovered * @return 1) The number of owedTokens withdrawn * 2) The number of heldTokens withdrawn */ function withdrawSingleBucket( address onBehalfOf, uint256 bucket, uint256 maxWeight, uint256 maxHeldToken ) private returns (uint256, uint256) { // calculate the user's share uint256 bucketWeight = weightForBucket[bucket]; if (bucketWeight == 0) { return (0, 0); } uint256 userWeight = weightForBucketForAccount[bucket][onBehalfOf]; uint256 weightToWithdraw = Math.min256(maxWeight, userWeight); if (weightToWithdraw == 0) { return (0, 0); } // update state weightForBucket[bucket] = weightForBucket[bucket].sub(weightToWithdraw); weightForBucketForAccount[bucket][onBehalfOf] = userWeight.sub(weightToWithdraw); // calculate for owedToken uint256 owedTokenToWithdraw = withdrawOwedToken( bucket, weightToWithdraw, bucketWeight ); // calculate for heldToken uint256 heldTokenToWithdraw = withdrawHeldToken( bucket, weightToWithdraw, bucketWeight, maxHeldToken ); emit Withdraw( onBehalfOf, bucket, weightToWithdraw, owedTokenToWithdraw, heldTokenToWithdraw ); return (owedTokenToWithdraw, heldTokenToWithdraw); } /** * Helper function to withdraw earned owedToken from this contract. * * @param bucket The bucket number to withdraw from * @param userWeight The amount of weight the user is using to withdraw * @param bucketWeight The total weight of the bucket * @return The amount of owedToken being withdrawn */ function withdrawOwedToken( uint256 bucket, uint256 userWeight, uint256 bucketWeight ) private returns (uint256) { // amount to return for the bucket uint256 owedTokenToWithdraw = MathHelpers.getPartialAmount( userWeight, bucketWeight, availableForBucket[bucket].add(getBucketOwedAmount(bucket)) ); // check that there is enough token to give back require( owedTokenToWithdraw <= availableForBucket[bucket], "BucketLender#withdrawOwedToken: There must be enough available owedToken" ); // update amounts updateAvailable(bucket, owedTokenToWithdraw, false); return owedTokenToWithdraw; } /** * Helper function to withdraw heldToken from this contract. * * @param bucket The bucket number to withdraw from * @param userWeight The amount of weight the user is using to withdraw * @param bucketWeight The total weight of the bucket * @param maxHeldToken The total amount of heldToken available to withdraw * @return The amount of heldToken being withdrawn */ function withdrawHeldToken( uint256 bucket, uint256 userWeight, uint256 bucketWeight, uint256 maxHeldToken ) private returns (uint256) { if (maxHeldToken == 0) { return 0; } // user's principal for the bucket uint256 principalForBucketForAccount = MathHelpers.getPartialAmount( userWeight, bucketWeight, principalForBucket[bucket] ); uint256 heldTokenToWithdraw = MathHelpers.getPartialAmount( principalForBucketForAccount, principalTotal, maxHeldToken ); updatePrincipal(bucket, principalForBucketForAccount, false); return heldTokenToWithdraw; } // ============ Setter Functions ============ /** * Changes the critical bucket variable * * @param bucket The value to set criticalBucket to */ function setCriticalBucket( uint256 bucket ) private { // don't spend the gas to sstore unless we need to change the value if (criticalBucket == bucket) { return; } criticalBucket = bucket; } /** * Changes the available owedToken amount. This changes both the variable to track the total * amount as well as the variable to track a particular bucket. * * @param bucket The bucket number * @param amount The amount to change the available amount by * @param increase True if positive change, false if negative change */ function updateAvailable( uint256 bucket, uint256 amount, bool increase ) private { if (amount == 0) { return; } uint256 newTotal; uint256 newForBucket; if (increase) { newTotal = availableTotal.add(amount); newForBucket = availableForBucket[bucket].add(amount); emit AvailableIncreased(newTotal, bucket, newForBucket, amount); // solium-disable-line } else { newTotal = availableTotal.sub(amount); newForBucket = availableForBucket[bucket].sub(amount); emit AvailableDecreased(newTotal, bucket, newForBucket, amount); // solium-disable-line } availableTotal = newTotal; availableForBucket[bucket] = newForBucket; } /** * Changes the principal amount. This changes both the variable to track the total * amount as well as the variable to track a particular bucket. * * @param bucket The bucket number * @param amount The amount to change the principal amount by * @param increase True if positive change, false if negative change */ function updatePrincipal( uint256 bucket, uint256 amount, bool increase ) private { if (amount == 0) { return; } uint256 newTotal; uint256 newForBucket; if (increase) { newTotal = principalTotal.add(amount); newForBucket = principalForBucket[bucket].add(amount); emit PrincipalIncreased(newTotal, bucket, newForBucket, amount); // solium-disable-line } else { newTotal = principalTotal.sub(amount); newForBucket = principalForBucket[bucket].sub(amount); emit PrincipalDecreased(newTotal, bucket, newForBucket, amount); // solium-disable-line } principalTotal = newTotal; principalForBucket[bucket] = newForBucket; } } // File: contracts/margin/external/BucketLender/BucketLenderWithRecoveryDelay.sol /** * @title BucketLenderWithRecoveryDelay * @author dYdX * * Extension of BucketLender that delays the force-recovery time */ contract BucketLenderWithRecoveryDelay is BucketLender { // ============ State Variables ============ // number of seconds after position has closed that must be waited before force-recovering uint256 public RECOVERY_DELAY; // ============ Constructor ============ constructor( address margin, bytes32 positionId, address heldToken, address owedToken, uint32[7] parameters, address[] trustedMarginCallers, address[] trustedWithdrawers, uint256 recoveryDelay ) public BucketLender( margin, positionId, heldToken, owedToken, parameters, trustedMarginCallers, trustedWithdrawers ) { RECOVERY_DELAY = recoveryDelay; } // ============ Margin-Only State-Changing Functions ============ // Overrides the function in BucketLender function forceRecoverCollateralOnBehalfOf( address /* recoverer */, bytes32 positionId, address recipient ) external onlyMargin nonReentrant onlyPosition(positionId) returns (address) { MarginCommon.Position memory position = MarginHelper.getPosition(DYDX_MARGIN, positionId); uint256 positionEnd = uint256(position.startTimestamp).add(position.maxDuration); if (position.callTimestamp > 0) { uint256 marginCallEnd = uint256(position.callTimestamp).add(position.callTimeLimit); positionEnd = Math.min256(positionEnd, marginCallEnd); } require ( block.timestamp >= positionEnd.add(RECOVERY_DELAY), "BucketLenderWithRecoveryDelay#forceRecoverCollateralOnBehalfOf: Recovery too early" ); return forceRecoverCollateralInternal(recipient); } }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
[{"constant":true,"inputs":[],"name":"DYDX_MARGIN","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"BUCKET_TIME","outputs":[{"name":"","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"HELD_TOKEN","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"cachedRepaidAmount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"CALL_TIMELIMIT","outputs":[{"name":"","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"weightForBucket","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MIN_HELD_TOKEN_NUMERATOR","outputs":[{"name":"","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"","type":"address"},{"name":"positionId","type":"bytes32"},{"name":"recipient","type":"address"}],"name":"forceRecoverCollateralOnBehalfOf","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"buckets","type":"uint256[]"},{"name":"maxWeights","type":"uint256[]"},{"name":"onBehalfOf","type":"address"}],"name":"withdraw","outputs":[{"name":"","type":"uint256"},{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"bucket","type":"uint256"}],"name":"getBucketOwedAmount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"RECOVERY_DELAY","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"beneficiary","type":"address"},{"name":"amount","type":"uint256"}],"name":"deposit","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"wasForceClosed","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"principalForBucket","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"INTEREST_PERIOD","outputs":[{"name":"","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"positionId","type":"bytes32"}],"name":"receiveLoanOwnership","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"payer","type":"address"},{"name":"positionId","type":"bytes32"},{"name":"principalAdded","type":"uint256"},{"name":"lentAmount","type":"uint256"}],"name":"increaseLoanOnBehalfOf","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"INTEREST_RATE","outputs":[{"name":"","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"principalTotal","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"criticalBucket","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"renounceOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"token","type":"address"},{"name":"to","type":"address"}],"name":"withdrawExcessToken","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"MIN_HELD_TOKEN_DENOMINATOR","outputs":[{"name":"","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"canceler","type":"address"},{"name":"positionId","type":"bytes32"}],"name":"cancelMarginCallOnBehalfOf","outputs":[{"name":"","type":"address"}],"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_MARGIN_CALLERS","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"},{"name":"","type":"address"}],"name":"weightForBucketForAccount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"caller","type":"address"},{"name":"positionId","type":"bytes32"},{"name":"depositAmount","type":"uint256"}],"name":"marginCallOnBehalfOf","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"MAX_DURATION","outputs":[{"name":"","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"availableTotal","outputs":[{"name":"","type":"uint256"}],"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":"getCurrentBucket","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"POSITION_ID","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"rebalanceBuckets","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"addresses","type":"address[9]"},{"name":"values256","type":"uint256[7]"},{"name":"values32","type":"uint32[4]"},{"name":"positionId","type":"bytes32"},{"name":"signature","type":"bytes"}],"name":"verifyLoanOffering","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"OWED_TOKEN","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"availableForBucket","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"margin","type":"address"},{"name":"positionId","type":"bytes32"},{"name":"heldToken","type":"address"},{"name":"owedToken","type":"address"},{"name":"parameters","type":"uint32[7]"},{"name":"trustedMarginCallers","type":"address[]"},{"name":"trustedWithdrawers","type":"address[]"},{"name":"recoveryDelay","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"beneficiary","type":"address"},{"indexed":false,"name":"bucket","type":"uint256"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"weight","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"withdrawer","type":"address"},{"indexed":false,"name":"bucket","type":"uint256"},{"indexed":false,"name":"weight","type":"uint256"},{"indexed":false,"name":"owedTokenWithdrawn","type":"uint256"},{"indexed":false,"name":"heldTokenWithdrawn","type":"uint256"}],"name":"Withdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"principalTotal","type":"uint256"},{"indexed":false,"name":"bucketNumber","type":"uint256"},{"indexed":false,"name":"principalForBucket","type":"uint256"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"PrincipalIncreased","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"principalTotal","type":"uint256"},{"indexed":false,"name":"bucketNumber","type":"uint256"},{"indexed":false,"name":"principalForBucket","type":"uint256"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"PrincipalDecreased","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"availableTotal","type":"uint256"},{"indexed":false,"name":"bucketNumber","type":"uint256"},{"indexed":false,"name":"availableForBucket","type":"uint256"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"AvailableIncreased","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"availableTotal","type":"uint256"},{"indexed":false,"name":"bucketNumber","type":"uint256"},{"indexed":false,"name":"availableForBucket","type":"uint256"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"AvailableDecreased","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"}]
Deployed Bytecode
0x6080604052600436106101d75763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416630dda60cc81146101dc578063168ed05c1461021a5780631752c065146102485780632cff585d1461025d5780632ee29dc8146102845780633a4bb778146102995780633fda1a1d146102b15780634120bcec146102c6578063456a09c8146102fe57806345a2556c1461035c57806347b456521461037457806347e7ef24146103895780634c3efd99146103ba5780634f3e98ab146103e35780634f8894a4146103fb578063501b0b18146104105780635451fb26146104415780635b72a33a146104785780636613d8711461048d5780636fa6de59146104a2578063715018a6146104b75780637995ba90146104ce5780637ce875691461050257806383253cfa146105175780638da5cb5b14610548578063a753b1a81461055d578063abb0522d1461058b578063abdd0c44146105bc578063b1724b46146105f0578063bc9e0da314610605578063bd7456e31461061a578063cd4c4c0c14610648578063d7ac71ff1461065d578063d92ed48d14610672578063ea51c34314610687578063eada7fad146106b4578063f2fde38b146106c9578063f783e11e146106f7575b600080fd5b3480156101e857600080fd5b506101f161070f565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b34801561022657600080fd5b5061022f61072b565b6040805163ffffffff9092168252519081900360200190f35b34801561025457600080fd5b506101f161074f565b34801561026957600080fd5b5061027261076b565b60408051918252519081900360200190f35b34801561029057600080fd5b5061022f610771565b3480156102a557600080fd5b50610272600435610785565b3480156102bd57600080fd5b5061022f610797565b3480156102d257600080fd5b506101f173ffffffffffffffffffffffffffffffffffffffff60043581169060243590604435166107af565b34801561030a57600080fd5b50610343602460048035828101929082013591813591820191013573ffffffffffffffffffffffffffffffffffffffff60443516610afc565b6040805192835260208301919091528051918290030190f35b34801561036857600080fd5b50610272600435610f49565b34801561038057600080fd5b506102726110f4565b34801561039557600080fd5b5061027273ffffffffffffffffffffffffffffffffffffffff600435166024356110fa565b3480156103c657600080fd5b506103cf6117a7565b604080519115158252519081900360200190f35b3480156103ef57600080fd5b506102726004356117b0565b34801561040757600080fd5b5061022f6117c2565b34801561041c57600080fd5b506101f173ffffffffffffffffffffffffffffffffffffffff600435166024356117ee565b34801561044d57600080fd5b506101f173ffffffffffffffffffffffffffffffffffffffff60043516602435604435606435612189565b34801561048457600080fd5b5061022f61266f565b34801561049957600080fd5b50610272612697565b3480156104ae57600080fd5b5061027261269d565b3480156104c357600080fd5b506104cc6126a3565b005b3480156104da57600080fd5b5061027273ffffffffffffffffffffffffffffffffffffffff60043581169060243516612734565b34801561050e57600080fd5b5061022f6128dd565b34801561052357600080fd5b506101f173ffffffffffffffffffffffffffffffffffffffff600435166024356128f9565b34801561055457600080fd5b506101f1612ba1565b34801561056957600080fd5b506103cf73ffffffffffffffffffffffffffffffffffffffff60043516612bbd565b34801561059757600080fd5b5061027260043573ffffffffffffffffffffffffffffffffffffffff60243516612bd2565b3480156105c857600080fd5b506101f173ffffffffffffffffffffffffffffffffffffffff60043516602435604435612bef565b3480156105fc57600080fd5b5061022f612f0c565b34801561061157600080fd5b50610272612f18565b34801561062657600080fd5b506103cf73ffffffffffffffffffffffffffffffffffffffff60043516612f1e565b34801561065457600080fd5b50610272612f33565b34801561066957600080fd5b5061027261318c565b34801561067e57600080fd5b506104cc613192565b34801561069357600080fd5b506101f16004610124610204610284356102a4356024810190850135613218565b3480156106c057600080fd5b506101f16141c0565b3480156106d557600080fd5b506104cc73ffffffffffffffffffffffffffffffffffffffff600435166141dc565b34801561070357600080fd5b50610272600435614209565b60015473ffffffffffffffffffffffffffffffffffffffff1681565b600e5474010000000000000000000000000000000000000000900463ffffffff1681565b600d5473ffffffffffffffffffffffffffffffffffffffff1681565b600a5481565b600f54640100000000900463ffffffff1681565b60086020526000908152604090205481565b600f5468010000000000000000900463ffffffff1681565b60006107b9615561565b600154600090819073ffffffffffffffffffffffffffffffffffffffff16331461086a57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f4f6e6c794d617267696e236f6e6c794d617267696e3a204f6e6c79204d61726760448201527f696e2063616e2063616c6c000000000000000000000000000000000000000000606482015290519081900360840190fd5b6002805460010190819055600c548790811461090d57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f4275636b65744c656e646572236f6e6c79506f736974696f6e3a20496e636f7260448201527f7265637420706f736974696f6e00000000000000000000000000000000000000606482015290519081900360840190fd5b6001546109309073ffffffffffffffffffffffffffffffffffffffff168961421b565b945061095a85610120015163ffffffff168660e0015163ffffffff166143a990919063ffffffff16565b9350600085610100015163ffffffff1611156109a7576109988560c0015163ffffffff1686610100015163ffffffff166143a990919063ffffffff16565b92506109a484846143bc565b93505b6012546109bb90859063ffffffff6143a916565b421015610a7557604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152605260248201527f4275636b65744c656e646572576974685265636f7665727944656c617923666f60448201527f7263655265636f766572436f6c6c61746572616c4f6e426568616c664f663a2060648201527f5265636f7665727920746f6f206561726c790000000000000000000000000000608482015290519081900360a40190fd5b610a7e876143d4565b9550506002548114610af157604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f5265656e7472616e637920636865636b206661696c7572650000000000000000604482015290519081900360640190fd5b505050509392505050565b6000806000610b096155c5565b600280546001019081905560009081908190819081908d8c14610bd957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604160248201527f4275636b65744c656e6465722377697468647261773a20546865206c656e677460448201527f6873206f662074686520696e70757420617272617973206d757374206d61746360648201527f6800000000000000000000000000000000000000000000000000000000000000608482015290519081900360a40190fd5b73ffffffffffffffffffffffffffffffffffffffff8b163314610cc2573360009081526011602052604090205460ff161515610cc257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152605060248201527f4275636b65744c656e6465722377697468647261773a204f6e6c79207472757360448201527f7465642077697468647261776572732063616e207769746864726177206f6e2060648201527f626568616c66206f66206f746865727300000000000000000000000000000000608482015290519081900360a40190fd5b610cca6144e0565b600154600c54604080517f6d8ab12400000000000000000000000000000000000000000000000000000000815260048101929092525160009a5073ffffffffffffffffffffffffffffffffffffffff90921691636d8ab12491602480820192602092909190829003018186803b158015610d4357600080fd5b505afa158015610d57573d6000803e3d6000fd5b505050506040513d6020811015610d6d57600080fd5b50518015610d835750610d7e612f33565b600954145b15610d8e5760095497505b600b546000965060ff1615610dc957600d54610dc69073ffffffffffffffffffffffffffffffffffffffff163063ffffffff6145b916565b95505b600094505b8d851015610e60578e8e86818110610de257fe5b90506020020135935083600014158015610dfb57508784145b15610e0557610e55565b610e238b858f8f89818110610e1657fe5b9050602002013589614687565b9093509150610e41838860005b60200201519063ffffffff6143a916565b8752610e4f82886001610e30565b60208801525b600190940193610dce565b8651600e54610e899173ffffffffffffffffffffffffffffffffffffffff909116903390614822565b610ebb338860016020020151600d5473ffffffffffffffffffffffffffffffffffffffff16919063ffffffff61482216565b86516020880151600254919b5099508114610f3757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f5265656e7472616e637920636865636b206661696c7572650000000000000000604482015290519081900360640190fd5b50505050505050509550959350505050565b600154600c54604080517f640075f30000000000000000000000000000000000000000000000000000000081526004810192909252516000928392839273ffffffffffffffffffffffffffffffffffffffff9092169163640075f391602480820192602092909190829003018186803b158015610fc557600080fd5b505afa158015610fd9573d6000803e3d6000fd5b505050506040513d6020811015610fef57600080fd5b505115610fff57600092506110ed565b600084815260056020526040902054915081151561102057600092506110ed565b600154600c54600654604080517fa633f61f0000000000000000000000000000000000000000000000000000000081526004810193909352602483019190915263ffffffff421660448301525173ffffffffffffffffffffffffffffffffffffffff9092169163a633f61f91606480820192602092909190829003018186803b1580156110ac57600080fd5b505afa1580156110c0573d6000803e3d6000fd5b505050506040513d60208110156110d657600080fd5b50516006549091506110ea908390836149c1565b92505b5050919050565b60125481565b6002805460019081019182905554600c5460009273ffffffffffffffffffffffffffffffffffffffff9283169284918291829190891615156111c357604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603c60248201527f4275636b65744c656e646572236465706f7369743a2042656e6566696369617260448201527f792063616e6e6f7420626520746865207a65726f206164647265737300000000606482015290519081900360840190fd5b87151561125757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603060248201527f4275636b65744c656e646572236465706f7369743a2043616e6e6f742064657060448201527f6f736974207a65726f20746f6b656e7300000000000000000000000000000000606482015290519081900360840190fd5b604080517f640075f300000000000000000000000000000000000000000000000000000000815260048101879052905173ffffffffffffffffffffffffffffffffffffffff88169163640075f3916024808301926020929190829003018186803b1580156112c457600080fd5b505afa1580156112d8573d6000803e3d6000fd5b505050506040513d60208110156112ee57600080fd5b5051156113a857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604160248201527f4275636b65744c656e646572236465706f7369743a2043616e6e6f742064657060448201527f6f7369742061667465722074686520706f736974696f6e20697320636c6f736560648201527f6400000000000000000000000000000000000000000000000000000000000000608482015290519081900360a40190fd5b604080517f6e0cd41500000000000000000000000000000000000000000000000000000000815260048101879052905173ffffffffffffffffffffffffffffffffffffffff881691636e0cd415916024808301926020929190829003018186803b15801561141557600080fd5b505afa158015611429573d6000803e3d6000fd5b505050506040513d602081101561143f57600080fd5b5051156114f957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604860248201527f4275636b65744c656e646572236465706f7369743a2043616e6e6f742064657060448201527f6f736974207768696c652074686520706f736974696f6e206973206d6172676960648201527f6e2d63616c6c6564000000000000000000000000000000000000000000000000608482015290519081900360a40190fd5b6115016144e0565b600e5461152c9073ffffffffffffffffffffffffffffffffffffffff1633308b63ffffffff6149df16565b611534612f33565b935061155d61154285610f49565b6000868152600360205260409020549063ffffffff6143a916565b92506000915082151561157257879150611591565b60008481526008602052604090205461158e90899085906149c1565b91505b81151561162557604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603460248201527f4275636b65744c656e646572236465706f7369743a2043616e6e6f742064657060448201527f6f73697420666f72207a65726f20776569676874000000000000000000000000606482015290519081900360840190fd5b61163184896001614b54565b600084815260076020908152604080832073ffffffffffffffffffffffffffffffffffffffff8d168452909152902054611671908363ffffffff6143a916565b600085815260076020908152604080832073ffffffffffffffffffffffffffffffffffffffff8e1684528252808320939093558682526008905220546116bd908363ffffffff6143a916565b6000858152600860209081526040918290209290925580518681529182018a90528181018490525173ffffffffffffffffffffffffffffffffffffffff8b16917f36af321ec8d3c75236829c5317affd40ddb308863a1236d2d277a4025cccee1e919081900360600190a2839650600254811461179b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f5265656e7472616e637920636865636b206661696c7572650000000000000000604482015290519081900360640190fd5b50505050505092915050565b600b5460ff1681565b60056020526000908152604090205481565b600e547c0100000000000000000000000000000000000000000000000000000000900463ffffffff1681565b60006117f8615561565b600154600090819073ffffffffffffffffffffffffffffffffffffffff1633146118a957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f4f6e6c794d617267696e236f6e6c794d617267696e3a204f6e6c79204d61726760448201527f696e2063616e2063616c6c000000000000000000000000000000000000000000606482015290519081900360840190fd5b6002805460010190819055600c548690811461194c57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f4275636b65744c656e646572236f6e6c79506f736974696f6e3a20496e636f7260448201527f7265637420706f736974696f6e00000000000000000000000000000000000000606482015290519081900360840190fd5b600154600c546119729173ffffffffffffffffffffffffffffffffffffffff169061421b565b6080810151600f5491965094506119ae9063ffffffff6801000000000000000082048116916c01000000000000000000000000900416866149c1565b9250600084116119ba57fe5b600654156119c457fe5b73ffffffffffffffffffffffffffffffffffffffff88163014156119e457fe5b600e54855173ffffffffffffffffffffffffffffffffffffffff908116911614611a9557604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603e60248201527f4275636b65744c656e64657223726563656976654c6f616e4f776e657273686960448201527f703a20506f736974696f6e206f776564546f6b656e206d69736d617463680000606482015290519081900360840190fd5b600d54602086015173ffffffffffffffffffffffffffffffffffffffff908116911614611b4957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603e60248201527f4275636b65744c656e64657223726563656976654c6f616e4f776e657273686960448201527f703a20506f736974696f6e2068656c64546f6b656e206d69736d617463680000606482015290519081900360840190fd5b600f5461012086015163ffffffff908116911614611bee57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602481018290527f4275636b65744c656e64657223726563656976654c6f616e4f776e657273686960448201527f703a20506f736974696f6e206d61784475726174696f6e206d69736d61746368606482015290519081900360840190fd5b600f5460c086015163ffffffff9081166401000000009092041614611cc057604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604260248201527f4275636b65744c656e64657223726563656976654c6f616e4f776e657273686960448201527f703a20506f736974696f6e2063616c6c54696d654c696d6974206d69736d617460648201527f6368000000000000000000000000000000000000000000000000000000000000608482015290519081900360a40190fd5b600e5461014086015163ffffffff90811678010000000000000000000000000000000000000000000000009092041614611da757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604160248201527f4275636b65744c656e64657223726563656976654c6f616e4f776e657273686960448201527f703a20506f736974696f6e20696e74657265737452617465206d69736d61746360648201527f6800000000000000000000000000000000000000000000000000000000000000608482015290519081900360a40190fd5b600e5461016086015163ffffffff9081167c01000000000000000000000000000000000000000000000000000000009092041614611e9257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604360248201527f4275636b65744c656e64657223726563656976654c6f616e4f776e657273686960448201527f703a20506f736974696f6e20696e746572657374506572696f64206d69736d6160648201527f7463680000000000000000000000000000000000000000000000000000000000608482015290519081900360a40190fd5b600154600c54604080517f54d79868000000000000000000000000000000000000000000000000000000008152600481019290925251859273ffffffffffffffffffffffffffffffffffffffff16916354d79868916024808301926020929190829003018186803b158015611f0657600080fd5b505afa158015611f1a573d6000803e3d6000fd5b505050506040513d6020811015611f3057600080fd5b50511015611feb57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604560248201527f4275636b65744c656e64657223726563656976654c6f616e4f776e657273686960448201527f703a204e6f7420656e6f7567682068656c64546f6b656e20617320636f6c6c6160648201527f746572616c000000000000000000000000000000000000000000000000000000608482015290519081900360a40190fd5b600080527f05b8ccbb9d4d8fb16ea74ce3c29a41f1b461fbdaff4714a0d9a8eb05499746bc849055600684905560086020527f5eff886ea0ce6ca488a3d6e336d6c0f75f46d19b42c06ce5ee98e42c96d256c75461204f908563ffffffff6143a916565b7f5eff886ea0ce6ca488a3d6e336d6c0f75f46d19b42c06ce5ee98e42c96d256c75573ffffffffffffffffffffffffffffffffffffffff881660009081527f6d5257204ebe7d88fd91ae87941cb2dd9d8062b64ae5a2bd2d28ec40b9fbf6df60205260409020546120c6908563ffffffff6143a916565b73ffffffffffffffffffffffffffffffffffffffff891660009081527f6d5257204ebe7d88fd91ae87941cb2dd9d8062b64ae5a2bd2d28ec40b9fbf6df602052604090205530955050600254811461217f57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f5265656e7472616e637920636865636b206661696c7572650000000000000000604482015290519081900360640190fd5b5050505092915050565b60015460009081908190819073ffffffffffffffffffffffffffffffffffffffff16331461223e57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f4f6e6c794d617267696e236f6e6c794d617267696e3a204f6e6c79204d61726760448201527f696e2063616e2063616c6c000000000000000000000000000000000000000000606482015290519081900360840190fd5b6002805460010190819055600c54889081146122e157604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f4275636b65744c656e646572236f6e6c79506f736974696f6e3a20496e636f7260448201527f7265637420706f736974696f6e00000000000000000000000000000000000000606482015290519081900360840190fd5b60015473ffffffffffffffffffffffffffffffffffffffff90811695508a1630146123b957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152605060248201527f4275636b65744c656e64657223696e6372656173654c6f616e4f6e426568616c60448201527f664f663a204f74686572206c656e646572732063616e6e6f74206c656e64206660648201527f6f72207468697320706f736974696f6e00000000000000000000000000000000608482015290519081900360a40190fd5b600c54604080517f6e0cd41500000000000000000000000000000000000000000000000000000000815260048101929092525173ffffffffffffffffffffffffffffffffffffffff871691636e0cd415916024808301926020929190829003018186803b15801561242957600080fd5b505afa15801561243d573d6000803e3d6000fd5b505050506040513d602081101561245357600080fd5b50511561250d57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152605360248201527f4275636b65744c656e64657223696e6372656173654c6f616e4f6e426568616c60448201527f664f663a204e6f206c656e64696e67207768696c652074686520706f7369746960648201527f6f6e206973206d617267696e2d63616c6c656400000000000000000000000000608482015290519081900360a40190fd5b600c54604080517f0e8a4ac700000000000000000000000000000000000000000000000000000000815260048101929092525173ffffffffffffffffffffffffffffffffffffffff871691630e8a4ac7916024808301926020929190829003018186803b15801561257d57600080fd5b505afa158015612591573d6000803e3d6000fd5b505050506040513d60208110156125a757600080fd5b505193506125bb848963ffffffff614c8d16565b92506125da6125d584600654614c8d90919063ffffffff16565b614c9f565b6125e48888614e72565b60065484146125ef57fe5b30955050600254811461266357604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f5265656e7472616e637920636865636b206661696c7572650000000000000000604482015290519081900360640190fd5b50505050949350505050565b600e547801000000000000000000000000000000000000000000000000900463ffffffff1681565b60065481565b60095481565b60005473ffffffffffffffffffffffffffffffffffffffff1633146126c757600080fd5b6000805460405173ffffffffffffffffffffffffffffffffffffffff909116917ff8df31144d9c2f0f6b59d69b8b98abd5459d07f2742c4df920b25aae33c6482091a2600080547fffffffffffffffffffffffff0000000000000000000000000000000000000000169055565b60008054819073ffffffffffffffffffffffffffffffffffffffff16331461275b57600080fd5b6127636144e0565b61278973ffffffffffffffffffffffffffffffffffffffff85163063ffffffff6145b916565b600e5490915073ffffffffffffffffffffffffffffffffffffffff858116911614156127ca576004546127c390829063ffffffff614c8d16565b90506128ab565b600d5473ffffffffffffffffffffffffffffffffffffffff858116911614156128ab57600b5460ff16156128ab57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604f60248201527f4275636b65744c656e646572237769746864726177457863657373546f6b656e60448201527f3a2068656c64546f6b656e2063616e6e6f742062652077697468647261776e2060648201527f696620666f7263652d636c6f7365640000000000000000000000000000000000608482015290519081900360a40190fd5b6128d273ffffffffffffffffffffffffffffffffffffffff8516848363ffffffff61482216565b8091505b5092915050565b600f546c01000000000000000000000000900463ffffffff1681565b60015460009073ffffffffffffffffffffffffffffffffffffffff1633146129a857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f4f6e6c794d617267696e236f6e6c794d617267696e3a204f6e6c79204d61726760448201527f696e2063616e2063616c6c000000000000000000000000000000000000000000606482015290519081900360840190fd5b6002805460010190819055600c5483908114612a4b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f4275636b65744c656e646572236f6e6c79506f736974696f6e3a20496e636f7260448201527f7265637420706f736974696f6e00000000000000000000000000000000000000606482015290519081900360840190fd5b73ffffffffffffffffffffffffffffffffffffffff851660009081526010602052604090205460ff161515612b2d57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604d60248201527f4275636b65744c656e6465722363616e63656c4d617267696e43616c6c4f6e4260448201527f6568616c664f663a204d617267696e2d63616c6c2d63616e63656c6572206d7560648201527f7374206265207472757374656400000000000000000000000000000000000000608482015290519081900360a40190fd5b3092505060025481146128d657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f5265656e7472616e637920636865636b206661696c7572650000000000000000604482015290519081900360640190fd5b60005473ffffffffffffffffffffffffffffffffffffffff1681565b60106020526000908152604090205460ff1681565b600760209081526000928352604080842090915290825290205481565b60015460009073ffffffffffffffffffffffffffffffffffffffff163314612c9e57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f4f6e6c794d617267696e236f6e6c794d617267696e3a204f6e6c79204d61726760448201527f696e2063616e2063616c6c000000000000000000000000000000000000000000606482015290519081900360840190fd5b6002805460010190819055600c5484908114612d4157604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f4275636b65744c656e646572236f6e6c79506f736974696f6e3a20496e636f7260448201527f7265637420706f736974696f6e00000000000000000000000000000000000000606482015290519081900360840190fd5b73ffffffffffffffffffffffffffffffffffffffff861660009081526010602052604090205460ff161515612dfd57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602481018290527f4275636b65744c656e646572236d617267696e43616c6c4f6e426568616c664f60448201527f663a204d617267696e2d63616c6c6572206d7573742062652074727573746564606482015290519081900360840190fd5b8315612e9057604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603e60248201527f4275636b65744c656e646572236d617267696e43616c6c4f6e426568616c664f60448201527f663a204465706f73697420616d6f756e74206d757374206265207a65726f0000606482015290519081900360840190fd5b309250506002548114612f0457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f5265656e7472616e637920636865636b206661696c7572650000000000000000604482015290519081900360640190fd5b509392505050565b600f5463ffffffff1681565b60045481565b60116020526000908152604090205460ff1681565b600154600c54600e54604080517f640075f300000000000000000000000000000000000000000000000000000000815260048101849052905160009473ffffffffffffffffffffffffffffffffffffffff16939274010000000000000000000000000000000000000000900463ffffffff16918591859163640075f3916024808301926020929190829003018186803b158015612fcf57600080fd5b505afa158015612fe3573d6000803e3d6000fd5b505050506040513d6020811015612ff957600080fd5b50511561300257fe5b604080517f6d8ab12400000000000000000000000000000000000000000000000000000000815260048101859052905173ffffffffffffffffffffffffffffffffffffffff861691636d8ab124916024808301926020929190829003018186803b15801561306f57600080fd5b505afa158015613083573d6000803e3d6000fd5b505050506040513d602081101561309957600080fd5b505115156130aa5760009450613185565b604080517f26ad8d1b00000000000000000000000000000000000000000000000000000000815260048101859052905173ffffffffffffffffffffffffffffffffffffffff8616916326ad8d1b916024808301926020929190829003018186803b15801561311757600080fd5b505afa15801561312b573d6000803e3d6000fd5b505050506040513d602081101561314157600080fd5b505163ffffffff908116915061318290600190613176908581169061316a9042908790614c8d16565b9063ffffffff61500916565b9063ffffffff6143a916565b94505b5050505090565b600c5481565b60028054600101908190556131a56144e0565b600254811461321557604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f5265656e7472616e637920636865636b206661696c7572650000000000000000604482015290519081900360640190fd5b50565b60006132226155e0565b60015473ffffffffffffffffffffffffffffffffffffffff1633146132ce57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f4f6e6c794d617267696e236f6e6c794d617267696e3a204f6e6c79204d61726760448201527f696e2063616e2063616c6c000000000000000000000000000000000000000000606482015290519081900360840190fd5b6002805460010190819055600c548690811461337157604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f4275636b65744c656e646572236f6e6c79506f736974696f6e3a20496e636f7260448201527f7265637420706f736974696f6e00000000000000000000000000000000000000606482015290519081900360840190fd5b600154600c54604080517f6d8ab12400000000000000000000000000000000000000000000000000000000815260048101929092525173ffffffffffffffffffffffffffffffffffffffff90921691636d8ab12491602480820192602092909190829003018186803b1580156133e657600080fd5b505afa1580156133fa573d6000803e3d6000fd5b505050506040513d602081101561341057600080fd5b505115156134cb57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604d60248201527f4275636b65744c656e646572237665726966794c6f616e4f66666572696e673a60448201527f205468697320636f6e74726163742073686f756c64206e6f74206f70656e206160648201527f206e657720706f736974696f6e00000000000000000000000000000000000000608482015290519081900360a40190fd5b60408051610120818101909252613560918c90600990839083908082843750506040805160e081810190925293508e9250600791508390839080828437505060408051608081810190925293508e92506004915083908390808284375050604080516020601f8f018190048102820181019092528d815293508d92508c9150819084018382808284375061501e945050505050565b600e54815191945073ffffffffffffffffffffffffffffffffffffffff91821691161461358957fe5b600d54602084015173ffffffffffffffffffffffffffffffffffffffff9081169116146135b257fe5b604083015173ffffffffffffffffffffffffffffffffffffffff1630146135d557fe5b606083015173ffffffffffffffffffffffffffffffffffffffff1630146135f857fe5b608083015173ffffffffffffffffffffffffffffffffffffffff16156136a557604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603f60248201527f4275636b65744c656e646572237665726966794c6f616e4f66666572696e673a60448201527f206c6f616e4f66666572696e672e74616b6572206973206e6f6e2d7a65726f00606482015290519081900360840190fd5b60c083015173ffffffffffffffffffffffffffffffffffffffff161561377857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604660248201527f4275636b65744c656e646572237665726966794c6f616e4f66666572696e673a60448201527f206c6f616e4f66666572696e672e666565526563697069656e74206973206e6f60648201527f6e2d7a65726f0000000000000000000000000000000000000000000000000000608482015290519081900360a40190fd5b60a083015173ffffffffffffffffffffffffffffffffffffffff161561384b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604760248201527f4275636b65744c656e646572237665726966794c6f616e4f66666572696e673a60448201527f206c6f616e4f66666572696e672e706f736974696f6e4f776e6572206973206e60648201527f6f6e2d7a65726f00000000000000000000000000000000000000000000000000608482015290519081900360a40190fd5b60e083015173ffffffffffffffffffffffffffffffffffffffff161561391e57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604860248201527f4275636b65744c656e646572237665726966794c6f616e4f66666572696e673a60448201527f206c6f616e4f66666572696e672e6c656e646572466565546f6b656e2069732060648201527f6e6f6e2d7a65726f000000000000000000000000000000000000000000000000608482015290519081900360a40190fd5b61010083015173ffffffffffffffffffffffffffffffffffffffff16156139f257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604760248201527f4275636b65744c656e646572237665726966794c6f616e4f66666572696e673a60448201527f206c6f616e4f66666572696e672e74616b6572466565546f6b656e206973206e60648201527f6f6e2d7a65726f00000000000000000000000000000000000000000000000000608482015290519081900360a40190fd5b6139fa61505d565b6101208401515114613aba57604080517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526044602482018190527f4275636b65744c656e646572237665726966794c6f616e4f66666572696e673a908201527f206c6f616e4f66666572696e672e6d6178416d6f756e7420697320696e636f7260648201527f7265637400000000000000000000000000000000000000000000000000000000608482015290519081900360a40190fd5b6101208301516020015115613b7c57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604360248201527f4275636b65744c656e646572237665726966794c6f616e4f66666572696e673a60448201527f206c6f616e4f66666572696e672e6d696e416d6f756e74206973206e6f6e2d7a60648201527f65726f0000000000000000000000000000000000000000000000000000000000608482015290519081900360a40190fd5b6101208301516040015115613c3e57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604660248201527f4275636b65744c656e646572237665726966794c6f616e4f66666572696e673a60448201527f206c6f616e4f66666572696e672e6d696e48656c64546f6b656e206973206e6f60648201527f6e2d7a65726f0000000000000000000000000000000000000000000000000000608482015290519081900360a40190fd5b6101208301516060015115613d0057604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604360248201527f4275636b65744c656e646572237665726966794c6f616e4f66666572696e673a60448201527f206c6f616e4f66666572696e672e6c656e646572466565206973206e6f6e2d7a60648201527f65726f0000000000000000000000000000000000000000000000000000000000608482015290519081900360a40190fd5b6101208301516080015115613dc257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604260248201527f4275636b65744c656e646572237665726966794c6f616e4f66666572696e673a60448201527f206c6f616e4f66666572696e672e74616b6572466565206973206e6f6e2d7a6560648201527f726f000000000000000000000000000000000000000000000000000000000000608482015290519081900360a40190fd5b613dca61505d565b61014084015114613e8857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604160248201527f4275636b65744c656e646572237665726966794c6f616e4f66666572696e673a60448201527f2065787069726174696f6e54696d657374616d7020697320696e636f7272656360648201527f7400000000000000000000000000000000000000000000000000000000000000608482015290519081900360a40190fd5b6101a083015115613f2057604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603e60248201527f4275636b65744c656e646572237665726966794c6f616e4f66666572696e673a60448201527f206c6f616e4f66666572696e672e73616c74206973206e6f6e2d7a65726f0000606482015290519081900360840190fd5b613f28615081565b63ffffffff1683610160015163ffffffff16141515613ff457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604860248201527f4275636b65744c656e646572237665726966794c6f616e4f66666572696e673a60448201527f206c6f616e4f66666572696e672e63616c6c54696d656c696d6974206973206960648201527f6e636f7272656374000000000000000000000000000000000000000000000000608482015290519081900360a40190fd5b613ffc615081565b63ffffffff1683610180015163ffffffff161415156140c857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604660248201527f4275636b65744c656e646572237665726966794c6f616e4f66666572696e673a60448201527f206c6f616e4f66666572696e672e6d61784475726174696f6e20697320696e6360648201527f6f72726563740000000000000000000000000000000000000000000000000000608482015290519081900360a40190fd5b600e5461012084015160a0015163ffffffff9081167801000000000000000000000000000000000000000000000000909204161461410257fe5b600e5461012084015160c0015163ffffffff9081167c0100000000000000000000000000000000000000000000000000000000909204161461414057fe5b3093505060025481146141b457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f5265656e7472616e637920636865636b206661696c7572650000000000000000604482015290519081900360640190fd5b50509695505050505050565b600e5473ffffffffffffffffffffffffffffffffffffffff1681565b60005473ffffffffffffffffffffffffffffffffffffffff16331461420057600080fd5b61321581615089565b60036020526000908152604090205481565b614223615561565b61422b615675565b6142336155c5565b61423b615694565b604080517f1928b3cb00000000000000000000000000000000000000000000000000000000815260048101879052905173ffffffffffffffffffffffffffffffffffffffff881691631928b3cb91602480830192610180929190829003018186803b1580156142a957600080fd5b505afa1580156142bd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052506101808110156142e357600080fd5b506040805161018081018252825173ffffffffffffffffffffffffffffffffffffffff908116825260208085015182169083015283830151811692820192909252606080840151909216918101919091526080808301519082015260a0808301519082015260c08083015163ffffffff9081169183019190915260e0808401518216908301526101008084015182169083015261012080840151821690830152610140808401518216908301526101609283015116918101919091529695505050505050565b818101828110156143b657fe5b92915050565b60008183106143cb57816143cd565b825b9392505050565b600073ffffffffffffffffffffffffffffffffffffffff821630146144a657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604e60248201527f4275636b65744c656e64657223666f7263655265636f766572436f6c6c61746560448201527f72616c4f6e426568616c664f663a20526563697069656e74206d75737420626560648201527f207468697320636f6e7472616374000000000000000000000000000000000000608482015290519081900360a40190fd5b6144ae6144e0565b50600b80547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905530919050565b600b5460009060ff16156144f357613215565b600154600c54604080517f0e8a4ac700000000000000000000000000000000000000000000000000000000815260048101929092525173ffffffffffffffffffffffffffffffffffffffff90921691630e8a4ac791602480820192602092909190829003018186803b15801561456857600080fd5b505afa15801561457c573d6000803e3d6000fd5b505050506040513d602081101561459257600080fd5b50516006549091506145ae906125d5908363ffffffff614c8d16565b600654811461321557fe5b60008273ffffffffffffffffffffffffffffffffffffffff166370a08231836040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b15801561465457600080fd5b505afa158015614668573d6000803e3d6000fd5b505050506040513d602081101561467e57600080fd5b50519392505050565b6000838152600860205260408120548190818080808415156146af5760009650869550614814565b60008a815260076020908152604080832073ffffffffffffffffffffffffffffffffffffffff8f16845290915290205493506146eb89856143bc565b92508215156147005760009650869550614814565b60008a81526008602052604090205461471f908463ffffffff614c8d16565b60008b81526008602052604090205561473e848463ffffffff614c8d16565b600760008c815260200190815260200160002060008d73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555061479d8a8487615138565b91506147ab8a84878b615244565b604080518c81526020810186905280820185905260608101839052905191925073ffffffffffffffffffffffffffffffffffffffff8d16917fe08737ac48a1dab4b1a46c7dc9398bd5bfc6d7ad6fabb7cd8caa254de14def359181900360800190a28181965096505b505050505094509492505050565b3081158061485b57508273ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b15614865576149bb565b8373ffffffffffffffffffffffffffffffffffffffff1663a9059cbb84846040518363ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050600060405180830381600087803b15801561490857600080fd5b505af115801561491c573d6000803e3d6000fd5b5050505061492861529f565b15156149bb57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602760248201527f546f6b656e496e746572616374237472616e736665723a205472616e7366657260448201527f206661696c656400000000000000000000000000000000000000000000000000606482015290519081900360840190fd5b50505050565b60006149d78361316a868563ffffffff6152d316565b949350505050565b801580614a1757508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16145b15614a21576149bb565b604080517f23b872dd00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528481166024830152604482018490529151918616916323b872dd9160648082019260009290919082900301818387803b158015614aa157600080fd5b505af1158015614ab5573d6000803e3d6000fd5b50505050614ac161529f565b15156149bb57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602f60248201527f546f6b656e496e746572616374237472616e7366657246726f6d3a205472616e60448201527f7366657246726f6d206661696c65640000000000000000000000000000000000606482015290519081900360840190fd5b600080831515614b6357614c86565b8215614bee57600454614b7c908563ffffffff6143a916565b600086815260036020526040902054909250614b9e908563ffffffff6143a916565b60408051848152602081018890528082018390526060810187905290519192507fca6e648ae199ddefe590826a3fa1725537263712dc91de54fbe9265e8481bcb2919081900360800190a1614c6f565b600454614c01908563ffffffff614c8d16565b600086815260036020526040902054909250614c23908563ffffffff614c8d16565b60408051848152602081018890528082018390526060810187905290519192507f4f57952c4b465f787533606ee05864b1a98fc1d66e200cea50d06ffe2b911bc4919081900360800190a15b600482905560008581526003602052604090208190555b5050505050565b600082821115614c9957fe5b50900390565b6000808080808080871515614cb357614e68565b600154600c54604080517fbb39c85f00000000000000000000000000000000000000000000000000000000815260048101929092525173ffffffffffffffffffffffffffffffffffffffff9092169163bb39c85f91602480820192602092909190829003018186803b158015614d2857600080fd5b505afa158015614d3c573d6000803e3d6000fd5b505050506040513d6020811015614d5257600080fd5b5051600a549097508890614d6d90899063ffffffff614c8d16565b1015614d7557fe5b600a54889650614d8c90889063ffffffff614c8d16565b945060095493508392505b6000861115614e495783831115614daa57fe5b600083815260056020526040902054614dc49087906143bc565b9150811515614dd257614e1f565b614ddd8287876149c1565b9050614deb83826001614b54565b614df7838360006152fc565b614e07868363ffffffff614c8d16565b9550614e19858263ffffffff614c8d16565b94508293505b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90920191614d97565b8515614e5157fe5b8415614e5957fe5b614e6284615434565b600a8790555b5050505050505050565b60008060008060008060006004548811151515614f3c57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604360248201527f4275636b65744c656e646572236163636f756e74466f72496e6372656173653a60448201527f204e6f206c656e64696e67206e6f742d6163636f756e7465642d666f7220667560648201527f6e64730000000000000000000000000000000000000000000000000000000000608482015290519081900360a40190fd5b889650879550614f4a612f33565b935060095492505b6000871115614fe55783831115614f6557fe5b600083815260036020526040902054614f7f9087906143bc565b9150811515614f8d57614fda565b614f988287896149c1565b9050614fa683836000614b54565b614fb2838260016152fc565b614fc2878263ffffffff614c8d16565b9650614fd4868363ffffffff614c8d16565b95508294505b600190920191614f52565b8615614fed57fe5b8515614ff557fe5b614ffe85615434565b505050505050505050565b6000818381151561501657fe5b049392505050565b6150266155e0565b61502e6155e0565b6150388187615448565b61504281866154c5565b61504c8185615518565b6101e0810192909252509392505050565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90565b63ffffffff90565b73ffffffffffffffffffffffffffffffffffffffff811615156150ab57600080fd5b6000805460405173ffffffffffffffffffffffffffffffffffffffff808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b60008061516c848461516761514c89610f49565b60008a8152600360205260409020549063ffffffff6143a916565b6149c1565b60008681526003602052604090205490915081111561523857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604860248201527f4275636b65744c656e6465722377697468647261774f776564546f6b656e3a2060448201527f5468657265206d75737420626520656e6f75676820617661696c61626c65206f60648201527f776564546f6b656e000000000000000000000000000000000000000000000000608482015290519081900360a40190fd5b6149d785826000614b54565b600080808315156152585760009250615295565b60008781526005602052604090205461527490879087906149c1565b915061528382600654866149c1565b9050615291878360006152fc565b8092505b5050949350505050565b6000803d80156152b657602081146152bf576152cb565b600191506152cb565b60206000803e60005191505b501515919050565b60008215156152e4575060006143b6565b508181028183828115156152f457fe5b04146143b657fe5b60008083151561530b57614c86565b821561539657600654615324908563ffffffff6143a916565b600086815260056020526040902054909250615346908563ffffffff6143a916565b60408051848152602081018890528082018390526060810187905290519192507f58eef9f4832bc1f3b7dd73c274fd9d7c5fcbc32c73adc5b3020be3e03e7a139a919081900360800190a1615417565b6006546153a9908563ffffffff614c8d16565b6000868152600560205260409020549092506153cb908563ffffffff614c8d16565b60408051848152602081018890528082018390526060810187905290519192507f79a3ce3942e009744cca7547c0a7692ec2065556cc98bfd8f5cf9c367e23596b919081900360800190a15b600682905560008581526005602052604090208190555050505050565b80600954141561544357613215565b600955565b805173ffffffffffffffffffffffffffffffffffffffff908116835260208083015182169084015260408083015182169084015260608083015182169084015260808083015182169084015260a08083015182169084015260c08083015182169084015260e0808301518216908401526101009182015116910152565b805161012083018051919091526020808301518251909101526040808301518251909101526060808301518251909101526080808301519151015260a081015161014083015260c001516101a090910152565b805163ffffffff908116610160840152602082015181166101808401526040820151610120909301805193821660a0909401939093526060909101519151911660c09190910152565b6040805161018081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e0810182905261010081018290526101208101829052610140810182905261016081019190915290565b60408051808201825290600290829080388339509192915050565b604080516102c081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e0810182905261010081019190915261012081016156366156b3565b815260200160008152602001600063ffffffff168152602001600063ffffffff1681526020016000815260200160008019168152602001606081525090565b6080604051908101604052806004906020820280388339509192915050565b60c0604051908101604052806006906020820280388339509192915050565b60e0604051908101604052806000815260200160008152602001600081526020016000815260200160008152602001600063ffffffff168152602001600063ffffffff1681525090565b8273ffffffffffffffffffffffffffffffffffffffff1663095ea7b383836040518363ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050600060405180830381600087803b1580156157a057600080fd5b505af11580156157b4573d6000803e3d6000fd5b505050506157c061529f565b151561585357604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f546f6b656e496e74657261637423617070726f76653a20417070726f76616c2060448201527f6661696c65640000000000000000000000000000000000000000000000000000606482015290519081900360840190fd5b5050505600a165627a7a723058206288949fe60b060ddb4836c65acd20733f5ff76e22409cba3a39d28c523511ff0029
Swarm Source
bzzr://6288949fe60b060ddb4836c65acd20733f5ff76e22409cba3a39d28c523511ff
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 30 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|---|---|---|---|---|
ETH | 100.00% | $17.99 | 0.0841 | $1.51 |
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.