Skip to content

Commit

Permalink
Merge pull request #106 from valorem-labs-inc/master_fixups
Browse files Browse the repository at this point in the history
fixups
  • Loading branch information
Flip-Liquid authored Nov 17, 2022
2 parents 9fb24f7 + a639114 commit 6a5f7c7
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 36 deletions.
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 @@ -157,7 +151,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 @@ -239,8 +232,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];
}

/*//////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -360,7 +352,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 @@ -576,14 +568,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 @@ -616,7 +608,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 @@ -644,30 +636,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 @@ -262,8 +262,6 @@ contract OptionSettlementTest is Test, NFTreceiver {
writer: ALICE,
exerciser: BOB
});

// TODO what do we assert here
}

function testUnderlyingWhenNotExercised() public {
Expand Down Expand Up @@ -609,7 +607,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 @@ -653,7 +651,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 @@ -867,7 +865,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 @@ -1590,7 +1588,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

0 comments on commit 6a5f7c7

Please sign in to comment.