Last active
March 24, 2023 19:04
-
-
Save Ashar2shahid/cab2e3c89e7e300c916d32cc1500179b 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 "https://github.com/smartcontractkit/chainlink/blob/develop/evm-contracts/src/v0.6/interfaces/LinkTokenInterface.sol"; | |
import "https://github.com/smartcontractkit/chainlink/blob/master/evm-contracts/src/v0.6/interfaces/AggregatorV3Interface.sol"; | |
contract ChainlinkOptions { | |
//Pricefeed interfaces | |
AggregatorV3Interface internal ethFeed; | |
//Interface for LINK token functions | |
LinkTokenInterface internal LINK; | |
uint ethPrice; | |
//Precomputing hash of strings | |
bytes32 ethHash = keccak256(abi.encodePacked("ETH")); | |
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 payable writer; //Issuer of option | |
address payable buyer; //Buyer of option | |
} | |
option[] public ethOpts; | |
constructor() public { | |
//ETH/USD Goerli feed | |
ethFeed = AggregatorV3Interface(0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e); | |
//LINK token address on Goerli | |
LINK = LinkTokenInterface(0x326C977E6efc84E512bB9C30f76E30c160eD06FB); | |
contractAddr = payable(address(this)); | |
} | |
//Returns the latest ETH price | |
function getEthPrice() public view returns (uint) { | |
( | |
uint80 roundID, | |
int price, | |
uint startedAt, | |
uint timeStamp, | |
uint80 answeredInRound | |
) = ethFeed.latestRoundData(); | |
// If the round is not complete yet, timestamp is 0 | |
require(timeStamp > 0, "Round not complete"); | |
//Price should never be negative thus cast int to unit is ok | |
//Price is 8 decimal places and will require 1e10 correction later to 18 places | |
return uint(price); | |
} | |
//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(string memory token, uint strike, uint premium, uint expiry, uint tknAmt) public payable { | |
bytes32 tokenHash = keccak256(abi.encodePacked(token)); | |
require(tokenHash == ethHash, "Only ETH tokens are supported"); | |
updatePrices(); | |
require(msg.value == tknAmt, "Incorrect amount of ETH supplied"); | |
uint latestCost = strike.mul(tknAmt).div(ethPrice.mul(10**10)); //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(string memory token, uint ID) public payable { | |
bytes32 tokenHash = keccak256(abi.encodePacked(token)); | |
require(tokenHash == ethHash, "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"); | |
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(string memory token, uint ID) public payable { | |
bytes32 tokenHash = keccak256(abi.encodePacked(token)); | |
require(tokenHash == ethHash, "Only ETH tokens are supported"); | |
updatePrices(); | |
require(!ethOpts[ID].canceled && ethOpts[ID].expiry > now, "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 | |
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(string memory token, 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 | |
bytes32 tokenHash = keccak256(abi.encodePacked(token)); | |
require(tokenHash == ethHash, "Only ETH 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 > now, "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.div(ethPrice.mul(10**10)); //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 | |
ethOpts[ID].writer.transfer(equivEth); | |
//Pay buyer contract amount of ETH | |
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(string memory token, uint ID) public payable { | |
bytes32 tokenHash = keccak256(abi.encodePacked(token)); | |
require(tokenHash == ethHash, "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 <= now && !ethOpts[ID].exercised && !ethOpts[ID].canceled, "This option is not eligible for withdraw"); | |
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(string memory token, uint ID) public { | |
bytes32 tokenHash = keccak256(abi.encodePacked(token)); | |
require(tokenHash == ethHash, "Only ETH tokens are supported"); | |
updatePrices(); | |
ethOpts[ID].latestCost = ethOpts[ID].strike.mul(ethOpts[ID].amount).div(ethPrice.mul(10**10)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment