Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixups #106

Merged
merged 10 commits into from
Nov 17, 2022
30 changes: 11 additions & 19 deletions src/OptionSettlementEngine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ import "solmate/utils/SafeTransferLib.sol";
import "solmate/utils/FixedPointMathLib.sol";
import "./TokenURIGenerator.sol";

// TODO fix 2 broken bucketing-related tests -- testFailAssignMultipleBuckets and testFailRandomAssignment
// TODO fix broken accessor-related test -- testFailGetClaimForTokenId
// TODO clarify excluded tokens

/**
* Valorem Options V1 is a DeFi money lego enabling writing covered call and covered put, physically settled, options.
* All written options are fully collateralized against an ERC-20 underlying asset and exercised with an
Expand All @@ -29,8 +25,6 @@ import "./TokenURIGenerator.sol";
/// @author Flip-Liquid
/// @author neodaoist
contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine {
//

/*//////////////////////////////////////////////////////////////
// State variables - Public
//////////////////////////////////////////////////////////////*/
Expand Down Expand Up @@ -60,7 +54,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine {
/// on the day in question. write() will add unexercised options into the bucket
/// corresponding to the # of days after the option type's creation.
/// exercise() will randomly assign exercise to a bucket <= the current day.
mapping(uint160 => OptionLotClaimBucket[]) internal _claimBucketByOption;
mapping(uint160 => OptionsDayBucket[]) internal _claimBucketByOption;

/// @notice Maintains a mapping from option id to a list of unexercised bucket (indices)
/// @dev Used during the assignment process to find claim buckets with unexercised
Expand Down Expand Up @@ -147,7 +141,6 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine {
// Token URI
//////////////////////////////////////////////////////////////*/

/// @dev TODO
function uri(uint256 tokenId) public view virtual override returns (string memory) {
Option memory optionInfo;
(uint160 optionKey, uint96 claimNum) = decodeTokenId(tokenId);
Expand Down Expand Up @@ -229,8 +222,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine {
}

function getClaimForTokenId(uint256 tokenId) public view returns (OptionLotClaim memory) {
(, uint96 claimNum) = decodeTokenId(tokenId);
return _claim[claimNum];
return _claim[tokenId];
neodaoist marked this conversation as resolved.
Show resolved Hide resolved
}

/*//////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -350,7 +342,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine {
revert InvalidOption(optionKey);
}
if (expiry <= block.timestamp) {
revert ExpiredOption(uint256(optionKey) << 96, expiry); // TODO measure gas savings and just use optionId
revert ExpiredOption(optionId, expiry);
}

uint256 rxAmount = amount * optionRecord.underlyingAmount;
Expand Down Expand Up @@ -566,14 +558,14 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine {
function _assignExercise(uint160 optionId, Option storage optionRecord, uint112 amount) internal {
// A bucket of the overall amounts written and exercised for all claims
// on a given day
OptionLotClaimBucket[] storage claimBucketArray = _claimBucketByOption[optionId];
OptionsDayBucket[] storage claimBucketArray = _claimBucketByOption[optionId];
uint16[] storage unexercisedBucketIndices = _unexercisedBucketsByOption[optionId];
uint16 unexercisedBucketsMod = uint16(unexercisedBucketIndices.length);
uint16 unexercisedBucketsIndex = uint16(optionRecord.settlementSeed % unexercisedBucketsMod);
while (amount > 0) {
// get the claim bucket to assign
uint16 bucketIndex = unexercisedBucketIndices[unexercisedBucketsIndex];
OptionLotClaimBucket storage claimBucketInfo = claimBucketArray[bucketIndex];
OptionsDayBucket storage claimBucketInfo = claimBucketArray[bucketIndex];

uint112 amountAvailable = claimBucketInfo.amountWritten - claimBucketInfo.amountExercised;
uint112 amountPresentlyExercised;
Expand Down Expand Up @@ -606,7 +598,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine {
return uint16(block.timestamp / 1 days);
}

function _getAmountExercised(OptionLotClaimIndex storage claimIndex, OptionLotClaimBucket storage claimBucketInfo)
function _getAmountExercised(OptionLotClaimIndex storage claimIndex, OptionsDayBucket storage claimBucketInfo)
internal
view
returns (uint256 _exercised, uint256 _unexercised)
Expand Down Expand Up @@ -634,30 +626,30 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine {
OptionLotClaimIndex[] storage claimIndexArray = _claimIdToClaimIndexArray[claimId];
for (uint256 i = 0; i < claimIndexArray.length; i++) {
OptionLotClaimIndex storage claimIndex = claimIndexArray[i];
OptionLotClaimBucket storage claimBucketInfo = _claimBucketByOption[optionId][claimIndex.bucketIndex];
OptionsDayBucket storage claimBucketInfo = _claimBucketByOption[optionId][claimIndex.bucketIndex];
(uint256 amountExercised, uint256 amountUnexercised) = _getAmountExercised(claimIndex, claimBucketInfo);
exerciseAmount += optionRecord.exerciseAmount * amountExercised;
underlyingAmount += optionRecord.underlyingAmount * amountUnexercised;
}
}

function _addOrUpdateClaimBucket(uint160 optionId, uint112 amount) internal returns (uint16) {
OptionLotClaimBucket[] storage claimBucketsInfo = _claimBucketByOption[optionId];
OptionsDayBucket[] storage claimBucketsInfo = _claimBucketByOption[optionId];
uint16[] storage unexercised = _unexercisedBucketsByOption[optionId];
OptionLotClaimBucket storage currentBucket;
OptionsDayBucket storage currentBucket;
uint16 daysAfterEpoch = _getDaysBucket();
uint16 bucketIndex = uint16(claimBucketsInfo.length);
if (claimBucketsInfo.length == 0) {
// add a new bucket none exist
claimBucketsInfo.push(OptionLotClaimBucket(amount, 0, daysAfterEpoch));
claimBucketsInfo.push(OptionsDayBucket(amount, 0, daysAfterEpoch));
// update _unexercisedBucketsByOption and corresponding index mapping
_updateUnexercisedBucketIndices(optionId, bucketIndex, unexercised);
return bucketIndex;
}

currentBucket = claimBucketsInfo[bucketIndex - 1];
if (currentBucket.daysAfterEpoch < daysAfterEpoch) {
claimBucketsInfo.push(OptionLotClaimBucket(amount, 0, daysAfterEpoch));
claimBucketsInfo.push(OptionsDayBucket(amount, 0, daysAfterEpoch));
_updateUnexercisedBucketIndices(optionId, bucketIndex, unexercised);
} else {
// Update claim bucket for today
Expand Down
24 changes: 13 additions & 11 deletions src/interfaces/IOptionSettlementEngine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -243,31 +243,33 @@ interface IOptionSettlementEngine {
uint96 nextClaimNum;
}

// TODO Review and clarify NatSpec on these next 3 claim-related structs
// Previous comment -- The amount written along with the option info can be used to calculate the underlying assets

/// @dev This struct contains the data about a claim ERC-1155 NFT associated with an option lot.
/// @dev This struct contains the data about a lot of options written for a particular option type.
/// When writing an amount of options of a particular type, the writer will be issued an ERC 1155 NFT
/// that represents a claim to the underlying and exercise assets of the options lot, to be claimed after
/// expiry of the option. The amount of each (underlying asset and exercise asset) paid to the claimant upon
/// redeeming their claim NFT depends on the option type, the amount of options written in their options lot
/// (represented in this struct) and what portion of their lot was exercised before expiry.
struct OptionLotClaim {
/// @param amountWritten The number of contracts written in this option lot claim
/// @param amountWritten The number of options written in this option lot claim
uint112 amountWritten;
/// @param claimed Whether or not this amount of an option lot has been claimed
/// @param claimed Whether or not this option lot has been claimed by the writer
bool claimed;
}

/// @dev Claims are options lots which are able to have options added to them on different
/// bucketed days. This struct is used to keep track of how many options in a single lot are
/// @dev Options lots are able to have options added to them on after the initial writing.
/// This struct is used to keep track of how many options in a single lot are
/// written on each day, in order to correctly perform fair assignment.
struct OptionLotClaimIndex {
/// @param amountWritten The amount of options written on a given day
/// @param amountWritten The amount of options written on a given day/bucket
uint112 amountWritten;
/// @param bucketIndex The index of the bucket on which the options are written
/// @param bucketIndex The index of the OptionsDayBucket in which the options are written
uint16 bucketIndex;
}

/// @dev Represents the total amount of options written and exercised for a group of
/// claims bucketed by day. Used in fair assignement to calculate the ratio of
/// underlying to exercise assets to be transferred to claimants.
struct OptionLotClaimBucket {
struct OptionsDayBucket {
/// @param amountWritten The number of options written in this bucket
uint112 amountWritten;
/// @param amountExercised The number of options exercised in this bucket
Expand Down
9 changes: 3 additions & 6 deletions test/OptionSettlement.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,6 @@ contract OptionSettlementTest is Test, NFTreceiver {
writer: ALICE,
exerciser: BOB
});

// TODO what do we assert here
}

function testUnderlyingWhenNotExercised() public {
Expand Down Expand Up @@ -605,7 +603,7 @@ contract OptionSettlementTest is Test, NFTreceiver {
assertEq(engine.getOptionForTokenId(optionId), option);
}

function testFailGetClaimForTokenId() public {
function testGetClaimForTokenId() public {
uint256 optionId =
engine.newOptionType(DAI_A, 1, USDC_A, 100, uint40(block.timestamp), uint40(block.timestamp + 30 days));

Expand Down Expand Up @@ -649,7 +647,7 @@ contract OptionSettlementTest is Test, NFTreceiver {

uint256 expectedOptionId = _createOptionIdFromStruct(optionInfo);

vm.expectEmit(false, true, true, true); // TODO fix
vm.expectEmit(false, true, true, true);
emit NewOptionType(
expectedOptionId,
WETH_A,
Expand Down Expand Up @@ -863,7 +861,7 @@ contract OptionSettlementTest is Test, NFTreceiver {
expectedFees[2] = ((usdcExerciseAmount / 10_000) * engine.feeBps());

for (uint256 i = 0; i < tokens.length; i++) {
vm.expectEmit(true, true, true, false); // TODO debug why USDC sweeping 1 wei less, but not DAI or WETH
vm.expectEmit(true, true, true, false);
emit FeeSwept(tokens[i], engine.feeTo(), expectedFees[i]); // sweeps 1 wei less as gas optimization
}

Expand Down Expand Up @@ -1586,7 +1584,6 @@ contract OptionSettlementTest is Test, NFTreceiver {
claimId = _writeAndExerciseOption(optionId, writer, exerciser);
}

// TODO ensure this function is written as desired
function _createNewOptionType(
address underlyingAsset,
uint96 underlyingAmount,
Expand Down