pragma solidity 0.4.24; import "contracts/StockPriceFetcher.sol"; import "contracts/imported/SafeMath.sol"; import "contracts/imported/usingOraclize.sol"; contract Levereth is StockPriceFetcher { using SafeMath for uint256; modifier onlyManager() { require(msg.sender == manager, "Only the manager can call this method"); _; } event Transfer( address _from, address _to, uint256 _stockId ); event TransferFail( address _from, address _to, uint256 _stockId, string _reason ); event Approval( address _owner, address _approved, uint256 _stockId ); struct LeverStock { uint32 lastStockSalePriceCents; uint16 currentMultiplierBasisPoints; bool isForSale; bool isLong; string fetchPriceUrl; uint purchasePriceWei; } address public manager; LeverStock[] public stocks; // A mapping of token owners mapping(uint256 => address) internal owners; // A mapping storing the balance of each address mapping(address => uint256) internal balances; // A mapping of the "_approved" address for each token mapping (uint256 => address) internal allowance; // A nested mapping for managing "operators" mapping (address => mapping (address => bool)) internal authorized; // Total number of stocks for sale uint internal numStocksForSale; uint16 private constant INITIAL_MULTIPLIER_BASIS_POINTS = // Allow initial transfer to increment multiplier to starting value 100 - MULTIPLIER_INCREASE_PER_TRANSFER_BASIS_POINTS; uint16 internal constant MULTIPLIER_INCREASE_PER_TRANSFER_BASIS_POINTS = 10; int internal constant BASIS_POINTS_PER_PERCENT = 100; int internal constant PRECISION_PER_PERCENT = 10000000000000000000; int internal constant DECIMAL_TO_PRECISION = 100 * PRECISION_PER_PERCENT; // Don't let stocks go above 5x leverage. uint16 private constant MAXIMUM_MULTIPLIER_BASIS_POINTS = 500; uint private constant CONTRACT_SALE_FEE_BASIS_POINTS = 100; uint8 private constant MAX_TICKER_LENGTH_BYTES = 5; constructor() public { manager = msg.sender; /** * We don't want any valid token to have stockId 0, since that is the * default value of uint, which could cause issues. Fill this position * with unowned trash. */ LeverStock memory stock = LeverStock({ fetchPriceUrl: "", lastStockSalePriceCents: 0, purchasePriceWei: 0, currentMultiplierBasisPoints: 0, isForSale: false, isLong: true }); .push(stock); } function getContractBalance() external view onlyManager returns (uint) { return address(this).balance; } function withdraw(uint amountWei) external onlyManager returns (bool) { require(amountWei <= address(this).balance, "Cannot overdraw the contract"); manager.transfer(amountWei); return true; } function issueLeverStock(string _ticker, bool _isLong) external payable { require(bytes(_ticker).length <= MAX_TICKER_LENGTH_BYTES, "Invalid stock ticker"); address to = msg.sender; uint payedPriceWei = msg.value; uint stockId = stocks.push(LeverStock({ fetchPriceUrl: strConcat( "https://api.iextrading.com/1.0/stock/", _ticker, "/price" ), lastStockSalePriceCents: 0, purchasePriceWei: 0, currentMultiplierBasisPoints: INITIAL_MULTIPLIER_BASIS_POINTS, isForSale: false, isLong: _isLong })) - 1; balances[manager]++; owners[stockId] = manager; startTransferFrom(manager, to, stockId, payedPriceWei); } function buy(uint _stockId) external payable { LeverStock storage stock = stocks[_stockId]; address buyer = msg.sender; uint payedPriceWei = msg.value; require(isValidStockId(_stockId)); require(stock.isForSale); require(buyer != 0x0); startTransferFrom(ownerOf(_stockId), buyer, _stockId, payedPriceWei); } function setForSale(bool forSale, uint _stockId) public { address owner = ownerOf(_stockId); LeverStock storage stock = stocks[_stockId]; require(isValidStockId(_stockId), "Not a valid stockId"); require( owner == msg.sender || authorized[owner][msg.sender] || oraclize_cbAddress() == msg.sender, "Request is not from the owner of the stock" ); if (forSale) { require(!stock.isForSale, "Stock already for sale"); allowance[_stockId] = address(this); stock.isForSale = true; numStocksForSale++; emit Approval(owner, address(this), _stockId); } else { require(stock.isForSale, "Stock not for sale"); delete allowance[_stockId]; stock.isForSale = false; numStocksForSale--; } } function startTransferFrom( address _from, address _to, uint _stockId, uint _payedPriceWei ) internal { require(msg.value >= 2 * ORACLE_PRICE_WEI); LeverStock memory stock = stocks[_stockId]; string memory fetchPriceUrl = stock.fetchPriceUrl; fetchPrice(_from, _to, _stockId, fetchPriceUrl, _payedPriceWei); } function finishTransferFrom( address _from, address _to, uint _stockId, uint _payedPriceWei, uint32 _stockPriceCents ) internal { // Subtract out the oracle price uint paymentWei = _payedPriceWei.sub(ORACLE_PRICE_WEI); if (_stockPriceCents == 0) { /** * Stock price will be zero when the result of oracle is an invalid number * For example, when the stock ticker is invalid. Refund the user in this case. */ _to.transfer(paymentWei); emit TransferFail(_from, _to, _stockId, "Invalid oracle result"); return; } LeverStock storage stock = stocks[_stockId]; /** * Confirm that the payed price is sufficient given the stock's new price. * If not, we refund the original address. * Stocks that are newly issued, i.e. have a lastSalePriceCents of 0, simply set their * price to the original payment, minus the oracle price burned. */ uint newPriceWei; if (stock.lastStockSalePriceCents > 0) { newPriceWei = calculateSalePriceWei( stock.purchasePriceWei, stock.lastStockSalePriceCents, _stockPriceCents, stock.currentMultiplierBasisPoints, stock.isLong ); } else { newPriceWei = paymentWei; } if (paymentWei < newPriceWei) { _to.transfer(paymentWei); emit TransferFail(_from, _to, _stockId, "Insufficient payment"); } else if (newPriceWei == 0) { _to.transfer(paymentWei); emit TransferFail(_from, _to, _stockId, "Price overflow"); } else { if (stock.isForSale) { setForSale(false, _stockId); } transferOwnership(_from, _to, _stockId, _stockPriceCents, newPriceWei); payAllParties(_from, _to, newPriceWei, paymentWei); } } function transferOwnership( address _from, address _to, uint _stockId, uint32 _stockPriceCents, uint _purchasePriceWei ) private { LeverStock storage stock = stocks[_stockId]; owners[_stockId] = _to; // an invariant balance[_from] > 0 holds balances[_from]--; balances[_to]++; if (allowance[_stockId] != 0x0) { delete allowance[_stockId]; } stock.lastStockSalePriceCents = _stockPriceCents; stock.purchasePriceWei = _purchasePriceWei; uint16 newMultiplierBasisPoints = stock.currentMultiplierBasisPoints + MULTIPLIER_INCREASE_PER_TRANSFER_BASIS_POINTS; if (newMultiplierBasisPoints <= MAXIMUM_MULTIPLIER_BASIS_POINTS) { stock.currentMultiplierBasisPoints = newMultiplierBasisPoints; } emit Transfer(_from, _to, _stockId); } function payAllParties( address _from, address _to, uint _purchasePriceWei, uint _paymentWei ) private { // Invariant holds that payment >= purchase price uint overPaymentWei = _paymentWei.sub(_purchasePriceWei); uint contractFee = _purchasePriceWei.div(CONTRACT_SALE_FEE_BASIS_POINTS); uint sellerPayment = _purchasePriceWei.sub(contractFee); _to.transfer(overPaymentWei); // refund the buyer any payment over the sale price _from.transfer(sellerPayment); // pay the seller; } /** * Returns the sale price of a stock, in wei * Returns 0 if the price change results in an overflow or underflow */ function calculateSalePriceWei( uint _purchasePriceWei, uint32 _lastStockPriceCents, uint32 _currentStockPriceCents, uint16 _multiplierBasisPoints, bool _isLong ) public pure returns (uint) { // TODO check for large purchase prices int priceChangePrecisionPoints = ( int(_currentStockPriceCents) - int(_lastStockPriceCents) ) * DECIMAL_TO_PRECISION / int(_lastStockPriceCents); int leveredPriceChangePrecisionPoints = priceChangePrecisionPoints * int(_multiplierBasisPoints) / BASIS_POINTS_PER_PERCENT; uint priceChange; if (leveredPriceChangePrecisionPoints < 0) { leveredPriceChangePrecisionPoints *= -1; priceChange = (_purchasePriceWei * uint(leveredPriceChangePrecisionPoints)) / uint(DECIMAL_TO_PRECISION); if (_isLong) { if (priceChange > _purchasePriceWei) { return 0; } return _purchasePriceWei - priceChange; } else { if (_purchasePriceWei + priceChange < _purchasePriceWei) { return 0; } return _purchasePriceWei + priceChange; } } else { priceChange = (_purchasePriceWei * uint(leveredPriceChangePrecisionPoints)) / uint(DECIMAL_TO_PRECISION); if (_isLong) { if (_purchasePriceWei + priceChange < _purchasePriceWei) { return 0; } return _purchasePriceWei + priceChange; } else { if (priceChange > _purchasePriceWei) { return 0; } return _purchasePriceWei - priceChange; } } } /** * This method MUST NEVER be called by smart contract code. First, it's fairly * expensive (it walks the entire stock array looking for the stocks belonging to owner), * but it also returns a dynamic array, which is only supported for web3 calls, and * not contract-to-contract calls. */ function getStocksOfOwner(address _owner) external view returns (uint[]) { uint stockCount = balanceOf(_owner); if (stockCount == 0) { // Return an empty array return new uint[](0); } else { uint[] memory result = new uint256[](stockCount); uint totalStocks = getTotalNumberOfStocks(); uint resultIndex = 0; uint stockId; for (stockId = 1; stockId <= totalStocks; stockId++) { if (owners[stockId] == _owner) { result[resultIndex] = stockId; resultIndex++; } } return result; } } // Same note as getStocksOfOwner function getStocksForSale() external view returns (uint[]) { if (numStocksForSale == 0) { // Return an empty array return new uint[](0); } else { uint[] memory result = new uint256[](numStocksForSale); uint totalStocks = getTotalNumberOfStocks(); uint resultIndex = 0; uint stockId; for (stockId = 1; stockId <= totalStocks; stockId++) { if (stocks[stockId].isForSale) { result[resultIndex] = stockId; resultIndex++; } } return result; } } function balanceOf(address _owner) public view returns (uint) { return balances[_owner]; } function ownerOf(uint256 _stockId) public view returns (address) { require(isValidStockId(_stockId)); if (owners[_stockId] != 0x0) { return owners[_stockId]; } else { return manager; } } function isValidStockId(uint _stockId) internal view returns (bool) { return _stockId != 0 && _stockId < stocks.length; } function getTotalNumberOfStocks() public view returns (uint) { return stocks.length - 1; } function getTotalNumberOfStocksForSale() public view returns (uint) { return numStocksForSale; } } contract StockPriceFetcher is usingOraclize { struct QueryCallbackParams { uint stockId; address from; address to; uint payedPrice; } mapping(bytes32 => bool) private validQueryIds; mapping(bytes32 => QueryCallbackParams) private queryIdToParams; uint private constant ORACLIZE_GAS_LIMIT = 250000; uint private constant ORACLIZE_GAS_PRICE_WEI = 1800000000; // 1.8 Gwei // 0.00045 ether uint internal constant ORACLE_PRICE_WEI = ORACLIZE_GAS_LIMIT * ORACLIZE_GAS_PRICE_WEI; constructor() internal { oraclize_setCustomGasPrice(ORACLIZE_GAS_PRICE_WEI); } function fetchPrice( address _from, address _to, uint _stockId, string url, uint _payedPrice ) internal { bytes32 queryId = oraclize_query("URL", url, ORACLIZE_GAS_LIMIT); validQueryIds[queryId] = true; queryIdToParams[queryId] = QueryCallbackParams({ stockId: _stockId, from: _from, to: _to, payedPrice: _payedPrice }); } /** * Oraclize Callback * * Debugging tip: if the callback isn't firing, it is not a bug with oraclize. * Something along the execution of the function is erroring and the * entire traction is getting reverted. Something is wrong with your code. */ function __callback(bytes32 queryId, string result) public { require(msg.sender == oraclize_cbAddress(), "Callback not called by oraclize"); require(validQueryIds[queryId], "Not a valid query id"); QueryCallbackParams memory params = queryIdToParams[queryId]; uint stockId = params.stockId; address from = params.from; address to = params.to; uint payedPrice = params.payedPrice; require(isValidStockId(stockId), "Not a valid stock Id"); require(from != 0x0, "here"); require(to != 0x0, "here"); // parse stock price, in cents uint stockPriceCents = parseInt(result, 2); // invalidate the query delete validQueryIds[queryId]; delete queryIdToParams[queryId]; finishTransferFrom(from, to, stockId, payedPrice, uint32(stockPriceCents)); } function finishTransferFrom( address _from, address _to, uint _stockId, uint _payedPriceWei, uint32 _stockPriceCents ) internal; function isValidStockId(uint _stockId) internal view returns (bool); }
0.4.18