pragma solidity ^0.5.16; // Inheritance import "./interfaces/IERC20.sol"; import "./interfaces/IBinaryOption.sol"; // Libraries import "./SafeDecimalMath.sol"; // Internal references import "./BinaryOptionMarket.sol"; // https://docs.synthetix.io/contracts/source/contracts/binaryoption contract BinaryOption is IERC20, IBinaryOption { /* ========== LIBRARIES ========== */ using SafeMath for uint; using SafeDecimalMath for uint; /* ========== STATE VARIABLES ========== */ string public constant name = "SNX Binary Option"; string public constant symbol = "sOPT"; uint8 public constant decimals = 18; BinaryOptionMarket public market; mapping(address => uint) public bidOf; uint public totalBids; mapping(address => uint) public balanceOf; uint public totalSupply; // The argument order is allowance[owner][spender] mapping(address => mapping(address => uint)) public allowance; // Enforce a 1 cent minimum bid balance uint internal constant _MINIMUM_BID = 1e16; /* ========== CONSTRUCTOR ========== */ constructor(address initialBidder, uint initialBid) public { market = BinaryOptionMarket(msg.sender); bidOf[initialBidder] = initialBid; totalBids = initialBid; } /* ========== VIEWS ========== */ function _claimableBalanceOf( uint _bid, uint price, uint exercisableDeposits ) internal view returns (uint) { uint owed = _bid.divideDecimal(price); uint supply = _totalClaimableSupply(exercisableDeposits); /* The last claimant might be owed slightly more or less than the actual remaining deposit based on rounding errors with the price. Therefore if the user's bid is the entire rest of the pot, just give them everything that's left. If there is no supply, then this option lost, and we'll return 0. */ if ((_bid == totalBids && _bid != 0) || supply == 0) { return supply; } /* Note that option supply on the losing side and deposits can become decoupled, but losing options are not claimable, therefore we only need to worry about the situation where supply < owed on the winning side. If somehow a user who is not the last bidder is owed more than what's available, subsequent bidders will be disadvantaged. Given that the minimum bid is 10^16 wei, this should never occur in reality. */ require(owed <= supply, "supply < claimable"); return owed; } function claimableBalanceOf(address account) external view returns (uint) { (uint price, uint exercisableDeposits) = market.senderPriceAndExercisableDeposits(); return _claimableBalanceOf(bidOf[account], price, exercisableDeposits); } function _totalClaimableSupply(uint exercisableDeposits) internal view returns (uint) { uint _totalSupply = totalSupply; // We'll avoid throwing an exception here to avoid breaking any dapps, but this case // should never occur given the minimum bid size. if (exercisableDeposits <= _totalSupply) { return 0; } return exercisableDeposits.sub(_totalSupply); } function totalClaimableSupply() external view returns (uint) { (, uint exercisableDeposits) = market.senderPriceAndExercisableDeposits(); return _totalClaimableSupply(exercisableDeposits); } /* ========== MUTATIVE FUNCTIONS ========== */ function _requireMinimumBid(uint bid) internal pure returns (uint) { require(bid >= _MINIMUM_BID || bid == 0, "Balance < $0.01"); return bid; } // This must only be invoked during bidding. function bid(address bidder, uint newBid) external onlyMarket { bidOf[bidder] = _requireMinimumBid(bidOf[bidder].add(newBid)); totalBids = totalBids.add(newBid); } // This must only be invoked during bidding. function refund(address bidder, uint newRefund) external onlyMarket { // The safe subtraction will catch refunds that are too large. bidOf[bidder] = _requireMinimumBid(bidOf[bidder].sub(newRefund)); totalBids = totalBids.sub(newRefund); } // This must only be invoked after bidding. function claim( address claimant, uint price, uint depositsRemaining ) external onlyMarket returns (uint optionsClaimed) { uint _bid = bidOf[claimant]; uint claimable = _claimableBalanceOf(_bid, price, depositsRemaining); // No options to claim? Nothing happens. if (claimable == 0) { return 0; } totalBids = totalBids.sub(_bid); bidOf[claimant] = 0; totalSupply = totalSupply.add(claimable); balanceOf[claimant] = balanceOf[claimant].add(claimable); // Increment rather than assigning since a transfer may have occurred. emit Transfer(address(0), claimant, claimable); emit Issued(claimant, claimable); return claimable; } // This must only be invoked after maturity. function exercise(address claimant) external onlyMarket { uint balance = balanceOf[claimant]; if (balance == 0) { return; } balanceOf[claimant] = 0; totalSupply = totalSupply.sub(balance); emit Transfer(claimant, address(0), balance); emit Burned(claimant, balance); } // This must only be invoked after the exercise window is complete. // Note that any options which have not been exercised will linger. function expire(address payable beneficiary) external onlyMarket { selfdestruct(beneficiary); } /* ---------- ERC20 Functions ---------- */ // This should only operate after bidding; // Since options can't be claimed until after bidding, all balances are zero until that time. // So we don't need to explicitly check the timestamp to prevent transfers. function _transfer( address _from, address _to, uint _value ) internal returns (bool success) { market.requireActiveAndUnpaused(); require(_to != address(0) && _to != address(this), "Invalid address"); uint fromBalance = balanceOf[_from]; require(_value <= fromBalance, "Insufficient balance"); balanceOf[_from] = fromBalance.sub(_value); balanceOf[_to] = balanceOf[_to].add(_value); emit Transfer(_from, _to, _value); return true; } function transfer(address _to, uint _value) external returns (bool success) { return _transfer(msg.sender, _to, _value); } function transferFrom( address _from, address _to, uint _value ) external returns (bool success) { uint fromAllowance = allowance[_from][msg.sender]; require(_value <= fromAllowance, "Insufficient allowance"); allowance[_from][msg.sender] = fromAllowance.sub(_value); return _transfer(_from, _to, _value); } function approve(address _spender, uint _value) external returns (bool success) { require(_spender != address(0)); allowance[msg.sender][_spender] = _value; emit Approval(msg.sender, _spender, _value); return true; } /* ========== MODIFIERS ========== */ modifier onlyMarket() { require(msg.sender == address(market), "Only market allowed"); _; } /* ========== EVENTS ========== */ event Issued(address indexed account, uint value); event Burned(address indexed account, uint value); event Transfer(address indexed from, address indexed to, uint value); event Approval(address indexed owner, address indexed spender, uint value); }