Transaction Hash:
Block:
10591104 at Aug-04-2020 04:17:01 AM +UTC
Transaction Fee:
0.0168834 ETH
$42.55
Gas Used:
422,085 Gas / 40 Gwei
Emitted Events:
121 |
TetherToken.Transfer( from=[Sender] 0x8d87e5f50934931041b6d434bbd0b90b1a6a11d0, to=[Receiver] PoolPawn, value=3560540600 )
|
122 |
PoolPawn.SupplyPawnLog( usr=[Sender] 0x8d87e5f50934931041b6d434bbd0b90b1a6a11d0, t=TetherToken, amount=3560540600, beg=0, end=3560540600 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x35F61DFB...D5B3a738d
Miner
| (firepool) | 8.044913978969054233 Eth | 8.061797378969054233 Eth | 0.0168834 | |
0x8D87E5f5...b1A6a11D0 |
0.060048880227975363 Eth
Nonce: 184
|
0.043165480227975363 Eth
Nonce: 185
| 0.0168834 | ||
0xdAC17F95...13D831ec7 | |||||
0xE48BC2Ba...3d35Ed09C | (ForTube: PoolPawn) |
Execution Trace
PoolPawn.supplyPawn( t=0xdAC17F958D2ee523a2206206994597C13D831ec7, amount=3560540600 )
0x9762e924b41396412faaf75aaca0ae7d95ff81b0.96e973f5( )
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.a74b0fff( )
0x7b244e6f93c94859f61f2b9f5e9744d223beb992.0f7c99fe( )
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.bc8bde7a( )
-
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.ea71c720( )
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.80ff93e4( )
0x70201d1ec58e7413ed429a682a9c3df66005695b.b0ed1d3a( )
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.80ff93e4( )
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.1fbf9524( )
-
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.80ff93e4( )
-
-
0x9762e924b41396412faaf75aaca0ae7d95ff81b0.e14768ed( )
-
TetherToken.balanceOf( who=0xE48BC2Ba0F2d2E140382d8B5C8f261a3d35Ed09C ) => ( 1554853632157 )
0x9762e924b41396412faaf75aaca0ae7d95ff81b0.a1d77967( )
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.a74b0fff( )
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.ea71c720( )
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.8c5494df( )
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.80ff93e4( )
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.a74b0fff( )
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.a74b0fff( )
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.ea71c720( )
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.80ff93e4( )
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.8c5494df( )
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.80ff93e4( )
-
0x9762e924b41396412faaf75aaca0ae7d95ff81b0.96e973f5( )
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.a74b0fff( )
0x7b244e6f93c94859f61f2b9f5e9744d223beb992.0f7c99fe( )
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.bc8bde7a( )
-
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.ea71c720( )
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.80ff93e4( )
0x70201d1ec58e7413ed429a682a9c3df66005695b.b0ed1d3a( )
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.80ff93e4( )
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.1fbf9524( )
-
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.80ff93e4( )
-
0x9762e924b41396412faaf75aaca0ae7d95ff81b0.4fc5f9cc( )
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.a74b0fff( )
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.ea71c720( )
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.8c5494df( )
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.80ff93e4( )
-
0x3db5ea898fffdf725d6b2853428147dd54e4b0e3.a74b0fff( )
-
-
TetherToken.allowance( _owner=0x8D87E5f50934931041B6D434BBd0B90b1A6a11D0, _spender=0xE48BC2Ba0F2d2E140382d8B5C8f261a3d35Ed09C ) => ( remaining=115792089237316195423570985008687907853269984665640564039457584007913129639935 )
-
TetherToken.balanceOf( who=0x8D87E5f50934931041B6D434BBd0B90b1A6a11D0 ) => ( 3560540650 )
-
TetherToken.transferFrom( _from=0x8D87E5f50934931041B6D434BBd0B90b1A6a11D0, _to=0xE48BC2Ba0F2d2E140382d8B5C8f261a3d35Ed09C, _value=3560540600 )
supplyPawn[PoolPawn (ln:918)]
pert[PoolPawn (ln:940)]
calculateBalance[PoolPawn (ln:947)]
valid_uint[PoolPawn (ln:948)]
add[PoolPawn (ln:953)]
sub[PoolPawn (ln:955)]
add[PoolPawn (ln:955)]
getCash[PoolPawn (ln:959)]
balanceOf[PoolPawn (ln:629)]
add[PoolPawn (ln:965)]
getDepositRate[PoolPawn (ln:969)]
valid_uint[PoolPawn (ln:970)]
valid_uint[PoolPawn (ln:971)]
pert[PoolPawn (ln:974)]
getLoanRate[PoolPawn (ln:980)]
valid_uint[PoolPawn (ln:981)]
valid_uint[PoolPawn (ln:982)]
add[PoolPawn (ln:996)]
sub[PoolPawn (ln:997)]
join[PoolPawn (ln:1001)]
safeTransferFrom[PoolPawn (ln:1003)]
allowance[PoolPawn (ln:1905)]
balanceOf[PoolPawn (ln:1911)]
safeTransferFrom[PoolPawn (ln:1923)]
transfer[PoolPawn (ln:1926)]
sub[PoolPawn (ln:1926)]
safeTransfer[PoolPawn (ln:1933)]
transfer[PoolPawn (ln:1943)]
sub[PoolPawn (ln:1943)]
transfer[PoolPawn (ln:1945)]
makePayable[PoolPawn (ln:1007)]
SupplyPawnLog[PoolPawn (ln:1012)]
File 1 of 2: PoolPawn
File 2 of 2: TetherToken
/** *Submitted for verification at Etherscan.io on 2020-08-01 */ /* * Copyright (c) The Force Protocol Development Team * Submitted for verification at Etherscan.io on 2019-09-17 */ pragma solidity 0.5.13; // pragma experimental ABIEncoderV2; contract ReentrancyGuard { bool private _notEntered; constructor() internal { // Storing an initial non-zero value makes deployment a bit more // expensive, but in exchange the refund on every call to nonReentrant // will be lower in amount. Since refunds are capped to a percetange of // the total transaction's gas, it is best to keep them low in cases // like this one, to increase the likelihood of the full refund coming // into effect. _notEntered = true; } /** * @dev Prevents a contract from calling itself, directly or indirectly. * Calling a `nonReentrant` function from another `nonReentrant` * function is not supported. It is possible to prevent this from happening * by making the `nonReentrant` function external, and make it call a * `private` function that does the actual work. */ modifier nonReentrant() { // On the first call to nonReentrant, _notEntered will be true require(_notEntered, "ReentrancyGuard: reentrant call"); // Any calls to nonReentrant after this point will fail _notEntered = false; _; // By storing the original value once again, a refund is triggered (see // https://eips.ethereum.org/EIPS/eip-2200) _notEntered = true; } } /** * Utility library of inline functions on addresses */ library Address { /** * 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 account address of the account to check * @return whether the target address is a contract */ function isContract(address account) 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. // solhint-disable-next-line no-inline-assembly assembly { size := extcodesize(account) } return size != 0; } } /** * @title SafeMath * @dev Unsigned math operations with safety checks that revert on error. */ library SafeMath { /** * @dev Multiplies two unsigned integers, reverts on overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { // Gas optimization: this is cheaper than requiring '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; } uint256 c = a * b; require(c / a == b, "uint mul overflow"); return c; } /** * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { // Solidity only automatically asserts when dividing by 0 require(b != 0, "uint div by zero"); uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } /** * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend). */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { require(b <= a, "uint sub overflow"); uint256 c = a - b; return c; } /** * @dev Adds two unsigned integers, reverts on overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a, "uint add overflow"); return c; } /** * @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo), * reverts when dividing by zero. */ function mod(uint256 a, uint256 b) internal pure returns (uint256) { require(b != 0, "uint mod by zero"); return a % b; } } /** * @title ERC20 interface * @dev see https://eips.ethereum.org/EIPS/eip-20 */ interface IERC20 { function transfer(address to, uint256 value) external returns (bool); function approve(address spender, uint256 value) external returns (bool); function transferFrom( address from, address to, uint256 value ) external returns (bool); function totalSupply() external view returns (uint256); function balanceOf(address who) external view returns (uint256); function allowance(address owner, address spender) external view returns (uint256); event Transfer(address indexed from, address indexed to, uint256 value); event Approval( address indexed owner, address indexed spender, uint256 value ); } /** * @title SafeERC20 * @dev Wrappers around ERC20 operations that throw on failure (when the token * contract returns false). Tokens that return no value (and instead revert or * throw on failure) are also supported, non-reverting calls are assumed to be * successful. * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract, * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. */ library SafeERC20 { using SafeMath for uint256; using Address for address; function safeTransfer( IERC20 token, address to, uint256 value ) internal { callOptionalReturn( token, abi.encodeWithSelector(token.transfer.selector, to, value) ); } function safeTransferFrom( IERC20 token, address from, address to, uint256 value ) internal { callOptionalReturn( token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value) ); } function safeApprove( IERC20 token, address spender, uint256 value ) internal { // safeApprove should only be called when setting an initial allowance, // or when resetting it to zero. To increase and decrease it, use // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' require((value == 0) || (token.allowance(address(this), spender) == 0)); callOptionalReturn( token, abi.encodeWithSelector(token.approve.selector, spender, value) ); } function safeIncreaseAllowance( IERC20 token, address spender, uint256 value ) internal { uint256 newAllowance = token.allowance(address(this), spender).add( value ); callOptionalReturn( token, abi.encodeWithSelector( token.approve.selector, spender, newAllowance ) ); } function safeDecreaseAllowance( IERC20 token, address spender, uint256 value ) internal { uint256 newAllowance = token.allowance(address(this), spender).sub( value ); callOptionalReturn( token, abi.encodeWithSelector( token.approve.selector, spender, newAllowance ) ); } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * @param token The token targeted by the call. * @param data The call data (encoded using abi.encode or one of its variants). */ function callOptionalReturn(IERC20 token, bytes memory data) private { // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // we're implementing it ourselves. // A Solidity high level call has three parts: // 1. The target address is checked to verify it contains contract code // 2. The call itself is made, and success asserted // 3. The return value is decoded, which in turn checks the size of the returned data. require(address(token).isContract()); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = address(token).call(data); require(success); if (returndata.length > 0) { // Return data is optional require(abi.decode(returndata, (bool))); } } } library addressMakePayable { function makePayable(address x) internal pure returns (address payable) { return address(uint160(x)); } } contract IOracle { function get(address token) external view returns (uint256, bool); } contract IInterestRateModel { function getLoanRate(int256 cash, int256 borrow) external view returns (int256 y); function getDepositRate(int256 cash, int256 borrow) external view returns (int256 y); function calculateBalance( int256 principal, int256 lastIndex, int256 newIndex ) external view returns (int256 y); function calculateInterestIndex( int256 Index, int256 r, int256 t ) external view returns (int256 y); function pert( int256 principal, int256 r, int256 t ) external view returns (int256 y); function getNewReserve( int256 oldReserve, int256 cash, int256 borrow, int256 blockDelta ) external view returns (int256 y); } contract PoolPawn is ReentrancyGuard { using SafeERC20 for IERC20; using SafeMath for uint256; using addressMakePayable for address; uint public constant int_max = 57896044618658097711785492504343953926634992332820282019728792003956564819967; address public admin; //the admin address address public proposedAdmin; //use pull over push pattern for admin // uint256 public constant interestRateDenomitor = 1e18; /** * @notice Container for borrow balance information * @member principal Total balance (with accrued interest), after applying the most recent balance-changing action * @member interestIndex Global borrowIndex as of the most recent balance-changing action */ // Balance struct struct Balance { uint256 principal; uint256 interestIndex; uint256 totalPnl; //total profit and loss } struct Market { uint256 accrualBlockNumber; int256 supplyRate; //存款利率 int256 demondRate; //借款利率 IInterestRateModel irm; uint256 totalSupply; uint256 supplyIndex; uint256 totalBorrows; uint256 borrowIndex; uint256 totalReserves; //系统盈利 uint256 minPledgeRate; //最小质押率 uint256 liquidationDiscount; //清算折扣 uint256 decimals; //币种的最小精度 } // Mappings of users' balance of each token mapping(address => mapping(address => Balance)) public accountSupplySnapshot; //tokenContract->address(usr)->SupplySnapshot mapping(address => mapping(address => Balance)) public accountBorrowSnapshot; //tokenContract->address(usr)->BorrowSnapshot struct LiquidateInfo { address targetAccount; //被清算账户 address liquidator; //清算人 address assetCollatera; //抵押物token地址 address assetBorrow; //债务token地址 uint256 liquidateAmount; //清算额度,抵押物 uint256 targetAmount; //目标额度, 债务 uint256 timestamp; } mapping(uint256 => LiquidateInfo) public liquidateInfoMap; uint256 public liquidateIndexes; function setLiquidateInfoMap( address _targetAccount, address _liquidator, address _assetCollatera, address _assetBorrow, uint256 x, uint256 y ) internal { LiquidateInfo memory newStruct = LiquidateInfo( _targetAccount, _liquidator, _assetCollatera, _assetBorrow, x, y, block.timestamp ); // Update liquidation record liquidateInfoMap[liquidateIndexes] = newStruct; liquidateIndexes++; } //user table mapping(uint256 => address) public accounts; mapping(address => uint256) public indexes; uint256 public index = 1; // Add new user function join(address who) internal { if (indexes[who] == 0) { accounts[index] = who; indexes[who] = index; ++index; } } event SupplyPawnLog( address usr, address t, uint256 amount, uint256 beg, uint256 end ); event WithdrawPawnLog( address usr, address t, uint256 amount, uint256 beg, uint256 end ); event BorrowPawnLog( address usr, address t, uint256 amount, uint256 beg, uint256 end ); event RepayFastBorrowLog( address usr, address t, uint256 amount, uint256 beg, uint256 end ); event LiquidateBorrowPawnLog( address usr, address tBorrow, uint256 endBorrow, address liquidator, address tCol, uint256 endCol ); event WithdrawPawnEquityLog( address t, uint256 equityAvailableBefore, uint256 amount, address owner ); mapping(address => Market) public mkts; //tokenAddress->Market address[] public collateralTokens; //抵押币种 IOracle public oracleInstance; uint256 public constant initialInterestIndex = 10**18; uint256 public constant defaultOriginationFee = 0; // default is zero bps uint256 public constant originationFee = 0; uint256 public constant ONE_ETH = 1 ether; // 增加抵押币种 function addCollateralMarket(address asset) public onlyAdmin { for (uint256 i = 0; i < collateralTokens.length; i++) { if (collateralTokens[i] == asset) { return; } } collateralTokens.push(asset); } function getCollateralMarketsLength() external view returns (uint256) { return collateralTokens.length; } function setInterestRateModel(address t, address irm) public onlyAdmin { mkts[t].irm = IInterestRateModel(irm); } function setMinPledgeRate(address t, uint256 minPledgeRate) external onlyAdmin { mkts[t].minPledgeRate = minPledgeRate; } function setLiquidationDiscount(address t, uint256 liquidationDiscount) external onlyAdmin { mkts[t].liquidationDiscount = liquidationDiscount; } function initCollateralMarket( address t, address irm, address oracle, uint256 decimals ) external onlyAdmin { if (address(oracleInstance) == address(0)) { setOracle(oracle); } Market memory m = mkts[t]; if (address(m.irm) == address(0)) { setInterestRateModel(t, irm); } addCollateralMarket(t); if (m.supplyIndex == 0) { m.supplyIndex = initialInterestIndex; } if (m.borrowIndex == 0) { m.borrowIndex = initialInterestIndex; } if (m.decimals == 0) { m.decimals = decimals; } mkts[t] = m; } constructor() public { admin = msg.sender; } //Starting from Solidity 0.4.0, contracts without a fallback function automatically revert payments, making the code above redundant. // function() external payable { // revert("fallback can't be payable"); // } modifier onlyAdmin() { require(msg.sender == admin, "only admin can do this!"); _; } function proposeNewAdmin(address admin_) external onlyAdmin { proposedAdmin = admin_; } function claimAdministration() external { require(msg.sender == proposedAdmin, "Not proposed admin."); admin = proposedAdmin; proposedAdmin = address(0); } // Set the initial timestamp of tokens function setInitialTimestamp(address token) external onlyAdmin { mkts[token].accrualBlockNumber = now; } function setDecimals(address t, uint256 decimals) external onlyAdmin { mkts[t].decimals = decimals; } function setOracle(address oracle) public onlyAdmin { oracleInstance = IOracle(oracle); } modifier existOracle() { require(address(oracleInstance) != address(0), "oracle not set"); _; } // Get price of oracle function fetchAssetPrice(address asset) public view returns (uint256, bool) { require(address(oracleInstance) != address(0), "oracle not set"); return oracleInstance.get(asset); } function valid_uint(uint r) internal view returns (int256) { require(r <= int_max, "uint r is not valid"); return int256(r); } // Get the price of assetAmount tokens function getPriceForAssetAmount(address asset, uint256 assetAmount) public view returns (uint256) { require(address(oracleInstance) != address(0), "oracle not set"); (uint256 price, bool ok) = fetchAssetPrice(asset); require(ok && price != 0, "invalid token price"); return price.mul(assetAmount).div(10**mkts[asset].decimals); } // Calc the token amount of usdValue function getAssetAmountForValue(address t, uint256 usdValue) public view returns (uint256) { require(address(oracleInstance) != address(0), "oracle not set"); (uint256 price, bool ok) = fetchAssetPrice(t); require(ok && price != 0, "invalid token price"); return usdValue.mul(10**mkts[t].decimals).div(price); } // Balance of "t" token of this contract function getCash(address t) public view returns (uint256) { // address(0) represents for eth if (t == address(0)) { return address(this).balance; } IERC20 token = IERC20(t); return token.balanceOf(address(this)); } // Balance of "asset" token of the "from" account function getBalanceOf(address asset, address from) internal view returns (uint256) { // address(0) represents for eth if (asset == address(0)) { return address(from).balance; } IERC20 token = IERC20(asset); return token.balanceOf(from); } // totalBorrows / totalSupply function loanToDepositRatio(address asset) public view returns (uint256) { uint256 loan = mkts[asset].totalBorrows; uint256 deposit = mkts[asset].totalSupply; // uint256 _1 = 1 ether; return loan.mul(ONE_ETH).div(deposit); } //m:market, a:account //i(n,m)=i(n-1,m)*(1+rm*t) //return P*(i(n,m)/i(n-1,a)) // Calc the balance of the "t" token of "acc" account function getSupplyBalance(address acc, address t) public view returns (uint256) { Balance storage supplyBalance = accountSupplySnapshot[t][acc]; int256 mSupplyIndex = mkts[t].irm.pert( int256(mkts[t].supplyIndex), mkts[t].supplyRate, int256(now - mkts[t].accrualBlockNumber) ); uint256 userSupplyCurrent = uint256( mkts[t].irm.calculateBalance( valid_uint(supplyBalance.principal), int256(supplyBalance.interestIndex), mSupplyIndex ) ); return userSupplyCurrent; } // Calc the actual USD value of "t" token of "who" account function getSupplyBalanceInUSD(address who, address t) public view returns (uint256) { return getPriceForAssetAmount(t, getSupplyBalance(who, t)); } // Calc the profit of "t" token of "acc" account function getSupplyPnl(address acc, address t) public view returns (uint256) { Balance storage supplyBalance = accountSupplySnapshot[t][acc]; int256 mSupplyIndex = mkts[t].irm.pert( int256(mkts[t].supplyIndex), mkts[t].supplyRate, int256(now - mkts[t].accrualBlockNumber) ); uint256 userSupplyCurrent = uint256( mkts[t].irm.calculateBalance( valid_uint(supplyBalance.principal), int256(supplyBalance.interestIndex), mSupplyIndex ) ); if (userSupplyCurrent > supplyBalance.principal) { return supplyBalance.totalPnl.add( userSupplyCurrent.sub(supplyBalance.principal) ); } else { return supplyBalance.totalPnl; } } // Calc the profit of "t" token of "acc" account in USD value function getSupplyPnlInUSD(address who, address t) public view returns (uint256) { return getPriceForAssetAmount(t, getSupplyPnl(who, t)); } // Gets USD all token values of supply profit function getTotalSupplyPnl(address who) public view returns (uint256 sumPnl) { uint256 length = collateralTokens.length; for (uint256 i = 0; i < length; i++) { uint256 pnl = getSupplyPnlInUSD(who, collateralTokens[i]); sumPnl = sumPnl.add(pnl); } } //m:market, a:account //i(n,m)=i(n-1,m)*(1+rm*t) //return P*(i(n,m)/i(n-1,a)) function getBorrowBalance(address acc, address t) public view returns (uint256) { Balance storage borrowBalance = accountBorrowSnapshot[t][acc]; int256 mBorrowIndex = mkts[t].irm.pert( int256(mkts[t].borrowIndex), mkts[t].demondRate, int256(now - mkts[t].accrualBlockNumber) ); uint256 userBorrowCurrent = uint256( mkts[t].irm.calculateBalance( valid_uint(borrowBalance.principal), int256(borrowBalance.interestIndex), mBorrowIndex ) ); return userBorrowCurrent; } function getBorrowBalanceInUSD(address who, address t) public view returns (uint256) { return getPriceForAssetAmount(t, getBorrowBalance(who, t)); } function getBorrowPnl(address acc, address t) public view returns (uint256) { Balance storage borrowBalance = accountBorrowSnapshot[t][acc]; int256 mBorrowIndex = mkts[t].irm.pert( int256(mkts[t].borrowIndex), mkts[t].demondRate, int256(now - mkts[t].accrualBlockNumber) ); uint256 userBorrowCurrent = uint256( mkts[t].irm.calculateBalance( valid_uint(borrowBalance.principal), int256(borrowBalance.interestIndex), mBorrowIndex ) ); return borrowBalance.totalPnl.add(userBorrowCurrent).sub( borrowBalance.principal ); } function getBorrowPnlInUSD(address who, address t) public view returns (uint256) { return getPriceForAssetAmount(t, getBorrowPnl(who, t)); } // Gets USD all token values of borrow lose function getTotalBorrowPnl(address who) public view returns (uint256 sumPnl) { uint256 length = collateralTokens.length; // uint sumPnl = 0; for (uint256 i = 0; i < length; i++) { uint256 pnl = getBorrowPnlInUSD(who, collateralTokens[i]); sumPnl = sumPnl.add(pnl); } // return sumPnl; } // BorrowBalance * collateral ratio // collateral ratio always great than 1 function getBorrowBalanceLeverage(address who, address t) public view returns (uint256) { return getBorrowBalanceInUSD(who, t).mul(mkts[t].minPledgeRate).div( ONE_ETH ); } // Gets USD token values of supply and borrow balances function calcAccountTokenValuesInternal(address who, address t) public view returns (uint256, uint256) { return (getSupplyBalanceInUSD(who, t), getBorrowBalanceInUSD(who, t)); } // Gets USD token values of supply and borrow balances function calcAccountTokenValuesLeverageInternal(address who, address t) public view returns (uint256, uint256) { return ( getSupplyBalanceInUSD(who, t), getBorrowBalanceLeverage(who, t) ); } // Gets USD all token values of supply and borrow balances function calcAccountAllTokenValuesLeverageInternal(address who) public view returns (uint256 sumSupplies, uint256 sumBorrowLeverage) { uint256 length = collateralTokens.length; for (uint256 i = 0; i < length; i++) { ( uint256 supplyValue, uint256 borrowsLeverage ) = calcAccountTokenValuesLeverageInternal( who, collateralTokens[i] ); sumSupplies = sumSupplies.add(supplyValue); sumBorrowLeverage = sumBorrowLeverage.add(borrowsLeverage); } } function calcAccountLiquidity(address who) public view returns (uint256, uint256) { uint256 sumSupplies; uint256 sumBorrowsLeverage; //sumBorrows* collateral ratio ( sumSupplies, sumBorrowsLeverage ) = calcAccountAllTokenValuesLeverageInternal(who); if (sumSupplies < sumBorrowsLeverage) { return (0, sumBorrowsLeverage.sub(sumSupplies)); //不足 } else { return (sumSupplies.sub(sumBorrowsLeverage), 0); //有余 } } struct SupplyIR { uint256 startingBalance; uint256 newSupplyIndex; uint256 userSupplyCurrent; uint256 userSupplyUpdated; uint256 newTotalSupply; uint256 currentCash; uint256 updatedCash; uint256 newBorrowIndex; } // deposit function supplyPawn(address t, uint256 amount) external payable nonReentrant { uint256 supplyAmount = amount; // address(0) represents for eth if (t == address(0)) { require(amount == msg.value, "Eth value should be equal to amount"); supplyAmount = msg.value; } else { require(msg.value == 0, "Eth should not be provided"); } SupplyIR memory tmp; Market storage market = mkts[t]; Balance storage supplyBalance = accountSupplySnapshot[t][msg.sender]; uint256 lastTimestamp = market.accrualBlockNumber; uint256 blockDelta = now - lastTimestamp; // Calc the supplyIndex of supplyBalance tmp.newSupplyIndex = uint256( market.irm.pert( int256(market.supplyIndex), market.supplyRate, int256(blockDelta) ) ); tmp.userSupplyCurrent = uint256( market.irm.calculateBalance( valid_uint(accountSupplySnapshot[t][msg.sender].principal), int256(supplyBalance.interestIndex), int256(tmp.newSupplyIndex) ) ); tmp.userSupplyUpdated = tmp.userSupplyCurrent.add(supplyAmount); // Update supply of the market tmp.newTotalSupply = market.totalSupply.add(tmp.userSupplyUpdated).sub( supplyBalance.principal ); tmp.currentCash = getCash(t); // address(0) represents for eth // We support both ERC20 and ETH. // Calc the new Balance of the contract if it's ERC20 // else(ETH) does nothing because the cash has been added(msg.value) when the contract is called tmp.updatedCash = t != address(0) ? tmp.currentCash.add(supplyAmount) : tmp.currentCash; // Update supplyRate and demondRate market.supplyRate = market.irm.getDepositRate( valid_uint(tmp.updatedCash), valid_uint(market.totalBorrows) ); tmp.newBorrowIndex = uint256( market.irm.pert( int256(market.borrowIndex), market.demondRate, int256(blockDelta) ) ); market.demondRate = market.irm.getLoanRate( valid_uint(tmp.updatedCash), valid_uint(market.totalBorrows) ); market.borrowIndex = tmp.newBorrowIndex; market.supplyIndex = tmp.newSupplyIndex; market.totalSupply = tmp.newTotalSupply; market.accrualBlockNumber = now; // mkts[t] = market; tmp.startingBalance = supplyBalance.principal; supplyBalance.principal = tmp.userSupplyUpdated; supplyBalance.interestIndex = tmp.newSupplyIndex; // Update total profit of user if (tmp.userSupplyCurrent > tmp.startingBalance) { supplyBalance.totalPnl = supplyBalance.totalPnl.add( tmp.userSupplyCurrent.sub(tmp.startingBalance) ); } join(msg.sender); safeTransferFrom( t, msg.sender, address(this), address(this).makePayable(), supplyAmount, 0 ); emit SupplyPawnLog( msg.sender, t, supplyAmount, tmp.startingBalance, tmp.userSupplyUpdated ); } struct WithdrawIR { uint256 withdrawAmount; uint256 startingBalance; uint256 newSupplyIndex; uint256 userSupplyCurrent; uint256 userSupplyUpdated; uint256 newTotalSupply; uint256 currentCash; uint256 updatedCash; uint256 newBorrowIndex; uint256 accountLiquidity; uint256 accountShortfall; uint256 usdValueOfWithdrawal; uint256 withdrawCapacity; } // withdraw function withdrawPawn(address t, uint256 requestedAmount) external nonReentrant { Market storage market = mkts[t]; Balance storage supplyBalance = accountSupplySnapshot[t][msg.sender]; WithdrawIR memory tmp; uint256 lastTimestamp = mkts[t].accrualBlockNumber; uint256 blockDelta = now - lastTimestamp; // Judge if the user has ability to withdraw (tmp.accountLiquidity, tmp.accountShortfall) = calcAccountLiquidity( msg.sender ); require( tmp.accountLiquidity != 0 && tmp.accountShortfall == 0, "can't withdraw, shortfall" ); // Update the balance of the user tmp.newSupplyIndex = uint256( mkts[t].irm.pert( int256(mkts[t].supplyIndex), mkts[t].supplyRate, int256(blockDelta) ) ); tmp.userSupplyCurrent = uint256( mkts[t].irm.calculateBalance( valid_uint(supplyBalance.principal), int256(supplyBalance.interestIndex), int256(tmp.newSupplyIndex) ) ); // Get the balance of this contract tmp.currentCash = getCash(t); // uint(-1) represents the user want to withdraw all of his supplies of the "t" token if (requestedAmount == uint256(-1)) { // max withdraw amount = min(his account liquidity, his balance) tmp.withdrawCapacity = getAssetAmountForValue( t, tmp.accountLiquidity ); tmp.withdrawAmount = min( min(tmp.withdrawCapacity, tmp.userSupplyCurrent), tmp.currentCash ); // tmp.withdrawAmount = min(tmp.withdrawAmount, tmp.currentCash); } else { tmp.withdrawAmount = requestedAmount; } // Update balance of this contract tmp.updatedCash = tmp.currentCash.sub(tmp.withdrawAmount); tmp.userSupplyUpdated = tmp.userSupplyCurrent.sub(tmp.withdrawAmount); // Get the amount of token to withdraw tmp.usdValueOfWithdrawal = getPriceForAssetAmount( t, tmp.withdrawAmount ); // require account liquidity is enough require( tmp.usdValueOfWithdrawal <= tmp.accountLiquidity, "account is short" ); // Update totalSupply of the market tmp.newTotalSupply = market.totalSupply.add(tmp.userSupplyUpdated).sub( supplyBalance.principal ); tmp.newSupplyIndex = uint256( mkts[t].irm.pert( int256(mkts[t].supplyIndex), mkts[t].supplyRate, int256(blockDelta) ) ); // Update loan to deposit rate market.supplyRate = mkts[t].irm.getDepositRate( valid_uint(tmp.updatedCash), valid_uint(market.totalBorrows) ); tmp.newBorrowIndex = uint256( mkts[t].irm.pert( int256(mkts[t].borrowIndex), mkts[t].demondRate, int256(blockDelta) ) ); market.demondRate = mkts[t].irm.getLoanRate( valid_uint(tmp.updatedCash), valid_uint(market.totalBorrows) ); market.accrualBlockNumber = now; market.totalSupply = tmp.newTotalSupply; market.supplyIndex = tmp.newSupplyIndex; market.borrowIndex = tmp.newBorrowIndex; // mkts[t] = market; tmp.startingBalance = supplyBalance.principal; supplyBalance.principal = tmp.userSupplyUpdated; supplyBalance.interestIndex = tmp.newSupplyIndex; safeTransferFrom( t, address(this).makePayable(), address(this), msg.sender, tmp.withdrawAmount, 0 ); emit WithdrawPawnLog( msg.sender, t, tmp.withdrawAmount, tmp.startingBalance, tmp.userSupplyUpdated ); } struct PayBorrowIR { uint256 newBorrowIndex; uint256 userBorrowCurrent; uint256 repayAmount; uint256 userBorrowUpdated; uint256 newTotalBorrows; uint256 currentCash; uint256 updatedCash; uint256 newSupplyIndex; uint256 startingBalance; } function min(uint256 a, uint256 b) internal pure returns (uint256) { if (a < b) { return a; } else { return b; } } //`(1 + originationFee) * borrowAmount` function calcBorrowAmountWithFee(uint256 borrowAmount) public pure returns (uint256) { return borrowAmount.mul((ONE_ETH).add(originationFee)).div(ONE_ETH); } // supply value * min pledge rate function getPriceForAssetAmountMulCollatRatio( address t, uint256 assetAmount ) public view returns (uint256) { return getPriceForAssetAmount(t, assetAmount) .mul(mkts[t].minPledgeRate) .div(ONE_ETH); } struct BorrowIR { uint256 newBorrowIndex; uint256 userBorrowCurrent; uint256 borrowAmountWithFee; uint256 userBorrowUpdated; uint256 newTotalBorrows; uint256 currentCash; uint256 updatedCash; uint256 newSupplyIndex; uint256 startingBalance; uint256 accountLiquidity; uint256 accountShortfall; uint256 usdValueOfBorrowAmountWithFee; } // borrow function BorrowPawn(address t, uint256 amount) external nonReentrant { BorrowIR memory tmp; Market storage market = mkts[t]; Balance storage borrowBalance = accountBorrowSnapshot[t][msg.sender]; uint256 lastTimestamp = mkts[t].accrualBlockNumber; uint256 blockDelta = now - lastTimestamp; // Calc borrow index tmp.newBorrowIndex = uint256( mkts[t].irm.pert( int256(mkts[t].borrowIndex), mkts[t].demondRate, int256(blockDelta) ) ); int256 lastIndex = int256(borrowBalance.interestIndex); tmp.userBorrowCurrent = uint256( mkts[t].irm.calculateBalance( valid_uint(borrowBalance.principal), lastIndex, int256(tmp.newBorrowIndex) ) ); // add borrow fee tmp.borrowAmountWithFee = calcBorrowAmountWithFee(amount); tmp.userBorrowUpdated = tmp.userBorrowCurrent.add( tmp.borrowAmountWithFee ); // Update market borrows tmp.newTotalBorrows = market .totalBorrows .add(tmp.userBorrowUpdated) .sub(borrowBalance.principal); // calc account liquidity (tmp.accountLiquidity, tmp.accountShortfall) = calcAccountLiquidity( msg.sender ); require( tmp.accountLiquidity != 0 && tmp.accountShortfall == 0, "can't borrow, shortfall" ); // require accountLiquitidy is enough tmp.usdValueOfBorrowAmountWithFee = getPriceForAssetAmountMulCollatRatio( t, tmp.borrowAmountWithFee ); require( tmp.usdValueOfBorrowAmountWithFee <= tmp.accountLiquidity, "can't borrow, without enough value" ); // Update the balance of this contract tmp.currentCash = getCash(t); tmp.updatedCash = tmp.currentCash.sub(amount); tmp.newSupplyIndex = uint256( mkts[t].irm.pert( int256(mkts[t].supplyIndex), mkts[t].supplyRate, int256(blockDelta) ) ); market.supplyRate = mkts[t].irm.getDepositRate( valid_uint(tmp.updatedCash), valid_uint(tmp.newTotalBorrows) ); market.demondRate = mkts[t].irm.getLoanRate( valid_uint(tmp.updatedCash), valid_uint(tmp.newTotalBorrows) ); market.accrualBlockNumber = now; market.totalBorrows = tmp.newTotalBorrows; market.supplyIndex = tmp.newSupplyIndex; market.borrowIndex = tmp.newBorrowIndex; // mkts[t] = market; tmp.startingBalance = borrowBalance.principal; borrowBalance.principal = tmp.userBorrowUpdated; borrowBalance.interestIndex = tmp.newBorrowIndex; // 更新币种的借币总额 // borrowBalance.totalPnl = borrowBalance.totalPnl.add(tmp.userBorrowCurrent.sub(tmp.startingBalance)); safeTransferFrom( t, address(this).makePayable(), address(this), msg.sender, amount, 0 ); emit BorrowPawnLog( msg.sender, t, amount, tmp.startingBalance, tmp.userBorrowUpdated ); // return 0; } // repay function repayFastBorrow(address t, uint256 amount) external payable nonReentrant { PayBorrowIR memory tmp; Market storage market = mkts[t]; Balance storage borrowBalance = accountBorrowSnapshot[t][msg.sender]; uint256 lastTimestamp = mkts[t].accrualBlockNumber; uint256 blockDelta = now - lastTimestamp; // calc the new borrow index tmp.newBorrowIndex = uint256( mkts[t].irm.pert( int256(mkts[t].borrowIndex), mkts[t].demondRate, int256(blockDelta) ) ); int256 lastIndex = int256(borrowBalance.interestIndex); tmp.userBorrowCurrent = uint256( mkts[t].irm.calculateBalance( valid_uint(borrowBalance.principal), lastIndex, int256(tmp.newBorrowIndex) ) ); // uint(-1) represents the user want to repay all of his borrows of "t" token if (amount == uint256(-1)) { // that is the minimum of (his balance, his borrows) tmp.repayAmount = min( getBalanceOf(t, msg.sender), tmp.userBorrowCurrent ); // address(0) represents for eth // if the user want to repay eth, he needs to repay a little more // because the exact amount will be calculated in the above // the extra eth will be returned in the safeTransferFrom if (t == address(0)) { require( msg.value > tmp.repayAmount, "Eth value should be larger than repayAmount" ); } } else { tmp.repayAmount = amount; if (t == address(0)) { require( msg.value == tmp.repayAmount, "Eth value should be equal to repayAmount" ); } } // calc the new borrows of user tmp.userBorrowUpdated = tmp.userBorrowCurrent.sub(tmp.repayAmount); // calc the new borrows of market tmp.newTotalBorrows = market .totalBorrows .add(tmp.userBorrowUpdated) .sub(borrowBalance.principal); tmp.currentCash = getCash(t); // address(0) represents for eth // just like the supplyPawn function, eth has been transfered. tmp.updatedCash = t != address(0) ? tmp.currentCash.add(tmp.repayAmount) : tmp.currentCash; tmp.newSupplyIndex = uint256( mkts[t].irm.pert( int256(mkts[t].supplyIndex), mkts[t].supplyRate, int256(blockDelta) ) ); // update deposit and loan rate market.supplyRate = mkts[t].irm.getDepositRate( valid_uint(tmp.updatedCash), valid_uint(tmp.newTotalBorrows) ); market.demondRate = mkts[t].irm.getLoanRate( valid_uint(tmp.updatedCash), valid_uint(tmp.newTotalBorrows) ); market.accrualBlockNumber = now; market.totalBorrows = tmp.newTotalBorrows; market.supplyIndex = tmp.newSupplyIndex; market.borrowIndex = tmp.newBorrowIndex; // mkts[t] = market; tmp.startingBalance = borrowBalance.principal; borrowBalance.principal = tmp.userBorrowUpdated; borrowBalance.interestIndex = tmp.newBorrowIndex; safeTransferFrom( t, msg.sender, address(this), address(this).makePayable(), tmp.repayAmount, msg.value ); emit RepayFastBorrowLog( msg.sender, t, tmp.repayAmount, tmp.startingBalance, tmp.userBorrowUpdated ); } // shortfall/(price*(minPledgeRate-liquidationDiscount-1)) // underwaterAsset is borrowAsset function calcDiscountedRepayToEvenAmount( address targetAccount, address underwaterAsset, uint256 underwaterAssetPrice ) public view returns (uint256) { (, uint256 shortfall) = calcAccountLiquidity(targetAccount); uint256 minPledgeRate = mkts[underwaterAsset].minPledgeRate; uint256 liquidationDiscount = mkts[underwaterAsset].liquidationDiscount; uint256 gap = minPledgeRate.sub(liquidationDiscount).sub(1 ether); return shortfall.mul(10**mkts[underwaterAsset].decimals).div( underwaterAssetPrice.mul(gap).div(ONE_ETH) ); //underwater asset amount } //[supplyCurrent / (1 + liquidationDiscount)] * (Oracle price for the collateral / Oracle price for the borrow) //[supplyCurrent * (Oracle price for the collateral)] / [ (1 + liquidationDiscount) * (Oracle price for the borrow) ] // amount of underwaterAsset to be repayed by liquidator, calculated by the amount of collateral asset function calcDiscountedBorrowDenominatedCollateral( address underwaterAsset, address collateralAsset, uint256 underwaterAssetPrice, uint256 collateralPrice, uint256 supplyCurrent_TargetCollateralAsset ) public view returns (uint256 res) { uint256 liquidationDiscount = mkts[underwaterAsset].liquidationDiscount; uint256 onePlusLiquidationDiscount = (ONE_ETH).add(liquidationDiscount); uint256 supplyCurrentTimesOracleCollateral = supplyCurrent_TargetCollateralAsset.mul(collateralPrice); res = supplyCurrentTimesOracleCollateral.div( onePlusLiquidationDiscount.mul(underwaterAssetPrice).div(ONE_ETH) ); //underwaterAsset amout res = res.mul(10**mkts[underwaterAsset].decimals); res = res.div(10**mkts[collateralAsset].decimals); } //closeBorrowAmount_TargetUnderwaterAsset * (1+liquidationDiscount) * priceBorrow/priceCollateral //underwaterAssetPrice * (1+liquidationDiscount) *closeBorrowAmount_TargetUnderwaterAsset) / collateralPrice //underwater is borrow // calc the amount of collateral asset bought by underwaterAsset(amount: closeBorrowAmount_TargetUnderwaterAsset) function calcAmountSeize( address underwaterAsset, address collateralAsset, uint256 underwaterAssetPrice, uint256 collateralPrice, uint256 closeBorrowAmount_TargetUnderwaterAsset ) public view returns (uint256 res) { uint256 liquidationDiscount = mkts[underwaterAsset].liquidationDiscount; uint256 onePlusLiquidationDiscount = (ONE_ETH).add(liquidationDiscount); res = underwaterAssetPrice.mul(onePlusLiquidationDiscount); res = res.mul(closeBorrowAmount_TargetUnderwaterAsset); res = res.div(collateralPrice); res = res.div(ONE_ETH); res = res.mul(10**mkts[collateralAsset].decimals); res = res.div(10**mkts[underwaterAsset].decimals); } struct LiquidateIR { // we need these addresses in the struct for use with `emitLiquidationEvent` to avoid `CompilerError: Stack too deep, try removing local variables.` address targetAccount; address assetBorrow; address liquidator; address assetCollateral; // borrow index and supply index are global to the asset, not specific to the user uint256 newBorrowIndex_UnderwaterAsset; uint256 newSupplyIndex_UnderwaterAsset; uint256 newBorrowIndex_CollateralAsset; uint256 newSupplyIndex_CollateralAsset; // the target borrow's full balance with accumulated interest uint256 currentBorrowBalance_TargetUnderwaterAsset; // currentBorrowBalance_TargetUnderwaterAsset minus whatever gets repaid as part of the liquidation uint256 updatedBorrowBalance_TargetUnderwaterAsset; uint256 newTotalBorrows_ProtocolUnderwaterAsset; uint256 startingBorrowBalance_TargetUnderwaterAsset; uint256 startingSupplyBalance_TargetCollateralAsset; uint256 startingSupplyBalance_LiquidatorCollateralAsset; uint256 currentSupplyBalance_TargetCollateralAsset; uint256 updatedSupplyBalance_TargetCollateralAsset; // If liquidator already has a balance of collateralAsset, we will accumulate // interest on it before transferring seized collateral from the borrower. uint256 currentSupplyBalance_LiquidatorCollateralAsset; // This will be the liquidator's accumulated balance of collateral asset before the liquidation (if any) // plus the amount seized from the borrower. uint256 updatedSupplyBalance_LiquidatorCollateralAsset; uint256 newTotalSupply_ProtocolCollateralAsset; uint256 currentCash_ProtocolUnderwaterAsset; uint256 updatedCash_ProtocolUnderwaterAsset; // cash does not change for collateral asset //mkts[t] uint256 newSupplyRateMantissa_ProtocolUnderwaterAsset; uint256 newBorrowRateMantissa_ProtocolUnderwaterAsset; // Why no variables for the interest rates for the collateral asset? // We don't need to calculate new rates for the collateral asset since neither cash nor borrows change uint256 discountedRepayToEvenAmount; //[supplyCurrent / (1 + liquidationDiscount)] * (Oracle price for the collateral / Oracle price for the borrow) (discountedBorrowDenominatedCollateral) uint256 discountedBorrowDenominatedCollateral; uint256 maxCloseableBorrowAmount_TargetUnderwaterAsset; uint256 closeBorrowAmount_TargetUnderwaterAsset; uint256 seizeSupplyAmount_TargetCollateralAsset; uint256 collateralPrice; uint256 underwaterAssetPrice; } // get the max amount to be liquidated function calcMaxLiquidateAmount( address targetAccount, address assetBorrow, address assetCollateral ) external view returns (uint256) { require(msg.sender != targetAccount, "can't self-liquidate"); LiquidateIR memory tmp; uint256 blockDelta = now - mkts[assetBorrow].accrualBlockNumber; Market storage borrowMarket = mkts[assetBorrow]; Market storage collateralMarket = mkts[assetCollateral]; Balance storage borrowBalance_TargeUnderwaterAsset = accountBorrowSnapshot[assetBorrow][targetAccount]; Balance storage supplyBalance_TargetCollateralAsset = accountSupplySnapshot[assetCollateral][targetAccount]; tmp.newSupplyIndex_CollateralAsset = uint256( collateralMarket.irm.pert( int256(collateralMarket.supplyIndex), collateralMarket.supplyRate, int256(blockDelta) ) ); tmp.newBorrowIndex_UnderwaterAsset = uint256( borrowMarket.irm.pert( int256(borrowMarket.borrowIndex), borrowMarket.demondRate, int256(blockDelta) ) ); tmp.currentSupplyBalance_TargetCollateralAsset = uint256( collateralMarket.irm.calculateBalance( valid_uint(supplyBalance_TargetCollateralAsset.principal), int256(supplyBalance_TargetCollateralAsset.interestIndex), int256(tmp.newSupplyIndex_CollateralAsset) ) ); tmp.currentBorrowBalance_TargetUnderwaterAsset = uint256( borrowMarket.irm.calculateBalance( valid_uint(borrowBalance_TargeUnderwaterAsset.principal), int256(borrowBalance_TargeUnderwaterAsset.interestIndex), int256(tmp.newBorrowIndex_UnderwaterAsset) ) ); bool ok; (tmp.collateralPrice, ok) = fetchAssetPrice(assetCollateral); require(ok, "fail to get collateralPrice"); (tmp.underwaterAssetPrice, ok) = fetchAssetPrice(assetBorrow); require(ok, "fail to get underwaterAssetPrice"); tmp.discountedBorrowDenominatedCollateral = calcDiscountedBorrowDenominatedCollateral( assetBorrow, assetCollateral, tmp.underwaterAssetPrice, tmp.collateralPrice, tmp.currentSupplyBalance_TargetCollateralAsset ); tmp.discountedRepayToEvenAmount = calcDiscountedRepayToEvenAmount( targetAccount, assetBorrow, tmp.underwaterAssetPrice ); tmp.maxCloseableBorrowAmount_TargetUnderwaterAsset = min( tmp.currentBorrowBalance_TargetUnderwaterAsset, tmp.discountedBorrowDenominatedCollateral ); tmp.maxCloseableBorrowAmount_TargetUnderwaterAsset = min( tmp.maxCloseableBorrowAmount_TargetUnderwaterAsset, tmp.discountedRepayToEvenAmount ); return tmp.maxCloseableBorrowAmount_TargetUnderwaterAsset; } // liquidate function liquidateBorrowPawn( address targetAccount, address assetBorrow, address assetCollateral, uint256 requestedAmountClose ) external payable nonReentrant { require(msg.sender != targetAccount, "can't self-liquidate"); LiquidateIR memory tmp; // Copy these addresses into the struct for use with `emitLiquidationEvent` // We'll use tmp.liquidator inside this function for clarity vs using msg.sender. tmp.targetAccount = targetAccount; tmp.assetBorrow = assetBorrow; tmp.liquidator = msg.sender; tmp.assetCollateral = assetCollateral; uint256 blockDelta = now - mkts[assetBorrow].accrualBlockNumber; Market storage borrowMarket = mkts[assetBorrow]; Market storage collateralMarket = mkts[assetCollateral]; // borrower's borrow balance and supply balance Balance storage borrowBalance_TargeUnderwaterAsset = accountBorrowSnapshot[assetBorrow][targetAccount]; Balance storage supplyBalance_TargetCollateralAsset = accountSupplySnapshot[assetCollateral][targetAccount]; // Liquidator might already hold some of the collateral asset Balance storage supplyBalance_LiquidatorCollateralAsset = accountSupplySnapshot[assetCollateral][tmp.liquidator]; bool ok; (tmp.collateralPrice, ok) = fetchAssetPrice(assetCollateral); require(ok, "fail to get collateralPrice"); (tmp.underwaterAssetPrice, ok) = fetchAssetPrice(assetBorrow); require(ok, "fail to get underwaterAssetPrice"); // calc borrower's borrow balance with the newest interest tmp.newBorrowIndex_UnderwaterAsset = uint256( borrowMarket.irm.pert( int256(borrowMarket.borrowIndex), borrowMarket.demondRate, int256(blockDelta) ) ); tmp.currentBorrowBalance_TargetUnderwaterAsset = uint256( borrowMarket.irm.calculateBalance( valid_uint(borrowBalance_TargeUnderwaterAsset.principal), int256(borrowBalance_TargeUnderwaterAsset.interestIndex), int256(tmp.newBorrowIndex_UnderwaterAsset) ) ); // calc borrower's supply balance with the newest interest tmp.newSupplyIndex_CollateralAsset = uint256( collateralMarket.irm.pert( int256(collateralMarket.supplyIndex), collateralMarket.supplyRate, int256(blockDelta) ) ); tmp.currentSupplyBalance_TargetCollateralAsset = uint256( collateralMarket.irm.calculateBalance( valid_uint(supplyBalance_TargetCollateralAsset.principal), int256(supplyBalance_TargetCollateralAsset.interestIndex), int256(tmp.newSupplyIndex_CollateralAsset) ) ); // calc liquidator's balance of the collateral asset tmp.currentSupplyBalance_LiquidatorCollateralAsset = uint256( collateralMarket.irm.calculateBalance( valid_uint(supplyBalance_LiquidatorCollateralAsset.principal), int256(supplyBalance_LiquidatorCollateralAsset.interestIndex), int256(tmp.newSupplyIndex_CollateralAsset) ) ); // update collateral asset of the market tmp.newTotalSupply_ProtocolCollateralAsset = collateralMarket .totalSupply .add(tmp.currentSupplyBalance_TargetCollateralAsset) .sub(supplyBalance_TargetCollateralAsset.principal); tmp.newTotalSupply_ProtocolCollateralAsset = tmp .newTotalSupply_ProtocolCollateralAsset .add(tmp.currentSupplyBalance_LiquidatorCollateralAsset) .sub(supplyBalance_LiquidatorCollateralAsset.principal); // calc the max amount to be liquidated tmp.discountedBorrowDenominatedCollateral = calcDiscountedBorrowDenominatedCollateral( assetBorrow, assetCollateral, tmp.underwaterAssetPrice, tmp.collateralPrice, tmp.currentSupplyBalance_TargetCollateralAsset ); tmp.discountedRepayToEvenAmount = calcDiscountedRepayToEvenAmount( targetAccount, assetBorrow, tmp.underwaterAssetPrice ); tmp.maxCloseableBorrowAmount_TargetUnderwaterAsset = min( min( tmp.currentBorrowBalance_TargetUnderwaterAsset, tmp.discountedBorrowDenominatedCollateral ), tmp.discountedRepayToEvenAmount ); // uint(-1) represents the user want to liquidate all if (requestedAmountClose == uint256(-1)) { tmp.closeBorrowAmount_TargetUnderwaterAsset = tmp .maxCloseableBorrowAmount_TargetUnderwaterAsset; } else { tmp.closeBorrowAmount_TargetUnderwaterAsset = requestedAmountClose; } require( tmp.closeBorrowAmount_TargetUnderwaterAsset <= tmp.maxCloseableBorrowAmount_TargetUnderwaterAsset, "closeBorrowAmount > maxCloseableBorrowAmount err" ); // address(0) represents for eth if (assetBorrow == address(0)) { // just the repay method, eth amount be transfered should be a litte more require( msg.value >= tmp.closeBorrowAmount_TargetUnderwaterAsset, "Not enough ETH" ); } else { // user needs to have enough balance require( getBalanceOf(assetBorrow, tmp.liquidator) >= tmp.closeBorrowAmount_TargetUnderwaterAsset, "insufficient balance" ); } // 计算清算人实际清算得到的此质押币数量 // The amount of collateral asset that liquidator can get tmp.seizeSupplyAmount_TargetCollateralAsset = calcAmountSeize( assetBorrow, assetCollateral, tmp.underwaterAssetPrice, tmp.collateralPrice, tmp.closeBorrowAmount_TargetUnderwaterAsset ); // 被清算人借币余额减少 // Update borrower's balance tmp.updatedBorrowBalance_TargetUnderwaterAsset = tmp .currentBorrowBalance_TargetUnderwaterAsset .sub(tmp.closeBorrowAmount_TargetUnderwaterAsset); // 更新借币市场总量 // Update borrow market tmp.newTotalBorrows_ProtocolUnderwaterAsset = borrowMarket .totalBorrows .add(tmp.updatedBorrowBalance_TargetUnderwaterAsset) .sub(borrowBalance_TargeUnderwaterAsset.principal); tmp.currentCash_ProtocolUnderwaterAsset = getCash(assetBorrow); // address(0) represents for eth // eth has been transfered when called tmp.updatedCash_ProtocolUnderwaterAsset = assetBorrow != address(0) ? tmp.currentCash_ProtocolUnderwaterAsset.add( tmp.closeBorrowAmount_TargetUnderwaterAsset ) : tmp.currentCash_ProtocolUnderwaterAsset; tmp.newSupplyIndex_UnderwaterAsset = uint256( borrowMarket.irm.pert( int256(borrowMarket.supplyIndex), borrowMarket.demondRate, int256(blockDelta) ) ); borrowMarket.supplyRate = borrowMarket.irm.getDepositRate( int256(tmp.updatedCash_ProtocolUnderwaterAsset), int256(tmp.newTotalBorrows_ProtocolUnderwaterAsset) ); borrowMarket.demondRate = borrowMarket.irm.getLoanRate( int256(tmp.updatedCash_ProtocolUnderwaterAsset), int256(tmp.newTotalBorrows_ProtocolUnderwaterAsset) ); tmp.newBorrowIndex_CollateralAsset = uint256( collateralMarket.irm.pert( int256(collateralMarket.supplyIndex), collateralMarket.demondRate, int256(blockDelta) ) ); // Update the balance of liquidator and borrower tmp.updatedSupplyBalance_TargetCollateralAsset = tmp .currentSupplyBalance_TargetCollateralAsset .sub(tmp.seizeSupplyAmount_TargetCollateralAsset); tmp.updatedSupplyBalance_LiquidatorCollateralAsset = tmp .currentSupplyBalance_LiquidatorCollateralAsset .add(tmp.seizeSupplyAmount_TargetCollateralAsset); borrowMarket.accrualBlockNumber = now; borrowMarket.totalBorrows = tmp.newTotalBorrows_ProtocolUnderwaterAsset; borrowMarket.supplyIndex = tmp.newSupplyIndex_UnderwaterAsset; borrowMarket.borrowIndex = tmp.newBorrowIndex_UnderwaterAsset; // mkts[assetBorrow] = borrowMarket; collateralMarket.accrualBlockNumber = now; collateralMarket.totalSupply = tmp .newTotalSupply_ProtocolCollateralAsset; collateralMarket.supplyIndex = tmp.newSupplyIndex_CollateralAsset; collateralMarket.borrowIndex = tmp.newBorrowIndex_CollateralAsset; // mkts[assetCollateral] = collateralMarket; tmp.startingBorrowBalance_TargetUnderwaterAsset = borrowBalance_TargeUnderwaterAsset .principal; // save for use in event borrowBalance_TargeUnderwaterAsset.principal = tmp .updatedBorrowBalance_TargetUnderwaterAsset; borrowBalance_TargeUnderwaterAsset.interestIndex = tmp .newBorrowIndex_UnderwaterAsset; tmp.startingSupplyBalance_TargetCollateralAsset = supplyBalance_TargetCollateralAsset .principal; // save for use in event supplyBalance_TargetCollateralAsset.principal = tmp .updatedSupplyBalance_TargetCollateralAsset; supplyBalance_TargetCollateralAsset.interestIndex = tmp .newSupplyIndex_CollateralAsset; tmp.startingSupplyBalance_LiquidatorCollateralAsset = supplyBalance_LiquidatorCollateralAsset .principal; // save for use in event supplyBalance_LiquidatorCollateralAsset.principal = tmp .updatedSupplyBalance_LiquidatorCollateralAsset; supplyBalance_LiquidatorCollateralAsset.interestIndex = tmp .newSupplyIndex_CollateralAsset; setLiquidateInfoMap( tmp.targetAccount, tmp.liquidator, tmp.assetCollateral, assetBorrow, tmp.seizeSupplyAmount_TargetCollateralAsset, tmp.closeBorrowAmount_TargetUnderwaterAsset ); safeTransferFrom( assetBorrow, tmp.liquidator.makePayable(), address(this), address(this).makePayable(), tmp.closeBorrowAmount_TargetUnderwaterAsset, msg.value ); emit LiquidateBorrowPawnLog( tmp.targetAccount, assetBorrow, tmp.updatedBorrowBalance_TargetUnderwaterAsset, tmp.liquidator, tmp.assetCollateral, tmp.updatedSupplyBalance_TargetCollateralAsset ); } function safeTransferFrom( address token, address payable owner, address spender, address payable to, uint256 amount, uint256 msgValue ) internal { require(amount != 0, "invalid safeTransferFrom amount"); if (owner != spender && token != address(0)) { // transfer in ERC20 require( IERC20(token).allowance(owner, spender) >= amount, "Insufficient allowance" ); } if (token != address(0)) { require( IERC20(token).balanceOf(owner) >= amount, "Insufficient balance" ); } else if (owner == spender) { // eth, owner == spender represents for transfer out, requires enough balance require(owner.balance >= amount, "Insufficient eth balance"); } if (owner != spender) { // transfer in if (token != address(0)) { // transferFrom ERC20 IERC20(token).safeTransferFrom(owner, to, amount); } else if (msgValue != 0 && msgValue > amount) { // return the extra eth to user owner.transfer(msgValue.sub(amount)); } // eth has been transfered when called using msg.value } else { // transfer out if (token != address(0)) { // ERC20 IERC20(token).safeTransfer(to, amount); } else { // 参数设置, msgValue 大于0,即还款或清算逻辑,实际还的钱大于需要还的钱,需要返回多余的钱 // msgValue 等于 0,借钱或取钱逻辑,直接转出 amount 数量的币 // msgValue greater than 0 represents for repay or liquidate, // which means we should give back the extra eth to user // msgValue equals to 0 represents for withdraw or borrow // just take the wanted money if (msgValue != 0 && msgValue > amount) { to.transfer(msgValue.sub(amount)); } else { to.transfer(amount); } } } } // admin transfers profit out function withdrawPawnEquity(address t, uint256 amount) external nonReentrant onlyAdmin { uint256 cash = getCash(t); uint256 equity = cash.add(mkts[t].totalBorrows).sub( mkts[t].totalSupply ); require(equity >= amount, "insufficient equity amount"); safeTransferFrom( t, address(this).makePayable(), address(this), admin.makePayable(), amount, 0 ); emit WithdrawPawnEquityLog(t, equity, amount, admin); } }
File 2 of 2: TetherToken
pragma solidity ^0.4.17; /** * @title SafeMath * @dev Math operations with safety checks that throw on error */ library SafeMath { function mul(uint256 a, uint256 b) internal pure returns (uint256) { if (a == 0) { return 0; } uint256 c = a * b; assert(c / a == b); return c; } 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 c; } function sub(uint256 a, uint256 b) internal pure returns (uint256) { assert(b <= a); return a - b; } function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; assert(c >= a); return c; } } /** * @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; /** * @dev The Ownable constructor sets the original `owner` of the contract to the sender * account. */ function Ownable() 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 transfer control of the contract to a newOwner. * @param newOwner The address to transfer ownership to. */ function transferOwnership(address newOwner) public onlyOwner { if (newOwner != address(0)) { owner = newOwner; } } } /** * @title ERC20Basic * @dev Simpler version of ERC20 interface * @dev see https://github.com/ethereum/EIPs/issues/20 */ contract ERC20Basic { uint public _totalSupply; function totalSupply() public constant returns (uint); function balanceOf(address who) public constant returns (uint); function transfer(address to, uint value) public; event Transfer(address indexed from, address indexed to, uint value); } /** * @title ERC20 interface * @dev see https://github.com/ethereum/EIPs/issues/20 */ contract ERC20 is ERC20Basic { function allowance(address owner, address spender) public constant returns (uint); function transferFrom(address from, address to, uint value) public; function approve(address spender, uint value) public; event Approval(address indexed owner, address indexed spender, uint value); } /** * @title Basic token * @dev Basic version of StandardToken, with no allowances. */ contract BasicToken is Ownable, ERC20Basic { using SafeMath for uint; mapping(address => uint) public balances; // additional variables for use if transaction fees ever became necessary uint public basisPointsRate = 0; uint public maximumFee = 0; /** * @dev Fix for the ERC20 short address attack. */ modifier onlyPayloadSize(uint size) { require(!(msg.data.length < size + 4)); _; } /** * @dev transfer token for a specified address * @param _to The address to transfer to. * @param _value The amount to be transferred. */ function transfer(address _to, uint _value) public onlyPayloadSize(2 * 32) { uint fee = (_value.mul(basisPointsRate)).div(10000); if (fee > maximumFee) { fee = maximumFee; } uint sendAmount = _value.sub(fee); balances[msg.sender] = balances[msg.sender].sub(_value); balances[_to] = balances[_to].add(sendAmount); if (fee > 0) { balances[owner] = balances[owner].add(fee); Transfer(msg.sender, owner, fee); } Transfer(msg.sender, _to, sendAmount); } /** * @dev Gets the balance of the specified address. * @param _owner The address to query the the balance of. * @return An uint representing the amount owned by the passed address. */ function balanceOf(address _owner) public constant returns (uint balance) { return balances[_owner]; } } /** * @title Standard ERC20 token * * @dev Implementation of the basic standard token. * @dev https://github.com/ethereum/EIPs/issues/20 * @dev Based oncode by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol */ contract StandardToken is BasicToken, ERC20 { mapping (address => mapping (address => uint)) public allowed; uint public constant MAX_UINT = 2**256 - 1; /** * @dev Transfer tokens from one address to another * @param _from address The address which you want to send tokens from * @param _to address The address which you want to transfer to * @param _value uint the amount of tokens to be transferred */ function transferFrom(address _from, address _to, uint _value) public onlyPayloadSize(3 * 32) { var _allowance = allowed[_from][msg.sender]; // Check is not needed because sub(_allowance, _value) will already throw if this condition is not met // if (_value > _allowance) throw; uint fee = (_value.mul(basisPointsRate)).div(10000); if (fee > maximumFee) { fee = maximumFee; } if (_allowance < MAX_UINT) { allowed[_from][msg.sender] = _allowance.sub(_value); } uint sendAmount = _value.sub(fee); balances[_from] = balances[_from].sub(_value); balances[_to] = balances[_to].add(sendAmount); if (fee > 0) { balances[owner] = balances[owner].add(fee); Transfer(_from, owner, fee); } Transfer(_from, _to, sendAmount); } /** * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. * @param _spender The address which will spend the funds. * @param _value The amount of tokens to be spent. */ function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) { // To change the approve amount you first have to reduce the addresses` // allowance to zero by calling `approve(_spender, 0)` if it is not // already 0 to mitigate the race condition described here: // https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 require(!((_value != 0) && (allowed[msg.sender][_spender] != 0))); allowed[msg.sender][_spender] = _value; Approval(msg.sender, _spender, _value); } /** * @dev Function to check the amount of tokens than an owner allowed to a spender. * @param _owner address The address which owns the funds. * @param _spender address The address which will spend the funds. * @return A uint specifying the amount of tokens still available for the spender. */ function allowance(address _owner, address _spender) public constant returns (uint remaining) { return allowed[_owner][_spender]; } } /** * @title Pausable * @dev Base contract which allows children to implement an emergency stop mechanism. */ contract Pausable is Ownable { event Pause(); event Unpause(); bool public paused = false; /** * @dev Modifier to make a function callable only when the contract is not paused. */ modifier whenNotPaused() { require(!paused); _; } /** * @dev Modifier to make a function callable only when the contract is paused. */ modifier whenPaused() { require(paused); _; } /** * @dev called by the owner to pause, triggers stopped state */ function pause() onlyOwner whenNotPaused public { paused = true; Pause(); } /** * @dev called by the owner to unpause, returns to normal state */ function unpause() onlyOwner whenPaused public { paused = false; Unpause(); } } contract BlackList is Ownable, BasicToken { /////// Getters to allow the same blacklist to be used also by other contracts (including upgraded Tether) /////// function getBlackListStatus(address _maker) external constant returns (bool) { return isBlackListed[_maker]; } function getOwner() external constant returns (address) { return owner; } mapping (address => bool) public isBlackListed; function addBlackList (address _evilUser) public onlyOwner { isBlackListed[_evilUser] = true; AddedBlackList(_evilUser); } function removeBlackList (address _clearedUser) public onlyOwner { isBlackListed[_clearedUser] = false; RemovedBlackList(_clearedUser); } function destroyBlackFunds (address _blackListedUser) public onlyOwner { require(isBlackListed[_blackListedUser]); uint dirtyFunds = balanceOf(_blackListedUser); balances[_blackListedUser] = 0; _totalSupply -= dirtyFunds; DestroyedBlackFunds(_blackListedUser, dirtyFunds); } event DestroyedBlackFunds(address _blackListedUser, uint _balance); event AddedBlackList(address _user); event RemovedBlackList(address _user); } contract UpgradedStandardToken is StandardToken{ // those methods are called by the legacy contract // and they must ensure msg.sender to be the contract address function transferByLegacy(address from, address to, uint value) public; function transferFromByLegacy(address sender, address from, address spender, uint value) public; function approveByLegacy(address from, address spender, uint value) public; } contract TetherToken is Pausable, StandardToken, BlackList { string public name; string public symbol; uint public decimals; address public upgradedAddress; bool public deprecated; // The contract can be initialized with a number of tokens // All the tokens are deposited to the owner address // // @param _balance Initial supply of the contract // @param _name Token Name // @param _symbol Token symbol // @param _decimals Token decimals function TetherToken(uint _initialSupply, string _name, string _symbol, uint _decimals) public { _totalSupply = _initialSupply; name = _name; symbol = _symbol; decimals = _decimals; balances[owner] = _initialSupply; deprecated = false; } // Forward ERC20 methods to upgraded contract if this one is deprecated function transfer(address _to, uint _value) public whenNotPaused { require(!isBlackListed[msg.sender]); if (deprecated) { return UpgradedStandardToken(upgradedAddress).transferByLegacy(msg.sender, _to, _value); } else { return super.transfer(_to, _value); } } // Forward ERC20 methods to upgraded contract if this one is deprecated function transferFrom(address _from, address _to, uint _value) public whenNotPaused { require(!isBlackListed[_from]); if (deprecated) { return UpgradedStandardToken(upgradedAddress).transferFromByLegacy(msg.sender, _from, _to, _value); } else { return super.transferFrom(_from, _to, _value); } } // Forward ERC20 methods to upgraded contract if this one is deprecated function balanceOf(address who) public constant returns (uint) { if (deprecated) { return UpgradedStandardToken(upgradedAddress).balanceOf(who); } else { return super.balanceOf(who); } } // Forward ERC20 methods to upgraded contract if this one is deprecated function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) { if (deprecated) { return UpgradedStandardToken(upgradedAddress).approveByLegacy(msg.sender, _spender, _value); } else { return super.approve(_spender, _value); } } // Forward ERC20 methods to upgraded contract if this one is deprecated function allowance(address _owner, address _spender) public constant returns (uint remaining) { if (deprecated) { return StandardToken(upgradedAddress).allowance(_owner, _spender); } else { return super.allowance(_owner, _spender); } } // deprecate current contract in favour of a new one function deprecate(address _upgradedAddress) public onlyOwner { deprecated = true; upgradedAddress = _upgradedAddress; Deprecate(_upgradedAddress); } // deprecate current contract if favour of a new one function totalSupply() public constant returns (uint) { if (deprecated) { return StandardToken(upgradedAddress).totalSupply(); } else { return _totalSupply; } } // Issue a new amount of tokens // these tokens are deposited into the owner address // // @param _amount Number of tokens to be issued function issue(uint amount) public onlyOwner { require(_totalSupply + amount > _totalSupply); require(balances[owner] + amount > balances[owner]); balances[owner] += amount; _totalSupply += amount; Issue(amount); } // Redeem tokens. // These tokens are withdrawn from the owner address // if the balance must be enough to cover the redeem // or the call will fail. // @param _amount Number of tokens to be issued function redeem(uint amount) public onlyOwner { require(_totalSupply >= amount); require(balances[owner] >= amount); _totalSupply -= amount; balances[owner] -= amount; Redeem(amount); } function setParams(uint newBasisPoints, uint newMaxFee) public onlyOwner { // Ensure transparency by hardcoding limit beyond which fees can never be added require(newBasisPoints < 20); require(newMaxFee < 50); basisPointsRate = newBasisPoints; maximumFee = newMaxFee.mul(10**decimals); Params(basisPointsRate, maximumFee); } // Called when new token are issued event Issue(uint amount); // Called when tokens are redeemed event Redeem(uint amount); // Called when contract is deprecated event Deprecate(address newAddress); // Called if contract ever adds fees event Params(uint feeBasisPoints, uint maxFee); }