Skip to content

Commit

Permalink
Ability to exercise option (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
0xAlcibiades authored Mar 21, 2022
1 parent 5b76db4 commit ec14c12
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 31 deletions.
94 changes: 72 additions & 22 deletions src/OptionSettlement.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ enum Type {
Claim
}

// TODO(Right now fee is taken on top of written amount and exercised amount, should the model be different?)
// TODO(Consider converting require strings to errors)
// TODO(An enum here indicating if the option is a put or a call would be redundant, but maybe useful?)
// TODO(Consider rebase tokens, fee on transfer tokens, or other tokens which may break assumptions)

struct Option {
// The underlying asset to be received
Expand Down Expand Up @@ -100,7 +102,7 @@ contract OptionSettlementEngine is ERC1155 {
return string(abi.encodePacked("data:application/json;base64,", json));
}

function newOptionsChain(Option memory optionInfo)
function newChain(Option memory optionInfo)
external
returns (uint256 tokenId)
{
Expand Down Expand Up @@ -135,16 +137,16 @@ contract OptionSettlementEngine is ERC1155 {
tokenType[_nextTokenId] = Type.Option;

// Check that both tokens are ERC20 by instantiating them and checking supply
ERC20 underlying = ERC20(optionInfo.underlyingAsset);
ERC20 exercise = ERC20(optionInfo.exerciseAsset);
ERC20 underlyingToken = ERC20(optionInfo.underlyingAsset);
ERC20 exerciseToken = ERC20(optionInfo.exerciseAsset);

// Check total supplies and ensure the option will be exercisable
require(
underlying.totalSupply() >= optionInfo.underlyingAmount,
underlyingToken.totalSupply() >= optionInfo.underlyingAmount,
"Invalid Supply"
);
require(
exercise.totalSupply() >= optionInfo.exerciseAmount,
exerciseToken.totalSupply() >= optionInfo.exerciseAmount,
"Invalid Supply"
);

Expand All @@ -159,23 +161,24 @@ contract OptionSettlementEngine is ERC1155 {
chainMap[chainKey] = true;
}

function writeOptions(uint256 tokenId, uint256 amount) external {
require(tokenType[tokenId] == Type.Option, "Token is not an option");
function write(uint256 optionId, uint256 amount) external {
require(tokenType[optionId] == Type.Option, "Token is not an option");
require(
option[tokenId].settlementSeed != 0,
option[optionId].settlementSeed != 0,
"Settlement seed not populated"
);

Option storage optionRecord = option[tokenId];
Option storage optionRecord = option[optionId];

uint256 tx_amount = amount * optionRecord.underlyingAmount;
uint256 rx_amount = amount * optionRecord.underlyingAmount;
uint256 fee = ((rx_amount / 10000) * feeBps);

// Transfer the requisite underlying asset
SafeTransferLib.safeTransferFrom(
ERC20(optionRecord.underlyingAsset),
msg.sender,
address(this),
tx_amount
(rx_amount + fee)
);

// TODO(Consider an internal balance counter here and aggregating these in a fee sweep)
Expand All @@ -184,16 +187,16 @@ contract OptionSettlementEngine is ERC1155 {
SafeTransferLib.safeTransfer(
ERC20(optionRecord.underlyingAsset),
feeTo,
((tx_amount / 10000) * feeBps)
fee
);
// TODO(Do we need any other internal balance counters?)

uint256 claimTokenId = _nextTokenId;
uint256 claimId = _nextTokenId;

// Mint the options contracts and claim token
uint256[] memory tokens = new uint256[](2);
tokens[0] = tokenId;
tokens[1] = claimTokenId;
tokens[0] = optionId;
tokens[1] = claimId;

uint256[] memory amounts = new uint256[](2);
amounts[0] = amount;
Expand All @@ -205,9 +208,9 @@ contract OptionSettlementEngine is ERC1155 {
_batchMint(msg.sender, tokens, amounts, data);

// Store info about the claim
tokenType[claimTokenId] = Type.Claim;
claim[claimTokenId] = Claim({
option: tokenId,
tokenType[claimId] = Type.Claim;
claim[claimId] = Claim({
option: optionId,
amountWritten: amount,
amountExercised: 0,
claimed: false
Expand All @@ -218,11 +221,58 @@ contract OptionSettlementEngine is ERC1155 {
++_nextTokenId;
}

// TODO(Exercise option)
function exercise(uint256 optionId, uint256 amount) external {
require(tokenType[optionId] == Type.Option, "Token is not an option");

Option storage optionRecord = option[optionId];

// TODO(Redeem claim)
// Require that we have reached the exercise timestamp

// TODO(Get info about options contract)
require(
optionRecord.exerciseTimestamp <= block.timestamp,
"Too early to exercise"
);
uint256 rx_amount = optionRecord.exerciseAmount * amount;
uint256 tx_amount = optionRecord.underlyingAmount * amount;
uint256 fee = ((rx_amount / 10000) * feeBps);

// Transfer in the requisite exercise asset
SafeTransferLib.safeTransferFrom(
ERC20(optionRecord.exerciseAsset),
msg.sender,
address(this),
(rx_amount + fee)
);

// TODO(Consider aggregating this)
// Transfer out protocol fee
SafeTransferLib.safeTransfer(
ERC20(optionRecord.exerciseAsset),
feeTo,
fee
);

// TODO(Get info about a claim)
// Transfer out the underlying
SafeTransferLib.safeTransfer(
ERC20(optionRecord.underlyingAsset),
msg.sender,
tx_amount
);

// TODO(Exercise assignment and claims update)
_burn(msg.sender, optionId, amount);
// TODO(Emit events for indexing and frontend)
}

function redeem(uint256 claimId) external view {
require(tokenType[claimId] == Type.Claim, "Token is not an claim");
// TODO(Implement)
}

function underlying(uint256 tokenId) external view {
require(tokenType[tokenId] != Type.None, "Token does not exist");
// TODO(Get info about underlying assets)
// TODO(Get info about options contract)
// TODO(Get info about a claim)
}
}
32 changes: 23 additions & 9 deletions src/test/OptionSettlement.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,17 @@ contract OptionSettlementTest is DSTest, NFTreceiver {
exerciseTimestamp: uint64(block.timestamp),
expiryTimestamp: (uint64(block.timestamp) + 604800)
});
engine.newOptionsChain(info);
engine.newChain(info);
// Now we have 1B DAI
writeTokenBalance(address(this), address(dai), 1000000000 * 1e18);
// And 10 M WETH
writeTokenBalance(address(this), address(weth), 10000000 * 1e18);
// Issue approvals
IERC20(weth).approve(address(engine), type(uint256).max);
IERC20(dai).approve(address(engine), type(uint256).max);
}

function testNewOptionsChain(uint256 settlementSeed) public {
function testNewChain(uint256 settlementSeed) public {
Option memory info = Option({
underlyingAsset: address(weth),
exerciseAsset: address(dai),
Expand All @@ -97,10 +100,10 @@ contract OptionSettlementTest is DSTest, NFTreceiver {
exerciseTimestamp: uint64(block.timestamp),
expiryTimestamp: (uint64(block.timestamp) + 604800)
});
engine.newOptionsChain(info);
engine.newChain(info);
}

function testFailDuplicateOptionsChain() public {
function testFailDuplicateChain() public {
// This should fail to create the second and duplicate options chain
Option memory info = Option({
underlyingAsset: address(weth),
Expand All @@ -111,7 +114,7 @@ contract OptionSettlementTest is DSTest, NFTreceiver {
exerciseTimestamp: uint64(block.timestamp),
expiryTimestamp: (uint64(block.timestamp) + 604800)
});
engine.newOptionsChain(info);
engine.newChain(info);
}

function testUri() public view {
Expand All @@ -122,13 +125,24 @@ contract OptionSettlementTest is DSTest, NFTreceiver {
engine.uri(1);
}

// TODO(Why is gasreport not working on this function)
function testWriteOptions(uint16 amountToWrite) public {
IERC20(weth).approve(address(engine), type(uint256).max);
engine.writeOptions(0, uint256(amountToWrite));
// TODO(Why is gas report not working on this function)
function testWrite(uint16 amountToWrite) public {
engine.write(0, uint256(amountToWrite));
// Assert that we have the contracts
assert(engine.balanceOf(address(this), 0) == amountToWrite);
// Assert that we have the claim
assert(engine.balanceOf(address(this), 1) == 1);
}

function testExercise(uint16 amountToWrite) public {
engine.write(0, uint256(amountToWrite));
// Assert that we have the contracts
assert(engine.balanceOf(address(this), 0) == amountToWrite);
// Assert that we have the claim
assert(engine.balanceOf(address(this), 1) == 1);
uint256 bal = IERC20(weth).balanceOf(address(this));
engine.exercise(0, amountToWrite);
uint256 newBal = IERC20(weth).balanceOf(address(this));
assert(newBal == (bal + (1 ether * uint256(amountToWrite))));
}
}

0 comments on commit ec14c12

Please sign in to comment.