Last active
April 9, 2023 19:13
-
-
Save Ashar2shahid/6da449b873d1b45e1e27ee01645c54a1 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity 0.8.17; | |
import "@api3/contracts/v0.8/interfaces/IProxy.sol"; | |
contract Api3Options { | |
// ETH/USD proxy address | |
address proxy; | |
uint ethPrice; | |
address payable contractAddr; | |
//Options stored in arrays of structs | |
struct option { | |
uint strike; //Price in USD (18 decimal places) option allows buyer to purchase tokens at | |
uint premium; //Fee in contract token that option writer charges | |
uint expiry; //Unix timestamp of expiration time | |
uint amount; //Amount of tokens the option contract is for | |
bool exercised; //Has option been exercised | |
bool canceled; //Has option been canceled | |
uint id; //Unique ID of option, also array index | |
uint latestCost; //Helper to show last updated cost to exercise | |
address writer; //Issuer of option | |
address buyer; //Buyer of option | |
} | |
option[] public ethOpts; | |
constructor(address _proxy) public { | |
//Set the ETH/USD proxy address | |
proxy = _proxy; | |
contractAddr = payable(address(this)); | |
} | |
//Returns the latest ETH price | |
function getEthPrice() public view returns (uint) { | |
(int224 value,uint32 timestamp) = IProxy(proxy).read(); | |
// if the data feed is being updated with a one day-heartbeat | |
// interval, you may want to check for that. | |
require( | |
timestamp + 1 days > block.timestamp, | |
"Timestamp older than one day" | |
); | |
//Price should never be negative thus cast int to unit is ok | |
//Price is 18 decimal places | |
return uint(uint224(value)); | |
} | |
//Updates prices to latest | |
function updatePrices() internal { | |
ethPrice = getEthPrice(); | |
} | |
//Allows user to write a covered call option | |
//Takes which token, a strike price(USD per token w/18 decimal places), premium(same unit as token), expiration time(unix) and how many tokens the contract is for | |
function writeOption(address ethProxy, uint strike, uint premium, uint expiry, uint tknAmt) public payable { | |
require(ethProxy == proxy, "Only ETH tokens are supported"); | |
updatePrices(); | |
require(msg.value == tknAmt, "Incorrect amount of ETH supplied"); | |
uint latestCost = (strike * (tknAmt)) / (ethPrice); //current cost to exercise in ETH, decimal places corrected | |
ethOpts.push(option(strike, premium, expiry, tknAmt, false, false, ethOpts.length, latestCost, msg.sender, address(0))); | |
} | |
//Allows option writer to cancel and get their funds back from an unpurchased option | |
function cancelOption(address ethProxy, uint ID) public payable { | |
require(ethProxy == proxy, "Only ETH tokens are supported"); | |
require(msg.sender == ethOpts[ID].writer, "You did not write this option"); | |
//Must not have already been canceled or bought | |
require(!ethOpts[ID].canceled && ethOpts[ID].buyer == address(0), "This option cannot be canceled"); | |
payable(ethOpts[ID].writer).transfer(ethOpts[ID].amount); | |
ethOpts[ID].canceled = true; | |
} | |
//Purchase a call option, needs desired token, ID of option and payment | |
function buyOption(address ethProxy, uint ID) public payable { | |
require(ethProxy == proxy, "Only ETH tokens are supported"); | |
updatePrices(); | |
require(!ethOpts[ID].canceled && ethOpts[ID].expiry > block.timestamp, "Option is canceled/expired and cannot be bought"); | |
//Transfer premium payment from buyer | |
require(msg.value == ethOpts[ID].premium, "Incorrect amount of ETH sent for premium"); | |
//Transfer premium payment to writer | |
payable(ethOpts[ID].writer).transfer(ethOpts[ID].premium); | |
ethOpts[ID].buyer = msg.sender; | |
} | |
//Exercise your call option, needs desired token, ID of option and payment | |
function exercise(address ethProxy, uint ID) public payable { | |
//If not expired and not already exercised, allow option owner to exercise | |
//To exercise, the strike value*amount equivalent paid to writer (from buyer) and amount of tokens in the contract paid to buyer | |
require(ethProxy == proxy, "Only ETH tokens are supported"); | |
require(ethOpts[ID].buyer == msg.sender, "You do not own this option"); | |
require(!ethOpts[ID].exercised, "Option has already been exercised"); | |
require(ethOpts[ID].expiry > block.timestamp, "Option is expired"); | |
//Conditions are met, proceed to payouts | |
updatePrices(); | |
//Cost to exercise | |
uint exerciseVal = ethOpts[ID].strike*ethOpts[ID].amount; | |
//Equivalent ETH value using Chainlink feed | |
uint equivEth = exerciseVal / (ethPrice); //move decimal 10 places right to account for 8 places of pricefeed | |
//Buyer exercises option by paying strike*amount equivalent ETH value | |
require(msg.value == equivEth, "Incorrect LINK amount sent to exercise"); | |
//Pay writer the exercise cost | |
payable(ethOpts[ID].writer).transfer(equivEth); | |
//Pay buyer contract amount of ETH | |
payable(msg.sender).transfer(ethOpts[ID].amount); | |
ethOpts[ID].exercised = true; | |
} | |
//Allows writer to retrieve funds from an expired, non-exercised, non-canceled option | |
function retrieveExpiredFunds(address ethProxy, uint ID) public payable { | |
require(ethProxy == proxy, "Only ETH tokens are supported"); | |
require(msg.sender == ethOpts[ID].writer, "You did not write this option"); | |
//Must be expired, not exercised and not canceled | |
require(ethOpts[ID].expiry <= block.timestamp && !ethOpts[ID].exercised && !ethOpts[ID].canceled, "This option is not eligible for withdraw"); | |
payable(ethOpts[ID].writer).transfer(ethOpts[ID].amount); | |
//Repurposing canceled flag to prevent more than one withdraw | |
ethOpts[ID].canceled = true; | |
} | |
//This is a helper function to help the user see what the cost to exercise an option is currently before they do so | |
//Updates lastestCost member of option which is publicly viewable | |
function updateExerciseCost(address ethProxy, uint ID) public { | |
require(ethProxy == proxy, "Only ETH tokens are supported"); | |
updatePrices(); | |
ethOpts[ID].latestCost = ethOpts[ID].strike * (ethOpts[ID].amount) / (ethPrice); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment