Skip to content

Commit

Permalink
Merge pull request #158 from valorem-labs-inc/refactor/dont-overload-…
Browse files Browse the repository at this point in the history
…underlying

Rename Underlying to Position — why overload `Underlying`?
  • Loading branch information
neodaoist authored Dec 19, 2022
2 parents 8686afe + 8ad50f6 commit b6db273
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 135 deletions.
143 changes: 74 additions & 69 deletions src/OptionSettlementEngine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine {
}

/// @inheritdoc IOptionSettlementEngine
function underlying(uint256 tokenId) external view returns (Underlying memory underlyingPosition) {
function position(uint256 tokenId) external view returns (Position memory positionInfo) {
(uint160 optionKey, uint96 claimKey) = _decodeTokenId(tokenId);

// Check the type of token and if it exists.
Expand All @@ -240,11 +240,11 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine {
revert ExpiredOption(tokenId, expiry);
}

underlyingPosition = Underlying({
positionInfo = Position({
underlyingAsset: optionRecord.underlyingAsset,
underlyingPosition: int256(uint256(optionRecord.underlyingAmount)),
underlyingAmount: int256(uint256(optionRecord.underlyingAmount)),
exerciseAsset: optionRecord.exerciseAsset,
exercisePosition: -int256(uint256(optionRecord.exerciseAmount))
exerciseAmount: -int256(uint256(optionRecord.exerciseAmount))
});
} else {
// Then tokenId is an initialized/unredeemed claim.
Expand All @@ -265,11 +265,11 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine {
totalExerciseAmount += indexExerciseAmount;
}

underlyingPosition = Underlying({
positionInfo = Position({
underlyingAsset: optionRecord.underlyingAsset,
underlyingPosition: int256(totalUnderlyingAmount),
underlyingAmount: int256(totalUnderlyingAmount),
exerciseAsset: optionRecord.exerciseAsset,
exercisePosition: int256(totalExerciseAmount)
exerciseAmount: int256(totalExerciseAmount)
});
}
}
Expand All @@ -295,9 +295,11 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine {
}
}

/// @notice Returns the URI for a given tokenId.
/// @param tokenId The tokenId of an option or claim.
/// @return The URI for the tokenId.
/**
* @notice Returns the URI for a given tokenId.
* @param tokenId The tokenId of an option or claim.
* @return The URI for the tokenId.
*/
function uri(uint256 tokenId) public view virtual override returns (string memory) {
Option memory optionInfo = optionTypeStates[uint160(tokenId >> OPTION_KEY_PADDING)].option;

Expand Down Expand Up @@ -680,34 +682,9 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine {
// Private Views
//////////////////////////////////////////////////////////////*/

/**
* @notice Encodes the supplied option id and claim id
* @dev See tokenType() for encoding scheme
* @param optionKey The optionKey to encode.
* @param claimKey The claimKey to encode.
* @return tokenId The encoded token id.
*/
function _encodeTokenId(uint160 optionKey, uint96 claimKey) private pure returns (uint256 tokenId) {
// Encode uint160 option key into upper 160b.
tokenId |= uint256(optionKey) << OPTION_KEY_PADDING;

// Encode uint96 claim key into lower 96b.
tokenId |= uint256(claimKey);
}

/**
* @notice Decodes the supplied token id
* @dev See tokenType() for encoding scheme
* @param tokenId The token id to decode
* @return optionKey claimNum The decoded components of the id as described above, padded as required
*/
function _decodeTokenId(uint256 tokenId) private pure returns (uint160 optionKey, uint96 claimKey) {
// Move option key to lsb to fit into uint160.
optionKey = uint160(tokenId >> OPTION_KEY_PADDING);

// Get lower 96b of tokenId for uint96 claim key.
claimKey = uint96(tokenId & CLAIM_KEY_MASK);
}
//
// Option information
//

/**
* @notice Checks if an option type is already initialized.
Expand All @@ -728,9 +705,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine {
return optionTypeStates[optionKey].claimIndices[claimKey].length > 0;
}

/**
* @notice Returns the exercised and unexercised amounts for a given claim index.
*/
/// @notice Returns the exercised and unexercised amounts for a given claim index.
function _getAssetAmountsForClaimIndex(
uint256 underlyingAssetAmount,
uint256 exerciseAssetAmount,
Expand All @@ -750,29 +725,52 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine {
);
}

/*//////////////////////////////////////////////////////////////
// Private Mutators
//////////////////////////////////////////////////////////////*/
//
// Token information
//

/**
* @notice Calculates, records, and emits an event for a fee accrual.
* @notice Encodes the supplied option id and claim id
* @dev See tokenType() for encoding scheme
* @param optionKey The optionKey to encode.
* @param claimKey The claimKey to encode.
* @return tokenId The encoded token id.
*/
function _calculateRecordAndEmitFee(uint256 optionId, address assetAddress, uint256 assetAmount)
private
returns (uint256 fee)
{
fee = (assetAmount * feeBps) / 10_000;
feeBalance[assetAddress] += fee;
function _encodeTokenId(uint160 optionKey, uint96 claimKey) private pure returns (uint256 tokenId) {
// Encode uint160 option key into upper 160b.
tokenId |= uint256(optionKey) << OPTION_KEY_PADDING;

emit FeeAccrued(optionId, assetAddress, msg.sender, fee);
// Encode uint96 claim key into lower 96b.
tokenId |= uint256(claimKey);
}

/**
* @notice Performs fair exercise assignment via the pseudorandom selection of a claim
* bucket between the initial creation of the option type and "today". The buckets
* are then iterated from oldest to newest (looping if we reach "today") if the
* exercise amount overflows into another bucket. The seed for the pseudorandom
* index is updated accordingly on the option type.
* @notice Decodes the supplied token id
* @dev See tokenType() for encoding scheme
* @param tokenId The token id to decode
* @return optionKey claimNum The decoded components of the id as described above, padded as required
*/
function _decodeTokenId(uint256 tokenId) private pure returns (uint160 optionKey, uint96 claimKey) {
// Move option key to lsb to fit into uint160.
optionKey = uint160(tokenId >> OPTION_KEY_PADDING);

// Get lower 96b of tokenId for uint96 claim key.
claimKey = uint96(tokenId & CLAIM_KEY_MASK);
}

/*//////////////////////////////////////////////////////////////
// Private Mutators
//////////////////////////////////////////////////////////////*/

//
// Exercise Assignment
//

/**
* @notice Performs fair exercise assignment via the pseudorandom selection of an
* unexercised or partially exercised bucket. If the exercise amount overflows into
* another bucket, the buckets are iterated from oldest to newest. The pseudorandom
* index seed is updated accordingly on the option type.
*/
function _assignExercise(OptionTypeState storage optionTypeState, Option storage optionRecord, uint112 amount)
private
Expand Down Expand Up @@ -817,10 +815,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine {
uint160(uint256(keccak256(abi.encode(optionRecord.settlementSeed, exerciseIndex))));
}

/**
* @notice Adds or updates a bucket as needed for a given option type and
* amount of options written, based on the present time and bucket state.
*/
/// @notice Adds or updates a bucket as needed for a given option type and amount written.
function _addOrUpdateBucket(OptionTypeState storage optionTypeState, uint112 amount)
private
returns (uint96 bucketIndex)
Expand All @@ -843,7 +838,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine {
Bucket storage currentBucket = buckets[currentBucketIndex];

if (bucketInfo.bucketExerciseStates[currentBucketIndex] != BucketExerciseState.Unexercised) {
// Add a new bucket to this option type, because the last was exercised.
// Add a new bucket to this option type, because the last was partially or fully exercised.
buckets.push(Bucket(amount, 0));
_updateUnexercisedBucketIndices(bucketInfo, writtenBucketIndex);
} else {
Expand All @@ -855,18 +850,13 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine {
return writtenBucketIndex;
}

/**
* @notice Adds the bucket index to the list of buckets with collateral
* and sets the mapping for that bucket having collateral to true.
*/
/// @notice Adds the bucket index to the list of unexercised buckets and sets state to Unexercised.
function _updateUnexercisedBucketIndices(BucketInfo storage bucketInfo, uint96 bucketIndex) private {
bucketInfo.unexercisedBucketIndices.push(bucketIndex);
bucketInfo.bucketExerciseStates[bucketIndex] = BucketExerciseState.Unexercised;
}

/**
* @notice Updates claimIndices for a given claim key.
*/
/// @notice Updates claimIndices for a given claim key.
function _addOrUpdateClaimIndex(
OptionTypeState storage optionTypeState,
uint96 claimKey,
Expand Down Expand Up @@ -895,4 +885,19 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine {
// Else, we are writing to an index that already exists. Update the amount written.
lastIndex.amountWritten += amount;
}

//
// Protocol Fee
//

/// @notice Calculates, records, and emits an event for a fee accrual.
function _calculateRecordAndEmitFee(uint256 optionId, address assetAddress, uint256 assetAmount)
private
returns (uint256 fee)
{
fee = (assetAmount * feeBps) / 10_000;
feeBalance[assetAddress] += fee;

emit FeeAccrued(optionId, assetAddress, msg.sender, fee);
}
}
22 changes: 11 additions & 11 deletions src/interfaces/IOptionSettlementEngine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ interface IOptionSettlementEngine {
// Data Structures
//////////////////////////////////////////////////////////////*/

/// @notice This enumeration is used to convey the type of an ERC1155 subtoken in the engine.
/// @notice The type of an ERC1155 subtoken in the engine.
enum TokenType {
None,
Option,
Expand Down Expand Up @@ -256,7 +256,7 @@ interface IOptionSettlementEngine {
}

/**
* @notice This struct contains the data about a claim to a short position written on an option type.
* @notice Data about a claim to a short position written on an 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, to be claimed after
* expiry of the option. The amount of each (underlying asset and exercise asset) paid to the claimant upon
Expand All @@ -278,15 +278,15 @@ interface IOptionSettlementEngine {
* @notice Data about the ERC20 assets and liabilities for a given option (long) or claim (short) token,
* in terms of the underlying and exercise ERC20 tokens.
*/
struct Underlying {
struct Position {
/// @custom:member underlyingAsset The address of the ERC20 underlying asset.
address underlyingAsset;
/// @custom:member underlyingPosition The amount, in wei, of the underlying asset represented by this position.
int256 underlyingPosition;
/// @custom:member underlyingAmount The amount, in wei, of the underlying asset represented by this position.
int256 underlyingAmount;
/// @custom:member exerciseAsset The address of the ERC20 exercise asset.
address exerciseAsset;
/// @custom:member exercisePosition The amount, in wei, of the exercise asset represented by this position.
int256 exercisePosition;
/// @custom:member exerciseAmount The amount, in wei, of the exercise asset represented by this position.
int256 exerciseAmount;
}

/*//////////////////////////////////////////////////////////////
Expand All @@ -312,14 +312,14 @@ interface IOptionSettlementEngine {
function claim(uint256 claimId) external view returns (Claim memory claimInfo);

/**
* @notice Gets information about the ERC20 token positions underlying an option or claim.
* @notice Gets information about the ERC20 token positions of an option or claim.
* @param tokenId The tokenId of the option or claim.
* @return position The Underlying position for the given tokenId.
* @return positionInfo The underlying and exercise token positions for the given tokenId.
*/
function underlying(uint256 tokenId) external view returns (Underlying memory position);
function position(uint256 tokenId) external view returns (Position memory positionInfo);

//
// Token information
// Token Information
//

/**
Expand Down
17 changes: 8 additions & 9 deletions test/OptionSettlementEngine.fuzz.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,14 @@ contract OptionSettlementFuzzTest is BaseEngineTest {

vm.startPrank(ALICE);
uint256 claimId = engine.write(testOptionId, amount);
IOptionSettlementEngine.Underlying memory claimUnderlying = engine.underlying(claimId);
IOptionSettlementEngine.Position memory claimPosition = engine.position(claimId);

assertEq(WETHLIKE.balanceOf(address(engine)), wethBalanceEngine + rxAmount + fee);
assertEq(WETHLIKE.balanceOf(ALICE), wethBalance - rxAmount - fee);

assertEq(engine.balanceOf(ALICE, testOptionId), amount);
assertEq(engine.balanceOf(ALICE, claimId), 1);
assertEq(uint256(claimUnderlying.underlyingPosition), testUnderlyingAmount * amount);
assertEq(uint256(claimPosition.underlyingAmount), testUnderlyingAmount * amount);

(uint160 optionId, uint96 claimIdx) = decodeTokenId(claimId);
assertEq(uint256(optionId) << 96, testOptionId);
Expand Down Expand Up @@ -241,18 +241,17 @@ contract OptionSettlementFuzzTest is BaseEngineTest {

function _claimAndAssert(address claimant, uint256 claimId) internal {
vm.startPrank(claimant);
IOptionSettlementEngine.Underlying memory underlying = engine.underlying(claimId);
uint256 exerciseAssetAmount = ERC20(underlying.exerciseAsset).balanceOf(claimant);
uint256 underlyingAssetAmount = ERC20(underlying.underlyingAsset).balanceOf(claimant);
IOptionSettlementEngine.Position memory position = engine.position(claimId);
uint256 exerciseAssetAmount = ERC20(position.exerciseAsset).balanceOf(claimant);
uint256 underlyingAssetAmount = ERC20(position.underlyingAsset).balanceOf(claimant);
engine.redeem(claimId);

assertEq(
ERC20(underlying.underlyingAsset).balanceOf(claimant),
underlyingAssetAmount + uint256(underlying.underlyingPosition)
ERC20(position.underlyingAsset).balanceOf(claimant),
underlyingAssetAmount + uint256(position.underlyingAmount)
);
assertEq(
ERC20(underlying.exerciseAsset).balanceOf(claimant),
exerciseAssetAmount + uint256(underlying.exercisePosition)
ERC20(position.exerciseAsset).balanceOf(claimant), exerciseAssetAmount + uint256(position.exerciseAmount)
);
vm.stopPrank();
}
Expand Down
Loading

0 comments on commit b6db273

Please sign in to comment.