diff --git a/.gas-snapshot b/.gas-snapshot index c519c4f..a5ed9e7 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,85 +1,82 @@ -ValoremOptionsClearinghouseFuzzTest:test_fuzzExercise(uint112,uint112) (runs: 256, μ: 363675, ~: 366353) -ValoremOptionsClearinghouseFuzzTest:test_fuzzNewOptionType(uint96,uint96,uint40,uint40) (runs: 256, μ: 130922, ~: 130922) -ValoremOptionsClearinghouseFuzzTest:test_fuzzWrite(uint112) (runs: 256, μ: 284216, ~: 284216) -ValoremOptionsClearinghouseFuzzTest:test_fuzzWriteExerciseRedeem(uint32) (runs: 256, μ: 9524189, ~: 9614090) -ValoremOptionsClearinghouseIntegrationTest:test_integrationAddOptionsToExistingClaim() (gas: 400435) -ValoremOptionsClearinghouseIntegrationTest:test_integrationDustHandling() (gas: 920361) +ValoremOptionsClearinghouseFuzzTest:test_fuzzExercise(uint112,uint112) (runs: 256, μ: 363186, ~: 366311) +ValoremOptionsClearinghouseFuzzTest:test_fuzzNewOptionType(uint96,uint96,uint40,uint40) (runs: 256, μ: 130900, ~: 130900) +ValoremOptionsClearinghouseFuzzTest:test_fuzzWrite(uint112) (runs: 256, μ: 284218, ~: 284218) +ValoremOptionsClearinghouseFuzzTest:test_fuzzWriteExerciseRedeem(uint32) (runs: 256, μ: 9537440, ~: 9615628) +ValoremOptionsClearinghouseIntegrationTest:test_integrationAddOptionsToExistingClaim() (gas: 400573) +ValoremOptionsClearinghouseIntegrationTest:test_integrationDustHandling() (gas: 920297) ValoremOptionsClearinghouseIntegrationTest:test_integrationFairAssignment() (gas: 603589) -ValoremOptionsClearinghouseIntegrationTest:test_integrationRandomAssignment() (gas: 906223) -ValoremOptionsClearinghouseIntegrationTest:test_integrationSweepFeesWhenFeesAccruedForExercise() (gas: 1418985) -ValoremOptionsClearinghouseIntegrationTest:test_integrationSweepFeesWhenFeesAccruedForWrite() (gas: 1343568) +ValoremOptionsClearinghouseIntegrationTest:test_integrationRandomAssignment() (gas: 906201) +ValoremOptionsClearinghouseIntegrationTest:test_integrationSweepFeesWhenFeesAccruedForExercise() (gas: 1418790) +ValoremOptionsClearinghouseIntegrationTest:test_integrationSweepFeesWhenFeesAccruedForWrite() (gas: 1343354) ValoremOptionsClearinghouseIntegrationTest:test_integrationWriteExerciseAddBuckets() (gas: 1062312) -ValoremOptionsClearinghouseInvariantTest:invariant_erc20_balances() (runs: 10, calls: 10000, reverts: 5019) -ValoremOptionsClearinghouseInvariantTest:invariant_options_written_match_claims() (runs: 10, calls: 10000, reverts: 4965) -ValoremOptionsClearinghouseInvariantTest:invariant_positions_accounting() (runs: 10, calls: 10000, reverts: 4922) ValoremOptionsClearinghouseUnitTest:testRevert_acceptFeeTo_whenNotPendingFeeTo() (gas: 35301) ValoremOptionsClearinghouseUnitTest:testRevert_claim_whenClaimDoesNotExist() (gas: 14648) -ValoremOptionsClearinghouseUnitTest:testRevert_construction_whenFeeToIsZeroAddress() (gas: 2055329) -ValoremOptionsClearinghouseUnitTest:testRevert_construction_whenTokenURIGeneratorIsZeroAddress() (gas: 39989) -ValoremOptionsClearinghouseUnitTest:testRevert_exercise_whenCallerHasNotGrantedSufficientApprovalToEngine() (gas: 584824) -ValoremOptionsClearinghouseUnitTest:testRevert_exercise_whenCallerHoldsInsufficientExerciseAsset() (gas: 582959) +ValoremOptionsClearinghouseUnitTest:testRevert_construction_whenFeeToIsZeroAddress() (gas: 2055447) +ValoremOptionsClearinghouseUnitTest:testRevert_construction_whenTokenURIGeneratorIsZeroAddress() (gas: 40107) +ValoremOptionsClearinghouseUnitTest:testRevert_exercise_whenCallerHasNotGrantedSufficientApprovalToEngine() (gas: 584781) +ValoremOptionsClearinghouseUnitTest:testRevert_exercise_whenCallerHoldsInsufficientExerciseAsset() (gas: 582916) ValoremOptionsClearinghouseUnitTest:testRevert_exercise_whenCallerHoldsInsufficientOptions() (gas: 264635) ValoremOptionsClearinghouseUnitTest:testRevert_exercise_whenExerciseTooEarly() (gas: 268095) ValoremOptionsClearinghouseUnitTest:testRevert_exercise_whenExpiredOption() (gas: 267890) ValoremOptionsClearinghouseUnitTest:testRevert_exercise_whenInvalidOption() (gas: 254368) -ValoremOptionsClearinghouseUnitTest:testRevert_newOptionType_whenExerciseWindowTooShort() (gas: 23356) -ValoremOptionsClearinghouseUnitTest:testRevert_newOptionType_whenExpiryWindowTooShort() (gas: 24821) -ValoremOptionsClearinghouseUnitTest:testRevert_newOptionType_whenInvalidAssets() (gas: 21454) -ValoremOptionsClearinghouseUnitTest:testRevert_newOptionType_whenOptionsTypeExists() (gas: 26485) -ValoremOptionsClearinghouseUnitTest:testRevert_newOptionType_whenTotalSuppliesAreTooLowToExercise() (gas: 45213) +ValoremOptionsClearinghouseUnitTest:testRevert_newOptionType_whenExerciseWindowTooShort() (gas: 23334) +ValoremOptionsClearinghouseUnitTest:testRevert_newOptionType_whenExpiryWindowTooShort() (gas: 24799) +ValoremOptionsClearinghouseUnitTest:testRevert_newOptionType_whenInvalidAssets() (gas: 21432) +ValoremOptionsClearinghouseUnitTest:testRevert_newOptionType_whenOptionsTypeExists() (gas: 26463) +ValoremOptionsClearinghouseUnitTest:testRevert_newOptionType_whenTotalSuppliesAreTooLowToExercise() (gas: 45169) ValoremOptionsClearinghouseUnitTest:testRevert_option_whenOptionDoesNotExist() (gas: 13355) ValoremOptionsClearinghouseUnitTest:testRevert_position_whenExpiredOption() (gas: 20053) ValoremOptionsClearinghouseUnitTest:testRevert_position_whenTokenNotFound() (gas: 12705) -ValoremOptionsClearinghouseUnitTest:testRevert_redeem_whenCallerDoesNotOwnClaimId() (gas: 249313) -ValoremOptionsClearinghouseUnitTest:testRevert_redeem_whenInvalidClaim() (gas: 9520) +ValoremOptionsClearinghouseUnitTest:testRevert_redeem_whenCallerDoesNotOwnClaimId() (gas: 249243) +ValoremOptionsClearinghouseUnitTest:testRevert_redeem_whenInvalidClaim() (gas: 9498) ValoremOptionsClearinghouseUnitTest:testRevert_setFeeTo_whenNotCurrentFeeTo() (gas: 11919) ValoremOptionsClearinghouseUnitTest:testRevert_setFeeTo_whenZeroAddress() (gas: 11641) -ValoremOptionsClearinghouseUnitTest:testRevert_setFeesEnabled_whenNotFeeTo() (gas: 11821) -ValoremOptionsClearinghouseUnitTest:testRevert_setTokenURIGenerator_whenNotCurrentFeeTo() (gas: 11889) -ValoremOptionsClearinghouseUnitTest:testRevert_setTokenURIGenerator_whenZeroAddress() (gas: 11609) -ValoremOptionsClearinghouseUnitTest:testRevert_sweepFees_whenNotFeeTo() (gas: 16927) -ValoremOptionsClearinghouseUnitTest:testRevert_uri_whenTokenNotFound() (gas: 20262) +ValoremOptionsClearinghouseUnitTest:testRevert_setFeesEnabled_whenNotFeeTo() (gas: 11866) +ValoremOptionsClearinghouseUnitTest:testRevert_setTokenURIGenerator_whenNotCurrentFeeTo() (gas: 11845) +ValoremOptionsClearinghouseUnitTest:testRevert_setTokenURIGenerator_whenZeroAddress() (gas: 11565) +ValoremOptionsClearinghouseUnitTest:testRevert_sweepFees_whenNotFeeTo() (gas: 16905) +ValoremOptionsClearinghouseUnitTest:testRevert_uri_whenTokenNotFound() (gas: 20218) ValoremOptionsClearinghouseUnitTest:testRevert_write_whenAmountWrittenCannotBeZero() (gas: 10676) ValoremOptionsClearinghouseUnitTest:testRevert_write_whenCallerDoesNotOwnClaimId() (gas: 261803) -ValoremOptionsClearinghouseUnitTest:testRevert_write_whenCallerHasNotGrantedSufficientApprovalToengine() (gas: 687624) -ValoremOptionsClearinghouseUnitTest:testRevert_write_whenCallerHoldsInsufficientExerciseAsset() (gas: 687659) +ValoremOptionsClearinghouseUnitTest:testRevert_write_whenCallerHasNotGrantedSufficientApprovalToengine() (gas: 687604) +ValoremOptionsClearinghouseUnitTest:testRevert_write_whenCallerHoldsInsufficientExerciseAsset() (gas: 687639) ValoremOptionsClearinghouseUnitTest:testRevert_write_whenExpiredOption() (gas: 16957) ValoremOptionsClearinghouseUnitTest:testRevert_write_whenInvalidOption() (gas: 13626) -ValoremOptionsClearinghouseUnitTest:test_claim_whenWrittenMultiple() (gas: 387402) +ValoremOptionsClearinghouseUnitTest:test_claim_whenWrittenMultiple() (gas: 387384) ValoremOptionsClearinghouseUnitTest:test_claim_whenWrittenMultipleTimesOnMultipleClaims() (gas: 458225) -ValoremOptionsClearinghouseUnitTest:test_claim_whenWrittenOnce() (gas: 306556) -ValoremOptionsClearinghouseUnitTest:test_exercise() (gas: 383968) -ValoremOptionsClearinghouseUnitTest:test_exercise_whenExercisingMultipleTimes() (gas: 425505) -ValoremOptionsClearinghouseUnitTest:test_feeBalance_whenFeeOff() (gas: 267101) -ValoremOptionsClearinghouseUnitTest:test_feeBalance_whenFeeOn() (gas: 323819) -ValoremOptionsClearinghouseUnitTest:test_feeBalance_whenMinimum() (gas: 401565) -ValoremOptionsClearinghouseUnitTest:test_feeBps() (gas: 5644) -ValoremOptionsClearinghouseUnitTest:test_feeTo() (gas: 7737) -ValoremOptionsClearinghouseUnitTest:test_feesEnabled() (gas: 7684) -ValoremOptionsClearinghouseUnitTest:test_newOptionType() (gas: 130083) +ValoremOptionsClearinghouseUnitTest:test_claim_whenWrittenOnce() (gas: 306539) +ValoremOptionsClearinghouseUnitTest:test_exercise() (gas: 383884) +ValoremOptionsClearinghouseUnitTest:test_exercise_whenExercisingMultipleTimes() (gas: 425331) +ValoremOptionsClearinghouseUnitTest:test_feeBalance_whenFeeOff() (gas: 267067) +ValoremOptionsClearinghouseUnitTest:test_feeBalance_whenFeeOn() (gas: 323643) +ValoremOptionsClearinghouseUnitTest:test_feeBalance_whenMinimum() (gas: 401499) +ValoremOptionsClearinghouseUnitTest:test_feeBps() (gas: 5600) +ValoremOptionsClearinghouseUnitTest:test_feeTo() (gas: 7760) +ValoremOptionsClearinghouseUnitTest:test_feesEnabled() (gas: 7729) +ValoremOptionsClearinghouseUnitTest:test_newOptionType() (gas: 130061) ValoremOptionsClearinghouseUnitTest:test_option_returnsOptionInfo() (gas: 25672) ValoremOptionsClearinghouseUnitTest:test_position_whenFullyExercisedClaim() (gas: 302781) ValoremOptionsClearinghouseUnitTest:test_position_whenOption() (gas: 31743) ValoremOptionsClearinghouseUnitTest:test_position_whenPartiallyExercisedClaim() (gas: 427858) ValoremOptionsClearinghouseUnitTest:test_position_whenUnexercisedClaim() (gas: 263939) -ValoremOptionsClearinghouseUnitTest:test_redeem_whenFullyAssigned() (gas: 335329) -ValoremOptionsClearinghouseUnitTest:test_redeem_whenPartiallyAssigned() (gas: 346683) -ValoremOptionsClearinghouseUnitTest:test_redeem_whenUnassigned() (gas: 238600) -ValoremOptionsClearinghouseUnitTest:test_setFeeToAndAcceptFeeTo() (gas: 29546) -ValoremOptionsClearinghouseUnitTest:test_setFeeToAndAcceptFeeTo_multipleTimes() (gas: 50411) -ValoremOptionsClearinghouseUnitTest:test_setFeesEnabled() (gas: 20581) -ValoremOptionsClearinghouseUnitTest:test_setTokenURIGenerator() (gas: 2035989) -ValoremOptionsClearinghouseUnitTest:test_sweepFees() (gas: 376859) -ValoremOptionsClearinghouseUnitTest:test_sweepFees_whenNoFees() (gas: 53259) +ValoremOptionsClearinghouseUnitTest:test_redeem_whenFullyAssigned() (gas: 335352) +ValoremOptionsClearinghouseUnitTest:test_redeem_whenPartiallyAssigned() (gas: 346711) +ValoremOptionsClearinghouseUnitTest:test_redeem_whenUnassigned() (gas: 238626) +ValoremOptionsClearinghouseUnitTest:test_setFeeToAndAcceptFeeTo() (gas: 29583) +ValoremOptionsClearinghouseUnitTest:test_setFeeToAndAcceptFeeTo_multipleTimes() (gas: 50448) +ValoremOptionsClearinghouseUnitTest:test_setFeesEnabled() (gas: 20761) +ValoremOptionsClearinghouseUnitTest:test_setTokenURIGenerator() (gas: 2035967) +ValoremOptionsClearinghouseUnitTest:test_sweepFees() (gas: 376661) +ValoremOptionsClearinghouseUnitTest:test_sweepFees_whenNoFees() (gas: 53105) ValoremOptionsClearinghouseUnitTest:test_tokenType_returnsClaim() (gas: 253539) ValoremOptionsClearinghouseUnitTest:test_tokenType_returnsNone() (gas: 7980) ValoremOptionsClearinghouseUnitTest:test_tokenType_returnsOption() (gas: 10128) -ValoremOptionsClearinghouseUnitTest:test_tokenURIGenerator() (gas: 9941) -ValoremOptionsClearinghouseUnitTest:test_uri() (gas: 479483) -ValoremOptionsClearinghouseUnitTest:test_write_whenExistingClaim() (gas: 301062) -ValoremOptionsClearinghouseUnitTest:test_write_whenFeeOff() (gas: 259288) -ValoremOptionsClearinghouseUnitTest:test_write_whenNewClaim() (gas: 280534) -ValoremOptionsClearinghousev11UnitTest:testRevert_redeem_whenBeforeExpiryAndClaimIsPartiallyAssigned() (gas: 352537) -ValoremOptionsClearinghousev11UnitTest:testRevert_redeem_whenBeforeExpiryAndClaimIsUnassigned() (gas: 262772) -ValoremOptionsClearinghousev11UnitTest:test_claimAssignmentStatus() (gas: 342942) -ValoremOptionsClearinghousev11UnitTest:test_redeem_whenBeforeExpiryAndClaimIsFullyAssigned() (gas: 312642) \ No newline at end of file +ValoremOptionsClearinghouseUnitTest:test_tokenURIGenerator() (gas: 9963) +ValoremOptionsClearinghouseUnitTest:test_uri() (gas: 479439) +ValoremOptionsClearinghouseUnitTest:test_write_whenExistingClaim() (gas: 300976) +ValoremOptionsClearinghouseUnitTest:test_write_whenFeeOff() (gas: 259335) +ValoremOptionsClearinghouseUnitTest:test_write_whenNewClaim() (gas: 280492) +ValoremOptionsClearinghousev11UnitTest:testRevert_redeem_whenBeforeExpiryAndClaimIsPartiallyAssigned() (gas: 352493) +ValoremOptionsClearinghousev11UnitTest:testRevert_redeem_whenBeforeExpiryAndClaimIsUnassigned() (gas: 262728) +ValoremOptionsClearinghousev11UnitTest:test_nettable() (gas: 3392407) +ValoremOptionsClearinghousev11UnitTest:test_redeem_whenBeforeExpiryAndClaimIsFullyAssigned() (gas: 312607) \ No newline at end of file diff --git a/.gas-snapshot-custom b/.gas-snapshot-custom new file mode 100644 index 0000000..6c2104a --- /dev/null +++ b/.gas-snapshot-custom @@ -0,0 +1,28 @@ +| src/ValoremOptionsClearinghouse.sol:ValoremOptionsClearinghouse contract | | | | | | +|--------------------------------------------------------------------------|-----------------|--------|--------|--------|---------| +| Deployment Cost | Deployment Size | | | | | +| 3361783 | 16902 | | | | | +| Function Name | min | avg | median | max | # calls | +| acceptFeeTo | 585 | 2455 | 2705 | 3825 | 4 | +| balanceOf | 642 | 2598 | 2642 | 2642 | 9578 | +| claim | 831 | 2834 | 2831 | 6269 | 4506 | +| exercise | 513 | 32222 | 20848 | 80748 | 90 | +| feeBalance | 619 | 2611 | 2619 | 2619 | 6034 | +| feeBps | 294 | 294 | 294 | 294 | 36 | +| feeTo | 406 | 2252 | 2406 | 2406 | 235 | +| feesEnabled | 395 | 1061 | 395 | 2395 | 3 | +| net | 9800 | 11455 | 9823 | 15468 | 5 | +| nettable | 3167 | 4418 | 4776 | 6385 | 9 | +| newOptionType | 3505 | 56175 | 97049 | 106049 | 234 | +| option | 1978 | 3231 | 1978 | 9978 | 7 | +| position | 1272 | 5398 | 4444 | 11026 | 61 | +| redeem | 443 | 13903 | 12830 | 24715 | 64 | +| safeTransferFrom | 3522 | 13796 | 19442 | 28802 | 87 | +| setFeeTo | 720 | 2366 | 720 | 24712 | 57 | +| setFeesEnabled | 1726 | 2591 | 2732 | 6957 | 264 | +| setTokenURIGenerator | 2594 | 4671 | 2689 | 8731 | 3 | +| sweepFees | 2863 | 16637 | 15776 | 83936 | 171 | +| tokenType | 724 | 1647 | 1062 | 2724 | 5 | +| tokenURIGenerator | 448 | 1448 | 1448 | 2448 | 2 | +| uri | 9530 | 236879 | 236879 | 464229 | 2 | +| write | 450 | 123614 | 107783 | 242841 | 365 | diff --git a/src/ValoremOptionsClearinghouse.sol b/src/ValoremOptionsClearinghouse.sol index 312401c..aafcc67 100644 --- a/src/ValoremOptionsClearinghouse.sol +++ b/src/ValoremOptionsClearinghouse.sol @@ -11,6 +11,8 @@ import "solmate/utils/FixedPointMathLib.sol"; import "./interfaces/IValoremOptionsClearinghouse.sol"; import "./TokenURIGenerator.sol"; +import "forge-std/console.sol"; // TODO + /*////////////////////////////////////////////////////////////////////////////////////////////////// // // // $$$$$$$$$$ // @@ -45,18 +47,20 @@ contract ValoremOptionsClearinghouse is ERC1155, IValoremOptionsClearinghouse { /** * @notice Stores the state of options written and exercised for a bucket. - * Used in fair exercise assignment assignment to calculate the ratio of - * underlying assets to exercise assets to be transferred to claimants. + * Used in fair exercise assignment to calculate the ratio of underlying + * assets to exercise assets to be transferred to claimants. */ struct Bucket { /// @custom:member amountWritten The number of option contracts written into this bucket. uint112 amountWritten; + // /// @custom:member TODO + // uint112 amountNetted; /// @custom:member amountExercised The number of option contracts exercised from this bucket. uint112 amountExercised; } - /// @notice The bucket information for a given option type. - struct BucketInfo { + /// @notice The bucket state for a given option type. + struct BucketState { /// @custom:member An array of buckets for a given option type. Bucket[] buckets; /// @custom:member An array of bucket indices with collateral available for exercise. @@ -68,9 +72,11 @@ contract ValoremOptionsClearinghouse is ERC1155, IValoremOptionsClearinghouse { * keep track of how many options are written from a claim into each bucket, * in order to correctly perform fair exercise assignment. */ - struct ClaimIndex { + struct ClaimBucketIndex { /// @custom:member amountWritten The amount of option contracts written into claim for given bucket. uint112 amountWritten; + // /// @custom:member TODO + // uint112 amountNetted; /// @custom:member bucketIndex The index of the Bucket into which the options collateral was deposited. uint96 bucketIndex; } @@ -80,9 +86,9 @@ contract ValoremOptionsClearinghouse is ERC1155, IValoremOptionsClearinghouse { /// @custom:member State for this option type. Option option; /// @custom:member State for assignment buckets on this option type. - BucketInfo bucketInfo; + BucketState bucketState; /// @custom:member A mapping to an array of bucket indices per claim token for this option type. - mapping(uint96 => ClaimIndex[]) claimIndices; + mapping(uint96 => ClaimBucketIndex[]) claimBucketIndices; } /*////////////////////////////////////////////////////////////// @@ -180,7 +186,7 @@ contract ValoremOptionsClearinghouse is ERC1155, IValoremOptionsClearinghouse { } /// @inheritdoc IValoremOptionsClearinghouse - function claim(uint256 claimId) public view returns (Claim memory claimInfo) { + function claim(uint256 claimId) public view returns (Claim memory claimState) { (uint160 optionKey, uint96 claimKey) = _decodeTokenId(claimId); if (!_isClaimInitialized(optionKey, claimKey)) { @@ -191,19 +197,21 @@ contract ValoremOptionsClearinghouse is ERC1155, IValoremOptionsClearinghouse { uint256 amountWritten; uint256 amountExercised; + // Setup pointers and put XYZ on the stack OptionTypeState storage optionTypeState = optionTypeStates[optionKey]; - ClaimIndex[] storage claimIndexArray = optionTypeState.claimIndices[claimKey]; - uint256 len = claimIndexArray.length; + ClaimBucketIndex[] storage claimBucketIndexArray = optionTypeState.claimBucketIndices[claimKey]; + uint256 len = claimBucketIndexArray.length; for (uint256 i = 0; i < len; i++) { - ClaimIndex storage claimIndex = claimIndexArray[i]; - Bucket storage bucket = optionTypeState.bucketInfo.buckets[claimIndex.bucketIndex]; - amountWritten += claimIndex.amountWritten; - amountExercised += - FixedPointMathLib.divWadDown((bucket.amountExercised * claimIndex.amountWritten), bucket.amountWritten); + ClaimBucketIndex storage claimBucketIndex = claimBucketIndexArray[i]; + Bucket storage bucket = optionTypeState.bucketState.buckets[claimBucketIndex.bucketIndex]; + amountWritten += claimBucketIndex.amountWritten; + amountExercised += FixedPointMathLib.divWadDown( + (bucket.amountExercised * (claimBucketIndex.amountWritten)), (bucket.amountWritten) + ); } - claimInfo = Claim({ + claimState = Claim({ // Scale the amount written by WAD for consistency. amountWritten: amountWritten * 1e18, amountExercised: amountExercised, @@ -245,15 +253,12 @@ contract ValoremOptionsClearinghouse is ERC1155, IValoremOptionsClearinghouse { uint256 totalExerciseAmount = 0; OptionTypeState storage optionTypeState = optionTypeStates[optionKey]; - ClaimIndex[] storage claimIndices = optionTypeState.claimIndices[claimKey]; - uint256 len = claimIndices.length; - uint256 underlyingAssetAmount = optionTypeState.option.underlyingAmount; - uint256 exerciseAssetAmount = optionTypeState.option.exerciseAmount; + ClaimBucketIndex[] storage claimBucketIndices = optionTypeState.claimBucketIndices[claimKey]; + uint256 len = claimBucketIndices.length; for (uint256 i = 0; i < len; i++) { - (uint256 indexUnderlyingAmount, uint256 indexExerciseAmount) = _getAssetAmountsForClaimIndex( - underlyingAssetAmount, exerciseAssetAmount, optionTypeState, claimIndices, i - ); + (uint256 indexUnderlyingAmount, uint256 indexExerciseAmount) = + _getAssetAmountsForClaimBucketIndex(i, claimBucketIndices, optionTypeState); totalUnderlyingAmount += indexUnderlyingAmount; totalExerciseAmount += indexExerciseAmount; } @@ -267,6 +272,14 @@ contract ValoremOptionsClearinghouse is ERC1155, IValoremOptionsClearinghouse { } } + /// @inheritdoc IValoremOptionsClearinghouse + function nettable(uint256 claimId) public view returns (uint256 amountOptionsNettable) { + Claim memory claimState = claim(claimId); + + // Calculate magnitude of position that is nettable, with a desirable loss of precision, rounding down. // TODO add more context / why + return (claimState.amountWritten - claimState.amountExercised) / 1e18; + } + // // Token information // @@ -452,7 +465,7 @@ contract ValoremOptionsClearinghouse is ERC1155, IValoremOptionsClearinghouse { tokenId = _encodeTokenId(optionKey, nextClaimKey); // Add claim bucket indices. - _addOrUpdateClaimIndex(optionTypeStates[optionKey], nextClaimKey, bucketIndex, amount); + _addOrUpdateClaimBucketIndex(optionTypeStates[optionKey], nextClaimKey, bucketIndex, amount); // Emit events about options written on a new claim. emit OptionsWritten(encodedOptionId, msg.sender, tokenId, amount); @@ -481,7 +494,7 @@ contract ValoremOptionsClearinghouse is ERC1155, IValoremOptionsClearinghouse { } // Add claim bucket indices. - _addOrUpdateClaimIndex(optionTypeStates[optionKey], claimKey, bucketIndex, amount); + _addOrUpdateClaimBucketIndex(optionTypeStates[optionKey], claimKey, bucketIndex, amount); // Emit events about options written on existing claim. emit OptionsWritten(encodedOptionId, msg.sender, tokenId, amount); @@ -497,12 +510,226 @@ contract ValoremOptionsClearinghouse is ERC1155, IValoremOptionsClearinghouse { return tokenId; } + // + // Net Offsetting Positions + // + + /// @inheritdoc IValoremOptionsClearinghouse + // function netNoAssign(uint256 claimId, uint256 amountOptionsToNet) external { + // (uint160 optionKey, uint96 claimKey) = _decodeTokenId(claimId); + + // // Must be a claimId. + // if (claimKey == 0) { + // // TODO revert can't net an option + // } + + // // Must hold this claim. + // uint256 claimBalance = balanceOf[msg.sender][claimId]; + // if (claimBalance != 1) { + // revert CallerDoesNotOwnClaimId(claimId); + // } + + // // Setup pointers to the option and claim state. + // OptionTypeState storage optionTypeState = optionTypeStates[optionKey]; + // Option storage optionRecord = optionTypeState.option; + + // // Must be before expiry (otherwise claim holder should just redeem). + // if (block.timestamp >= optionRecord.expiryTimestamp) { + // // TODO revert can't net on or after expiry + // } + + // // Must not be assigned. + // Claim memory claimState = claim(claimId); + // if (claimState.amountExercised == 0) { + // // TODO revert can't net a Claim that has been assigned + // } + + // // Must hold sufficient options and must be netting a nettable amount of options. + // uint256 optionId = uint256(optionKey) << OPTION_KEY_PADDING; + // uint256 amountOptionsHeld = balanceOf[msg.sender][optionId]; + // uint256 amountOptionsNettable = nettable(claimId); + // if (amountOptionsToNet > amountOptionsHeld || amountOptionsToNet > amountOptionsNettable) { + // revert CallerHoldsInsufficientClaimToNetOptions(claimId, amountOptionsToNet, amountOptionsNettable); + // } + + // // Consume claim -- as much as needed to fulfill desired net amount, returning + // // the asset amounts to be transferred to the netter. + // uint256 underlyingAmountNetted = amountOptionsToNet * optionRecord.underlyingAmount; + + // // TODO Bucket stuff + + // emit ClaimNetted(claimId, optionId, msg.sender, amountOptionsToNet, underlyingAmountNetted); + + // // Burn options, burn the claim (only if fully consumed), and make ERC20 asset transfers. + // _burn(msg.sender, optionId, amountOptionsToNet); + // // _burn(msg.sender, claimId, 1); // TODO + + // SafeTransferLib.safeTransfer(ERC20(optionRecord.underlyingAsset), msg.sender, underlyingAmountNetted); + // } + + /// @inheritdoc IValoremOptionsClearinghouse + function net(uint256 claimId, uint256 amountOptionsToNet) external { + (uint160 optionKey, uint96 claimKey) = _decodeTokenId(claimId); + + // Must be a claimId. + if (claimKey == 0) { + // TODO revert can't net an option + } + + // Must hold this claim. + uint256 claimBalance = balanceOf[msg.sender][claimId]; + if (claimBalance != 1) { + revert CallerDoesNotOwnClaimId(claimId); + } + + // Setup pointers to the option and claim state. + OptionTypeState storage optionTypeState = optionTypeStates[optionKey]; + Option storage optionRecord = optionTypeState.option; + + // Must be before expiry (otherwise claim holder should just redeem). + if (block.timestamp >= optionRecord.expiryTimestamp) { + // TODO revert can't net on or after expiry + } + + // Must hold sufficient options and must be netting a nettable amount of options. + uint256 optionId = uint256(optionKey) << OPTION_KEY_PADDING; + uint256 amountOptionsHeld = balanceOf[msg.sender][optionId]; + uint256 amountOptionsNettable = nettable(claimId); + if (amountOptionsToNet > amountOptionsHeld || amountOptionsToNet > amountOptionsNettable) { + revert CallerHoldsInsufficientClaimToNetOptions(claimId, amountOptionsToNet, amountOptionsNettable); + } + + // Consume claim -- as much as needed to fulfill desired net amount, returning + // the asset amounts to be transferred to the netter. + uint256 underlyingAmountNetted = _netOffsetting(amountOptionsToNet, claimKey, optionTypeState); + console.log("before burn options"); + + // Burn options, burn the claim (only if fully consumed and never assigned), and make ERC20 asset transfers. + _burn(msg.sender, optionId, amountOptionsToNet); + console.log("before burn claim"); + + // TODO may need an additional logic check, if the claim is still marked as initialized but is consumed + // (but under what circumstances is this even possible?) + if (!_isClaimInitialized(optionKey, claimKey)) { + _burn(msg.sender, claimId, 1); + } + + console.log("before transfer underlying"); + + if (underlyingAmountNetted > 0) { + SafeTransferLib.safeTransfer(ERC20(optionRecord.underlyingAsset), msg.sender, underlyingAmountNetted); + } + + emit ClaimNetted(claimId, optionId, msg.sender, amountOptionsToNet, underlyingAmountNetted); + } + + /// @notice TODO + function _netOffsetting(uint256 amountOptionsToNet, uint96 claimKey, OptionTypeState storage optionTypeState) + private + returns (uint256 underlyingAmountNetted) + { + Bucket[] storage buckets = optionTypeState.bucketState.buckets; + ClaimBucketIndex[] storage claimBucketIndices = optionTypeState.claimBucketIndices[claimKey]; + + // Get underlying amount for this option type. + uint256 underlyingAmount = optionTypeState.option.underlyingAmount; + + // Calculate the nettable collateral, based on amount of options that are nettable, iterating + // through each ClaimBucketIndex until enough options have been consumed. + uint256 i = 0; + while (amountOptionsToNet > 0) { + console.log("IIIIII", i); + // Get the ClaimBucketIndex and associated Bucket. + ClaimBucketIndex storage claimBucketIndex = claimBucketIndices[i]; + Bucket storage bucket = buckets[claimBucketIndex.bucketIndex]; + + uint256 claimBucketIndexAmountWritten = claimBucketIndex.amountWritten; + // uint256 claimBucketIndexAmountNetted = claimBucketIndex.amountNetted; + uint256 bucketAmountWritten = bucket.amountWritten; + // uint256 bucketAmountNetted = bucket.amountNetted; + uint256 bucketAmountExercised = bucket.amountExercised; + + // Calculate nettable options, based on this Bucket's available options and this ClaimBucketIndex's + // proportion of options written into the Bucket, less any options already netted. + uint256 availableOptionsInIndex = ( + (bucketAmountWritten - bucketAmountExercised) * (claimBucketIndex.amountWritten) + ) / (bucketAmountWritten); + + // console.log("Index: ", i); + // console.log("CBI amountWritten: ", claimBucketIndex.amountWritten); + // console.log("amountOptionsToNet: ", amountOptionsToNet); + // console.log("availableOptionsInIndex: ", availableOptionsInIndex); + + // Is the available options in this ClaimBucketIndex sufficient to fulfill the full net? + if (availableOptionsInIndex >= amountOptionsToNet) { + console.log("SUFFICIENT"); + // Accumulate the amount of underlying asset to return upon netting completion. + underlyingAmountNetted += amountOptionsToNet * underlyingAmount; + + // Update bucket accounting. + claimBucketIndex.amountWritten = uint112(claimBucketIndexAmountWritten - amountOptionsToNet); + bucket.amountWritten = uint112(bucketAmountWritten - amountOptionsToNet); + + // TODO messy -- could we combine? + if (availableOptionsInIndex == amountOptionsToNet && bucket.amountExercised == 0) { + // Perform "swap and pop" index management. + claimBucketIndices[i] = claimBucketIndices[claimBucketIndices.length - 1]; + claimBucketIndices.pop(); + } + + amountOptionsToNet = 0; + } else { + // This is not sufficient, so we need to consume all of this ClaimBucketIndex then move to the next. + console.log("NOPENOPE"); + + // Accumulate the amount of underlying asset to return upon netting completion. + underlyingAmountNetted += availableOptionsInIndex * underlyingAmount; + + // Update bucket accounting. + claimBucketIndex.amountWritten = uint112(claimBucketIndexAmountWritten - availableOptionsInIndex); + bucket.amountWritten = uint112(bucketAmountWritten - availableOptionsInIndex); + + // Loop management. + amountOptionsToNet -= availableOptionsInIndex; + i++; + } + + // // Calculate the total amount of collateral associated with this ClaimBucketIndex. + // (uint256 indexUnderlyingAmount, uint256 indexExerciseAmount) = _getAssetAmountsForClaimBucketIndex(i - 1, claimBucketIndices, optionTypeState); + + // // Get the ClaimBucketIndex from storage. + // ClaimBucketIndex storage claimBucketIndex = claimBucketIndices[i - 1]; + + // // Is this more or less than the nettable amount of options' worth? + // if (claimBucketIndex.amountWritten > amountOptionsNettable) { + // // Accumulate the amount of underlying and exercise assets in these variables, + // // based on the ClaimBucketIndex's exercise assignment status. + // totalUnderlyingAssetAmount += amountOptionsNettable * optionRecord.underlyingAmount; // TODO wrong, only works if no assignment + // totalExerciseAssetAmount += amountOptionsNettable * optionRecord.exerciseAmount; + + // // Update bucket accounting. + // // TODO + + // return; + // } else { + // + + // // + // totalUnderlyingAssetAmount += indexUnderlyingAmount; + // totalExerciseAssetAmount += indexExerciseAmount; + + // // This zeroes out the array during the redemption process for a gas refund. + // claimBucketIndices.pop(); + // } + } + } + // // Redeem Claims // /// @inheritdoc IValoremOptionsClearinghouse - function redeem(uint256 claimId) external { + function redeem(uint256 claimId) public { (uint160 optionKey, uint96 claimKey) = _decodeTokenId(claimId); // You can't redeem an option. @@ -516,34 +743,35 @@ contract ValoremOptionsClearinghouse is ERC1155, IValoremOptionsClearinghouse { revert CallerDoesNotOwnClaimId(claimId); } - // Setup pointers to the option and claim info. + // Setup pointers to the option and claim state. OptionTypeState storage optionTypeState = optionTypeStates[optionKey]; Option memory optionRecord = optionTypeState.option; - Claim memory claimInfo = claim(claimId); // TODO can we combine this with Claim accounting below? + Claim memory claimState = claim(claimId); // TODO can we combine this with Claim accounting below? // Can't redeem before expiry, unless Claim is fully assigned. - if (optionRecord.expiryTimestamp > block.timestamp && claimInfo.amountWritten > claimInfo.amountExercised) { + if (optionRecord.expiryTimestamp > block.timestamp && claimState.amountWritten > claimState.amountExercised) { + // TODO add claimState.amountNetted revert ClaimTooSoon(claimId, optionRecord.expiryTimestamp); } // Set up accumulators. - ClaimIndex[] storage claimIndices = optionTypeState.claimIndices[claimKey]; - uint256 len = claimIndices.length; - uint256 underlyingAssetAmount = optionTypeState.option.underlyingAmount; - uint256 exerciseAssetAmount = optionTypeState.option.exerciseAmount; + ClaimBucketIndex[] storage claimBucketIndices = optionTypeState.claimBucketIndices[claimKey]; + uint256 len = claimBucketIndices.length; uint256 totalUnderlyingAssetAmount; uint256 totalExerciseAssetAmount; + // Calculate the collateral of the Claim. for (uint256 i = len; i > 0; i--) { - (uint256 indexUnderlyingAmount, uint256 indexExerciseAmount) = _getAssetAmountsForClaimIndex( - underlyingAssetAmount, exerciseAssetAmount, optionTypeState, claimIndices, i - 1 - ); - // Accumulate the amount exercised and unexercised in these variables - // for later multiplication by optionRecord.exerciseAmount/underlyingAmount. + (uint256 indexUnderlyingAmount, uint256 indexExerciseAmount) = + _getAssetAmountsForClaimBucketIndex(i - 1, claimBucketIndices, optionTypeState); + + // Accumulate the amount of underlying and exercise assets in these variables, + // based on the ClaimBucketIndex's exercise assignment status. totalUnderlyingAssetAmount += indexUnderlyingAmount; totalExerciseAssetAmount += indexExerciseAmount; + // This zeroes out the array during the redemption process for a gas refund. - claimIndices.pop(); + claimBucketIndices.pop(); } emit ClaimRedeemed( @@ -712,26 +940,37 @@ contract ValoremOptionsClearinghouse is ERC1155, IValoremOptionsClearinghouse { * @return initialized Whether or not the claim is initialized. */ function _isClaimInitialized(uint160 optionKey, uint96 claimKey) private view returns (bool initialized) { - return optionTypeStates[optionKey].claimIndices[claimKey].length > 0; + return optionTypeStates[optionKey].claimBucketIndices[claimKey].length > 0; } - /// @notice Returns the exercised and unexercised amounts for a given claim index. - function _getAssetAmountsForClaimIndex( - uint256 underlyingAssetAmount, - uint256 exerciseAssetAmount, - OptionTypeState storage optionTypeState, - ClaimIndex[] storage claimIndexArray, - uint256 index + /// @notice Returns (unassigned) amount of the underlying asset and (assigned) amount of the exercise asset for a given ClaimBucketIndex. + function _getAssetAmountsForClaimBucketIndex( + uint256 index, + ClaimBucketIndex[] storage claimBucketIndexArray, + OptionTypeState storage optionTypeState ) private view returns (uint256 underlyingAmount, uint256 exerciseAmount) { - ClaimIndex storage claimIndex = claimIndexArray[index]; - Bucket storage bucket = optionTypeState.bucketInfo.buckets[claimIndex.bucketIndex]; - uint256 claimIndexAmountWritten = claimIndex.amountWritten; + // Get ClaimBucketIndex, Bucket, and amounts written/exercised from storage. + ClaimBucketIndex storage claimBucketIndex = claimBucketIndexArray[index]; + Bucket storage bucket = optionTypeState.bucketState.buckets[claimBucketIndex.bucketIndex]; + uint256 claimBucketIndexAmountWritten = claimBucketIndex.amountWritten; + // uint256 claimBucketIndexAmountNetted = claimBucketIndex.amountNetted; uint256 bucketAmountWritten = bucket.amountWritten; + // uint256 bucketAmountNetted = bucket.amountNetted; uint256 bucketAmountExercised = bucket.amountExercised; - underlyingAmount += ( - (bucketAmountWritten - bucketAmountExercised) * underlyingAssetAmount * claimIndexAmountWritten - ) / bucketAmountWritten; - exerciseAmount += (bucketAmountExercised * exerciseAssetAmount * claimIndexAmountWritten) / bucketAmountWritten; + + // Get underlying amount and exercise amount for this option type. + // TODO ⛽️ better caching + uint256 underlyingAssetAmount = optionTypeState.option.underlyingAmount; + uint256 exerciseAssetAmount = optionTypeState.option.exerciseAmount; + + // Calculate (unassigned, unnetted) amount of underlying asset, based on this ClaimBucketIndex's proportion of claims written into the bucket. + underlyingAmount += // TODO ⛽️ why increment ? + ((bucketAmountWritten - bucketAmountExercised) * underlyingAssetAmount * (claimBucketIndexAmountWritten)) + / (bucketAmountWritten); + + // Calculate (assigned) amount of exercise asset, based on this ClaimBucketIndex's proportion of claims written into the bucket. + exerciseAmount += + (bucketAmountExercised * exerciseAssetAmount * (claimBucketIndexAmountWritten)) / (bucketAmountWritten); } // @@ -788,33 +1027,38 @@ contract ValoremOptionsClearinghouse is ERC1155, IValoremOptionsClearinghouse { uint112 amount ) private { // Setup pointers to buckets and buckets with collateral available for exercise. - Bucket[] storage buckets = optionTypeState.bucketInfo.buckets; - uint96[] storage unexercisedBucketIndices = optionTypeState.bucketInfo.unexercisedBucketIndices; + Bucket[] storage buckets = optionTypeState.bucketState.buckets; + uint96[] storage unexercisedBucketIndices = optionTypeState.bucketState.unexercisedBucketIndices; uint96 numUnexercisedBuckets = uint96(unexercisedBucketIndices.length); uint96 exerciseIndex = uint96(optionRecord.settlementSeed % numUnexercisedBuckets); while (amount > 0) { // Get the claim bucket to assign exercise to. uint96 bucketIndex = unexercisedBucketIndices[exerciseIndex]; - Bucket storage bucketInfo = buckets[bucketIndex]; + Bucket storage bucket = buckets[bucketIndex]; - uint112 amountAvailable = bucketInfo.amountWritten - bucketInfo.amountExercised; + uint112 amountAvailable = bucket.amountWritten - bucket.amountExercised; uint112 amountPresentlyExercised = 0; + + console.log("Assignment --- Bucket", bucketIndex); + console.log("Amount available", amountAvailable, "Amount exercised", bucket.amountExercised); + if (amountAvailable <= amount) { - // Bucket is fully exercised/assigned. + // Bucket is fully assigned exercise. amount -= amountAvailable; amountPresentlyExercised = amountAvailable; + // Perform "swap and pop" index management. numUnexercisedBuckets--; uint96 overwrite = unexercisedBucketIndices[numUnexercisedBuckets]; unexercisedBucketIndices[exerciseIndex] = overwrite; unexercisedBucketIndices.pop(); } else { - // Bucket is partially exercised/assigned. + // Bucket is partially assigned exercise. amountPresentlyExercised = amount; amount = 0; } - bucketInfo.amountExercised += amountPresentlyExercised; + bucket.amountExercised += amountPresentlyExercised; emit BucketAssignedExercise(optionId, bucketIndex, amountPresentlyExercised); @@ -825,17 +1069,17 @@ contract ValoremOptionsClearinghouse is ERC1155, IValoremOptionsClearinghouse { } } - /// @notice Adds or updates a bucket as needed for a given option type and amount written. + /// @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) { // Setup pointers to buckets. - BucketInfo storage bucketInfo = optionTypeState.bucketInfo; - Bucket[] storage buckets = bucketInfo.buckets; + BucketState storage bucketState = optionTypeState.bucketState; + Bucket[] storage buckets = bucketState.buckets; uint96 writtenBucketIndex = uint96(buckets.length); if (buckets.length == 0) { // Add a new bucket for this option type, because none exist. - buckets.push(Bucket(amount, 0)); - bucketInfo.unexercisedBucketIndices.push(writtenBucketIndex); + buckets.push(Bucket({amountWritten: amount, amountExercised: 0})); + bucketState.unexercisedBucketIndices.push(writtenBucketIndex); return writtenBucketIndex; } @@ -846,8 +1090,8 @@ contract ValoremOptionsClearinghouse is ERC1155, IValoremOptionsClearinghouse { if (currentBucket.amountExercised != 0) { // Add a new bucket to this option type, because the last was partially or fully exercised. - buckets.push(Bucket(amount, 0)); - bucketInfo.unexercisedBucketIndices.push(writtenBucketIndex); + buckets.push(Bucket({amountWritten: amount, amountExercised: 0})); + bucketState.unexercisedBucketIndices.push(writtenBucketIndex); } else { // Write to the existing unexercised bucket. currentBucket.amountWritten += amount; @@ -857,28 +1101,28 @@ contract ValoremOptionsClearinghouse is ERC1155, IValoremOptionsClearinghouse { return writtenBucketIndex; } - /// @notice Updates claimIndices for a given claim key. - function _addOrUpdateClaimIndex( + /// @notice Adds or updates a ClaimBucketIndex as needed for a given claim key and amount written. + function _addOrUpdateClaimBucketIndex( OptionTypeState storage optionTypeState, uint96 claimKey, uint96 bucketIndex, uint112 amount ) private { - ClaimIndex[] storage claimIndices = optionTypeState.claimIndices[claimKey]; - uint256 arrayLength = claimIndices.length; + ClaimBucketIndex[] storage claimBucketIndices = optionTypeState.claimBucketIndices[claimKey]; + uint256 arrayLength = claimBucketIndices.length; // If the array is empty, create a new index and return. if (arrayLength == 0) { - claimIndices.push(ClaimIndex({amountWritten: amount, bucketIndex: bucketIndex})); + claimBucketIndices.push(ClaimBucketIndex({amountWritten: amount, bucketIndex: bucketIndex})); return; } - ClaimIndex storage lastIndex = claimIndices[arrayLength - 1]; + ClaimBucketIndex storage lastIndex = claimBucketIndices[arrayLength - 1]; // If we are writing to an index that doesn't yet exist, create it and return. if (lastIndex.bucketIndex < bucketIndex) { - claimIndices.push(ClaimIndex({amountWritten: amount, bucketIndex: bucketIndex})); + claimBucketIndices.push(ClaimBucketIndex({amountWritten: amount, bucketIndex: bucketIndex})); return; } diff --git a/src/interfaces/IValoremOptionsClearinghouse.sol b/src/interfaces/IValoremOptionsClearinghouse.sol index 8e1a3cd..80c19a5 100644 --- a/src/interfaces/IValoremOptionsClearinghouse.sol +++ b/src/interfaces/IValoremOptionsClearinghouse.sol @@ -80,6 +80,21 @@ interface IValoremOptionsClearinghouse { uint256 indexed optionId, uint256 indexed claimId, uint96 indexed bucketIndex, uint112 amount ); + // TODO do we need an additional event, BucketNettedFrom ? + + // + // Net events + // + + // TODO new + event ClaimNetted( + uint256 indexed claimId, + uint256 indexed optionId, + address netter, + uint256 indexed optionsAmountNetted, + uint256 underlyingAmountNetted + ); + // // Redeem events // @@ -204,6 +219,11 @@ interface IValoremOptionsClearinghouse { */ error CallerHoldsInsufficientOptions(uint256 optionId, uint112 amount); + // TODO new + error CallerHoldsInsufficientClaimToNetOptions( + uint256 claimId, uint256 amountOptionsRequested, uint256 amountOptionsNettable + ); + /** * @notice Claims cannot be redeemed before expiry. * @param claimId Supplied claim ID. @@ -305,7 +325,7 @@ interface IValoremOptionsClearinghouse { uint96 nextClaimKey; } - // TODO add early redeem + // TODO add info about early redeem /** * @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 @@ -317,6 +337,8 @@ interface IValoremOptionsClearinghouse { struct Claim { /// @custom:member amountWritten The number of option contracts written against this claim expressed as a 1e18 scalar value. uint256 amountWritten; + /// @custom:member TODO + // uint256 amountNetted; /// @custom:member amountExercised The amount of option contracts exercised against this claim expressed as a 1e18 scalar value. uint256 amountExercised; /// @custom:member optionId The option ID of the option type this claim is for. @@ -367,6 +389,11 @@ interface IValoremOptionsClearinghouse { */ function position(uint256 tokenId) external view returns (Position memory positionInfo); + /** + * // TODO + */ + function nettable(uint256 claimId) external view returns (uint256 amountOptionsNettable); + // // Token information // @@ -483,9 +510,14 @@ interface IValoremOptionsClearinghouse { //////////////////////////////////////////////////////////////*/ /** - * TODO + * @notice TODO + */ + function net(uint256 claimId, uint256 amountOptionsToNet) external; + + /** + * @notice TODO */ - // function net(uint256 optionid) external; + // function netNoAssign(uint256 claimId, uint256 amountOptionsToNet) external; /*////////////////////////////////////////////////////////////// // Redeem Claims diff --git a/test/ValoremOptionsClearinghouse-v1.1.unit.t.sol b/test/ValoremOptionsClearinghouse-v1.1.unit.t.sol index 86c8c88..1a36180 100644 --- a/test/ValoremOptionsClearinghouse-v1.1.unit.t.sol +++ b/test/ValoremOptionsClearinghouse-v1.1.unit.t.sol @@ -2,87 +2,124 @@ // Valorem Labs Inc. (c) 2023. pragma solidity 0.8.16; -import "solmate/utils/FixedPointMathLib.sol"; import "forge-std/Test.sol"; +import "forge-std/console.sol"; import {IERC20} from "forge-std/interfaces/IERC20.sol"; +import "solmate/utils/FixedPointMathLib.sol"; import "./utils/BaseClearinghouseTest.sol"; /// @notice Unit tests for ValoremOptionsClearinghouse v1.1.0 contract ValoremOptionsClearinghousev11UnitTest is BaseClearinghouseTest { + address private constant writer1 = ALICE; + address private constant writer2 = BOB; + address private constant writer3 = CAROL; + address private constant exerciser1 = userD; + address private constant exerciser2 = userE; + + // Scenario A, writer2 nets and redeems, writer1 and writer3 redeem + // Scenario B, writer2 nets and redeems, writer1 and writer3 redeem + // Scenario C, writer2 nets when not assigned + // Scenario D, writer2 nets after assignment + + uint256 private optionId; + uint256 private claimId1; + uint256 private claimId2; + uint256 private claimId3; + + IValoremOptionsClearinghouse.Claim private claimState1; + IValoremOptionsClearinghouse.Claim private claimState2; + IValoremOptionsClearinghouse.Claim private claimState3; + + IValoremOptionsClearinghouse.Position private position1; + IValoremOptionsClearinghouse.Position private position2; + IValoremOptionsClearinghouse.Position private position3; + + uint256 expectedUnderlyingReturnedFromRedeemClaim1; + uint256 expectedExerciseReturnedFromRedeemClaim1; + + uint256 expectedUnderlyingReturnedFromNetClaim2; + uint256 expectedUnderlyingReturnedFromRedeemClaim2; + uint256 expectedExerciseReturnedFromRedeemClaim2; + + uint256 expectedUnderlyingReturnedFromRedeemClaim3; + uint256 expectedExerciseReturnedFromRedeemClaim3; + + uint256 private constant DAWN = 1_000_000 seconds; + /*////////////////////////////////////////////////////////////// // Clearinghouse v1.1.0 //////////////////////////////////////////////////////////////*/ - function test_claimAssignmentStatus() public { - uint112 amountWritten = 5; - uint256 expectedFee = _calculateFee(testUnderlyingAmount * amountWritten); - uint256 expectedClaimId = testOptionId + 1; + // function test_claimAssignmentStatus() public { + // uint112 amountWritten = 5; + // uint256 expectedFee = _calculateFee(testUnderlyingAmount * amountWritten); + // uint256 expectedClaimId = testOptionId + 1; - vm.prank(ALICE); - uint256 claimId = engine.write(testOptionId, amountWritten); - - // Post-write conditions - assertEq(claimId, expectedClaimId, "claimId"); - assertEq(engine.balanceOf(ALICE, claimId), 1, "Alice Claim NFT"); - assertEq(engine.balanceOf(ALICE, testOptionId), amountWritten, "Alice Option tokens"); - assertEq( - IERC20(testUnderlyingAsset).balanceOf(ALICE), - STARTING_BALANCE_WETH - (testUnderlyingAmount * amountWritten) - expectedFee, - "Alice underlying" - ); - assertEq(IERC20(testExerciseAsset).balanceOf(ALICE), STARTING_BALANCE, "Alice exercise"); // no change - assertEq(engine.feeBalance(testUnderlyingAsset), expectedFee, "Fee balance underlying"); - assertEq(engine.feeBalance(testExerciseAsset), 0, "Fee balance exercise"); // no fee assessed on exercise asset during write() - - // Unassigned - // IValoremOptionsClearinghouse.Claim memory unassigned = engine.claim(claimId); - // emit log("Unassigned Claim ---------"); - // emit log_named_string("amountWritten", pp(unassigned.amountWritten, 18, 0)); - // emit log_named_string("amountExercised", pp(unassigned.amountExercised, 18, 0)); - // uint256 assignmentPercentage = unassigned.amountExercised / unassigned.amountWritten; - // emit log_named_uint("percentage", assignmentPercentage); - // if amountExercised == 0, claim is unassigned - - // Partially Assigned - vm.prank(ALICE); - engine.safeTransferFrom(ALICE, BOB, testOptionId, 5, ""); + // vm.prank(ALICE); + // uint256 claimId = engine.write(testOptionId, amountWritten); - vm.prank(BOB); - engine.exercise(testOptionId, 1); + // // Post-write conditions + // assertEq(claimId, expectedClaimId, "claimId"); + // assertEq(engine.balanceOf(ALICE, claimId), 1, "Alice Claim NFT"); + // assertEq(engine.balanceOf(ALICE, testOptionId), amountWritten, "Alice Option tokens"); + // assertEq( + // IERC20(testUnderlyingAsset).balanceOf(ALICE), + // STARTING_BALANCE_WETH - (testUnderlyingAmount * amountWritten) - expectedFee, + // "Alice underlying" + // ); + // assertEq(IERC20(testExerciseAsset).balanceOf(ALICE), STARTING_BALANCE, "Alice exercise"); // no change + // assertEq(engine.feeBalance(testUnderlyingAsset), expectedFee, "Fee balance underlying"); + // assertEq(engine.feeBalance(testExerciseAsset), 0, "Fee balance exercise"); // no fee assessed on exercise asset during write() - // IValoremOptionsClearinghouse.Claim memory partiallyAssigned = engine.claim(claimId); - // emit log("Partially Assigned Claim ---------"); - // emit log_named_string("amountWritten", pp(partiallyAssigned.amountWritten, 18, 0)); - // emit log_named_string("amountExercised", pp(partiallyAssigned.amountExercised, 18, 0)); - // assignmentPercentage = partiallyAssigned.amountExercised / partiallyAssigned.amountWritten; - // emit log_named_uint("percentage", assignmentPercentage); // TODO use scalar - // if amountExercised > 0 && amountWritten > amountExercised, claim is partially assigned + // Unassigned + // IValoremOptionsClearinghouse.Claim memory unassigned = engine.claim(claimId); + // emit log("Unassigned Claim ---------"); + // emit log_named_string("amountWritten", pp(unassigned.amountWritten, 18, 0)); + // emit log_named_string("amountExercised", pp(unassigned.amountExercised, 18, 0)); + // uint256 assignmentPercentage = unassigned.amountExercised / unassigned.amountWritten; + // emit log_named_uint("percentage", assignmentPercentage); + // if amountExercised == 0, claim is unassigned - // Fully Assigned - vm.prank(BOB); - engine.exercise(testOptionId, 4); - - // IValoremOptionsClearinghouse.Claim memory fullyAssigned = engine.claim(claimId); - // emit log("Fully Assigned Claim ---------"); - // emit log_named_string("amountWritten", pp(fullyAssigned.amountWritten, 18, 0)); - // emit log_named_string("amountExercised", pp(fullyAssigned.amountExercised, 18, 0)); - // assignmentPercentage = fullyAssigned.amountExercised / fullyAssigned.amountWritten; - // emit log_named_uint("percentage", assignmentPercentage); - // if amountWritten == amountExercised, claim is fully assigned - } + // // Partially Assigned + // vm.prank(ALICE); + // engine.safeTransferFrom(ALICE, BOB, testOptionId, 5, ""); - /*////////////////////////////////////////////////////////////// - // net(uint256 optionId) external - //////////////////////////////////////////////////////////////*/ + // vm.prank(BOB); + // engine.exercise(testOptionId, 1); + + // IValoremOptionsClearinghouse.Claim memory partiallyAssigned = engine.claim(claimId); + // emit log("Partially Assigned Claim ---------"); + // emit log_named_string("amountWritten", pp(partiallyAssigned.amountWritten, 18, 0)); + // emit log_named_string("amountExercised", pp(partiallyAssigned.amountExercised, 18, 0)); + // assignmentPercentage = partiallyAssigned.amountExercised / partiallyAssigned.amountWritten; + // emit log_named_uint("percentage", assignmentPercentage); // TODO use scalar + // if amountExercised > 0 && amountWritten > amountExercised, claim is partially assigned + + // // Fully Assigned + // vm.prank(BOB); + // engine.exercise(testOptionId, 4); + + // IValoremOptionsClearinghouse.Claim memory fullyAssigned = engine.claim(claimId); + // emit log("Fully Assigned Claim ---------"); + // emit log_named_string("amountWritten", pp(fullyAssigned.amountWritten, 18, 0)); + // emit log_named_string("amountExercised", pp(fullyAssigned.amountExercised, 18, 0)); + // assignmentPercentage = fullyAssigned.amountExercised / fullyAssigned.amountWritten; + // emit log_named_uint("percentage", assignmentPercentage); + // if amountWritten == amountExercised, claim is fully assigned + // } - // TODO + // /*////////////////////////////////////////////////////////////// + // // net(uint256 optionId) external + // //////////////////////////////////////////////////////////////*/ - // function test_close() public { + // // TODO + + // function test_net_whenUnassigned() public { // uint256 balanceA = ERC20A.balanceOf(ALICE); // uint256 balanceB = ERC20B.balanceOf(ALICE); - // // Alice writes 10 options + // // Alice writes 10 Options // vm.startPrank(ALICE); // uint256 optionId = engine.newOptionType({ // underlyingAsset: address(ERC20A), @@ -105,75 +142,54 @@ contract ValoremOptionsClearinghousev11UnitTest is BaseClearinghouseTest { // ); // assertEq(ERC20B.balanceOf(ALICE), balanceB, "Alice exercise asset before"); - // // Alice closes offsetting positions after no Longs have been exercised - // engine.close(optionId); + // // Alice nets offsetting positions after no Options have been exercised + // engine.netNoAssign(claimId); // assertEq(engine.balanceOf(ALICE, optionId), 0, "Alice option tokens after"); // assertEq(engine.balanceOf(ALICE, claimId), 0, "Alice claim tokens after"); - // assertEq(ERC20A.balanceOf(ALICE), balanceA, "Alice underlying asset after"); + // assertEq(ERC20A.balanceOf(ALICE), balanceA - _calculateFee(expectedWriteAmount), "Alice underlying asset after"); // still less write fee // assertEq(ERC20B.balanceOf(ALICE), balanceB, "Alice exercise asset after"); // } - // function test_close_whenPartiallyExercisedByWriter() public { - // uint256 balanceA = ERC20A.balanceOf(ALICE); - // uint256 balanceB = ERC20B.balanceOf(ALICE); - - // // Alice writes 10 options - // vm.startPrank(ALICE); - // uint256 optionId = engine.newOptionType({ - // underlyingAsset: address(ERC20A), - // underlyingAmount: 1 ether, - // exerciseAsset: address(ERC20B), - // exerciseAmount: 8 ether, - // exerciseTimestamp: uint40(block.timestamp), - // expiryTimestamp: uint40(block.timestamp + 30 days) + // function test_netNoAssign() public { + // // writer1 writes 2.15 options, of which exerciser1 takes 1 + // vm.startPrank(writer1); + // optionId = engine.newOptionType({ + // underlyingAsset: address(WETHLIKE), + // underlyingAmount: 1e12, + // exerciseAsset: address(USDCLIKE), + // exerciseAmount: 1750, + // exerciseTimestamp: uint40(DAWN + 1 days), + // expiryTimestamp: uint40(DAWN + 8 days) // }); - // uint256 claimId = engine.write(optionId, 10); + // claimId1 = engine.write(optionId, 2.15e6); + // engine.safeTransferFrom(writer1, exerciser1, optionId, 1e6, ""); - // uint256 expectedWriteAmount = 10 * 1 ether; - // uint256 expectedExerciseAmount = 3 * 8 ether; + // // Net some of it (0.2e6) + // uint256 uBalance = WETHLIKE.balanceOf(writer1); + // uint256 eBalance = USDCLIKE.balanceOf(writer1); - // assertEq(engine.balanceOf(ALICE, optionId), 10, "Alice option tokens before"); - // assertEq(engine.balanceOf(ALICE, claimId), 1, "Alice claim tokens before"); - // assertEq( - // ERC20A.balanceOf(ALICE), - // balanceA - expectedWriteAmount - _calculateFee(expectedWriteAmount), - // "Alice underlying asset before" - // ); - // assertEq(ERC20B.balanceOf(ALICE), balanceB, "Alice exercise asset before"); + // engine.netNoAssign(claimId1, 0.2e6); + // assertEq(WETHLIKE.balanceOf(writer1), uBalance + (0.2e6 * 1e12)); + // assertEq(USDCLIKE.balanceOf(writer1), eBalance); - // // Alice exercises 3 Longs - // engine.exercise(optionId, 3); + // // Redeem rest of it (0.95e6) + // uBalance = WETHLIKE.balanceOf(writer1); + // eBalance = USDCLIKE.balanceOf(writer1); - // assertEq(engine.balanceOf(ALICE, optionId), 7, "Alice option tokens after exercise"); - // assertEq(engine.balanceOf(ALICE, claimId), 0, "Alice claim tokens after exercise"); - // assertEq( - // ERC20A.balanceOf(ALICE), - // balanceA - expectedWriteAmount - _calculateFee(expectedWriteAmount) + (3 * 1 ether), - // "Alice underlying asset after exercise" - // ); - // assertEq( - // ERC20B.balanceOf(ALICE), - // balanceB - expectedExerciseAmount - _calculateFee(expectedExerciseAmount), - // "Alice exercise asset after exercise" - // ); - - // // Alice closes remaining 7 Longs and claims collateral back from 3 Longs that she exercised - // engine.close(optionId); - - // assertEq(engine.balanceOf(ALICE, optionId), 0, "Alice option tokens after close"); - // assertEq(engine.balanceOf(ALICE, claimId), 0, "Alice claim tokens after close"); - // assertEq(ERC20A.balanceOf(ALICE), balanceA, "Alice underlying asset after close"); - // assertEq(ERC20B.balanceOf(ALICE), balanceB, "Alice exercise asset after close"); + // vm.warp(DAWN + 8 days); + // engine.redeem(claimId1); + // assertEq(WETHLIKE.balanceOf(writer1), uBalance + (0.95e6 * 1e12)); + // assertEq(USDCLIKE.balanceOf(writer1), eBalance); // } - // function test_close_whenPartiallyExercisedByOtherHolder() public { + // function test_net_whenPartiallyAssigned() public { // uint256 aliceBalanceA = ERC20A.balanceOf(ALICE); // uint256 aliceBalanceB = ERC20B.balanceOf(ALICE); // uint256 bobBalanceA = ERC20A.balanceOf(BOB); // uint256 bobBalanceB = ERC20B.balanceOf(BOB); - // // Alice writes 10 options + // // Alice writes 10 Options // vm.startPrank(ALICE); // uint256 optionId = engine.newOptionType({ // underlyingAsset: address(ERC20A), @@ -197,14 +213,14 @@ contract ValoremOptionsClearinghousev11UnitTest is BaseClearinghouseTest { // ); // assertEq(ERC20B.balanceOf(ALICE), aliceBalanceB, "Alice exercise asset before"); - // // Alice transfers 3 Longs to Bob + // // Alice transfers 3 Options to Bob // engine.safeTransferFrom(ALICE, BOB, optionId, 3, ""); // vm.stopPrank(); // assertEq(engine.balanceOf(ALICE, optionId), 7, "Alice option tokens after transfer"); // assertEq(engine.balanceOf(ALICE, claimId), 1, "Alice claim tokens after transfer"); - // // Bob exercises 3 Longs + // // Bob exercises 3 Options // vm.prank(BOB); // engine.exercise(optionId, 3); @@ -213,33 +229,819 @@ contract ValoremOptionsClearinghousev11UnitTest is BaseClearinghouseTest { // aliceBalanceA - expectedWriteAmount - _calculateFee(expectedWriteAmount), // "Alice underlying asset after exercise" // ); - // assertEq( - // ERC20B.balanceOf(ALICE), - // aliceBalanceB, - // "Alice exercise asset after exercise" - // ); - // assertEq( - // ERC20A.balanceOf(BOB), - // bobBalanceA + (3 * 1 ether), - // "Bob underlying asset after exercise" - // ); + // assertEq(ERC20B.balanceOf(ALICE), aliceBalanceB, "Alice exercise asset after exercise"); + // assertEq(ERC20A.balanceOf(BOB), bobBalanceA + (3 * 1 ether), "Bob underlying asset after exercise"); // assertEq( // ERC20B.balanceOf(BOB), // bobBalanceB - expectedExerciseAmount - _calculateFee(expectedExerciseAmount), // "Bob exercise asset after exercise" // ); - // // Alice closes remaining 7 Longs and claims collateral back from 3 Longs that Bob exercised - // engine.close(optionId); + // // Alice closes remaining 7 Options and gets collateral back from 3 Options that Bob exercised + // engine.net(claimId); // assertEq(engine.balanceOf(ALICE, optionId), 0, "Alice option tokens after close"); // assertEq(engine.balanceOf(ALICE, claimId), 0, "Alice claim tokens after close"); - // assertEq(ERC20A.balanceOf(ALICE), aliceBalanceA - expectedWriteAmount - _calculateFee(expectedWriteAmount) + (3 * 1 ether), "Alice underlying asset after close"); + // assertEq( + // ERC20A.balanceOf(ALICE), + // aliceBalanceA - expectedWriteAmount - _calculateFee(expectedWriteAmount) + (3 * 1 ether), + // "Alice underlying asset after close" + // ); // assertEq(ERC20B.balanceOf(ALICE), aliceBalanceB + expectedExerciseAmount, "Alice exercise asset after close"); - // assertEq(ERC20A.balanceOf(BOB), bobBalanceA, "Bob underlying asset after close"); - // assertEq(ERC20B.balanceOf(BOB), bobBalanceB, "Bob exercise asset after close"); + // assertEq(ERC20A.balanceOf(BOB), bobBalanceA + (3 * 1 ether), "Bob underlying asset after close"); + // assertEq( + // ERC20B.balanceOf(BOB), + // bobBalanceB - expectedExerciseAmount - _calculateFee(expectedExerciseAmount), + // "Bob exercise asset after close" + // ); // } + // Scenario A, Option Type exerciseAmount is 1750 + // Scenario B is the same as A, but will have Option Type with different settlementSeed -- to explore a different assignment path + // Scenario C will be very basic, only 1 writer, 1 claim, no assignment -- to burn the Claim during net + // Scenario D is the same as C, but will include assignment -- to explore single Claim netting + redemption + + function test_net_scenarioA_whenThreeWritersTwoExercisesAndNetSufficient() public { + // t = 1 + + // writer1 writes 1.15 options, of which exerciser1 takes 1 + vm.startPrank(writer1); + optionId = engine.newOptionType({ + underlyingAsset: address(WETHLIKE), + underlyingAmount: 1e12, + exerciseAsset: address(USDCLIKE), + exerciseAmount: 1750, + exerciseTimestamp: uint40(DAWN + 1 days), + expiryTimestamp: uint40(DAWN + 8 days) + }); + claimId1 = engine.write(optionId, 0.01e6); // write 0.01 options, not taken + engine.write(claimId1, 0.04e6); // write 0.04 options, not taken + engine.write(claimId1, 0.95e6); // write 0.95 options, taken + engine.safeTransferFrom(writer1, exerciser1, optionId, 1e6, ""); + engine.write(claimId1, 0.15e6); // write 0.15 options, not taken + vm.stopPrank(); + + // bucket state -- 1 claim, 1 bucket + // option type inventory check + assertEq(engine.balanceOf(writer1, optionId), 0.15e6, "writer1 option balance t1"); + assertEq(engine.balanceOf(exerciser1, optionId), 1e6, "exerciser1 option balance t1"); + + // t = 2 + + // writer2 writes 0.1 options, exerciser2 takes all + vm.startPrank(writer2); + claimId2 = engine.write(optionId, 0.1e6); + engine.safeTransferFrom(writer2, exerciser2, optionId, 0.1e6, ""); + vm.stopPrank(); + + // bucket state -- 2 claims, 1 bucket + // inventory check + assertEq(engine.balanceOf(writer1, optionId), 0.15e6, "writer1 option balance t2"); + assertEq(engine.balanceOf(exerciser1, optionId), 1e6, "exerciser1 option balance t2"); + assertEq(engine.balanceOf(writer2, optionId), 0, "writer2 option balance t2"); + assertEq(engine.balanceOf(exerciser2, optionId), 0.1e6, "exerciser2 option balance t2"); + + // t = 3 + vm.warp(DAWN + 1 days); + + // FIRST EXERCISE -- Bob exercises his 1 option + vm.prank(exerciser1); + engine.exercise(optionId, 1e6); + + // inventory check + assertEq(engine.balanceOf(writer1, optionId), 0.15e6, "writer1 option balance t3"); + assertEq(engine.balanceOf(exerciser1, optionId), 0, "exerciser1 option balance t3"); + assertEq(engine.balanceOf(writer2, optionId), 0, "writer2 option balance t3"); + assertEq(engine.balanceOf(exerciser2, optionId), 0.1e6, "exerciser2 option balance t3"); + + // t = 4 + + // writer3 writes 0.01 options, no taker + vm.prank(writer3); + claimId3 = engine.write(optionId, 0.01e6); + + // writer1 writes 0.85 options, no taker + vm.prank(writer1); + engine.write(claimId1, 0.85e6); + + // writer2 writes 0.01 options, no taker + vm.prank(writer2); + engine.write(claimId2, 0.01e6); + + // bucket state -- + // B1 contains {Claim 1, Claim 2} + // B2 contains {Claim 3, Claim 1, Claim 2} + + // inventory check + assertEq(engine.balanceOf(writer1, optionId), 1e6, "writer1 option balance t4"); + assertEq(engine.balanceOf(exerciser1, optionId), 0, "exerciser1 option balance t4"); + assertEq(engine.balanceOf(writer2, optionId), 0.01e6, "writer2 option balance t4"); + assertEq(engine.balanceOf(exerciser2, optionId), 0.1e6, "exerciser2 option balance t4"); + assertEq(engine.balanceOf(writer3, optionId), 0.01e6, "writer3 option balance t4"); + + // t = 5 + + // SECOND EXERCISE + vm.prank(exerciser2); + engine.exercise(optionId, 0.05e6); + + // inventory check + assertEq(engine.balanceOf(writer1, optionId), 1e6, "writer1 option balance t5"); + assertEq(engine.balanceOf(exerciser1, optionId), 0, "exerciser1 option balance t5"); + assertEq(engine.balanceOf(writer2, optionId), 0.01e6, "writer2 option balance t5"); + assertEq(engine.balanceOf(exerciser2, optionId), 0.05e6, "exerciser2 option balance t5"); + assertEq(engine.balanceOf(writer3, optionId), 0.01e6, "writer3 option balance t5"); + + // t = 6 + + // writer1 writes 1 option, no takers + vm.prank(writer1); + engine.write(claimId1, 1e6); + + // bucket state -- + // B1 contains {Claim 1, Claim 2} + // B2 contains {Claim 3, Claim 1, Claim 2} + // B3 contains {Claim 1} + + // inventory check + assertEq(engine.balanceOf(writer1, optionId), 2e6, "writer1 option balance t6"); + assertEq(engine.balanceOf(exerciser1, optionId), 0, "exerciser1 option balance t6"); + assertEq(engine.balanceOf(writer2, optionId), 0.01e6, "writer2 option balance t6"); + assertEq(engine.balanceOf(exerciser2, optionId), 0.05e6, "exerciser2 option balance t6"); + assertEq(engine.balanceOf(writer3, optionId), 0.01e6, "writer3 option balance t6"); + + claimState1 = engine.claim(claimId1); + claimState2 = engine.claim(claimId2); + claimState3 = engine.claim(claimId3); + + console.log("Scenario A ------------------"); + // console.log("Claim 1 ----------"); + // console.log("amountWritten", claimState1.amountWritten, "amountExercised", claimState1.amountExercised); + // console.log("Claim 2 ----------"); + // console.log("amountWritten", claimState2.amountWritten, "amountExercised", claimState2.amountExercised); + // console.log("Claim 3 ----------"); + // console.log("amountWritten", claimState3.amountWritten, "amountExercised", claimState3.amountExercised); + + // + // Summary of current assignment status + // + + // Scenario A -- writer2 has a claim worth 0.029425287356321839080460 options + + // Options written, by claim + // 3000000000000000000000000+110000000000000000000000+10000000000000000000000 + // = 3120000.000000000000000000 + // for a total of 3.12 options written (.01+.04+.95+.15+.1+.01+.85+.01+1) + + // Claim assignment status + // 968850574712643678160919+80574712643678160919540+574712643678160919540 + // = 1049999.999999999999999999 + // for a total of ~1.05 options exercised (1 + .05) + + // + // Finally, we can test nettable() + // + + uint256 nettableWriter1A = engine.nettable(claimId1); + uint256 nettableWriter2A = engine.nettable(claimId2); + uint256 nettableWriter3A = engine.nettable(claimId3); + assertEq( + nettableWriter1A, (claimState1.amountWritten - claimState1.amountExercised) / 1e18, "A -- claim 1 nettable" + ); + assertEq( + nettableWriter2A, (claimState2.amountWritten - claimState2.amountExercised) / 1e18, "A -- claim 2 nettable" + ); + assertEq( + nettableWriter3A, (claimState3.amountWritten - claimState3.amountExercised) / 1e18, "A -- claim 3 nettable" + ); + + // + // Get positions for each Claim and check amounts available, before getting into actual netting + // + + position1 = engine.position(claimId1); + position2 = engine.position(claimId2); + position3 = engine.position(claimId3); + + expectedUnderlyingReturnedFromRedeemClaim1 = + (1e12 * (claimState1.amountWritten - claimState1.amountExercised)) / 1e18; + expectedExerciseReturnedFromRedeemClaim1 = (1750 * claimState1.amountExercised) / 1e18; + assertEq( + position1.underlyingAmount, + int256(expectedUnderlyingReturnedFromRedeemClaim1), + "A -- claim 1 underlying asset avail" + ); + assertEq( + position1.exerciseAmount, + int256(expectedExerciseReturnedFromRedeemClaim1), + "A -- claim 1 exercise asset avail" + ); + + expectedUnderlyingReturnedFromNetClaim2 = + (0.0275e6 * 1e12 * (claimState2.amountWritten - claimState2.amountExercised)) / claimState2.amountWritten; + expectedUnderlyingReturnedFromRedeemClaim2 = + (1e12 * (claimState2.amountWritten - claimState2.amountExercised)) / 1e18; + expectedExerciseReturnedFromRedeemClaim2 = (1750 * claimState2.amountExercised) / 1e18; + assertEq( + position2.underlyingAmount, + int256(expectedUnderlyingReturnedFromRedeemClaim2), + "A -- claim 2 underlying asset avail" + ); + assertEq( + position2.exerciseAmount, + int256(expectedExerciseReturnedFromRedeemClaim2), + "A -- claim 2 exercise asset avail" + ); + + expectedUnderlyingReturnedFromRedeemClaim3 = + (1e12 * (claimState3.amountWritten - claimState3.amountExercised)) / 1e18; + expectedExerciseReturnedFromRedeemClaim3 = (1750 * claimState3.amountExercised) / 1e18; + assertEq( + position3.underlyingAmount, + int256(expectedUnderlyingReturnedFromRedeemClaim3), + "A -- claim 3 underlying asset avail" + ); + assertEq( + position3.exerciseAmount, + int256(expectedExerciseReturnedFromRedeemClaim3), + "A -- claim 3 exercise asset avail" + ); + + // + // Test net() + // + + // temporary balance values + uint256 uBalance; + uint256 eBalance; + + // Expected WETH back after 1 net and 3 redeems: + // 999997804170000000000000+999997831909080459770114+999995996421839080459770+999999989309080459770114 + // 3999991621809999999999998 + + // Actual WETH back after 1 net and 3 redeems: + // 999997784026321839080459+999997833595287356321839+999996022149425287356321+999999989395287356321839 + // 3999991629166321839080458 + + // This much too much: + // 0.007356321839080460 + + // writer2 gets 0.0175 options from writer 1 + vm.prank(writer1); + engine.safeTransferFrom(writer1, writer2, optionId, 0.0175e6, ""); + + // vm.expectEmit(true, true, true, true); // TODO re-enable and investigate; forge not playing nice + // emit ClaimNetted( + // claimId2, + // optionId, + // writer2, + // 0.0275e6, + // 111 + // ); + uBalance = WETHLIKE.balanceOf(writer2); + eBalance = USDCLIKE.balanceOf(writer2); + vm.prank(writer2); + engine.net(claimId2, 0.0275e6); + assertEq(engine.balanceOf(writer2, optionId), 0, "A -- writer2 ALL options are burned after net"); + assertEq(engine.balanceOf(writer2, claimId2), 1, "A -- writer2 Claim is not burned after net"); + + // TODO Getting 0.000143678160919541 WETH too much back from net() + + assertEq( + WETHLIKE.balanceOf(writer2), + // uBalance + expectedUnderlyingReturnedFromNetClaim2, + uBalance + ((0.0275e6 * 1e12 * 1) / 1), + "A -- writer2 got correct WETHLIKE collateral back from net" + ); + assertEq(USDCLIKE.balanceOf(writer2), eBalance, "A -- writer2 got No USDCLIKE collateral back from net"); + + vm.warp(DAWN + 8 days); // warp to Option Type A expiry + + // Now let's redeem the rest of writer2's claim for the last little bit of collateral + uBalance = WETHLIKE.balanceOf(writer2); + eBalance = USDCLIKE.balanceOf(writer2); + vm.prank(writer2); + engine.redeem(claimId2); + assertEq(engine.balanceOf(writer2, claimId2), 0, "A -- writer2 Claim is burned after redeem"); + assertEq( + WETHLIKE.balanceOf(writer2), + uBalance + expectedUnderlyingReturnedFromRedeemClaim2, + "A -- writer2 got correct WETHLIKE collateral back from redeem" + ); + assertEq( + USDCLIKE.balanceOf(writer2), + eBalance + expectedExerciseReturnedFromRedeemClaim2, + "A -- writer2 got correct USDCLIKE collateral back from redeeem" + ); + + // Let's redeem the other writers' claims and ensure they also get back the correct collateral + uBalance = WETHLIKE.balanceOf(writer1); + eBalance = USDCLIKE.balanceOf(writer1); + vm.prank(writer1); + engine.redeem(claimId1); + assertEq(engine.balanceOf(writer1, claimId1), 0, "A -- writer1 Claim is burned after redeem"); + assertEq( + WETHLIKE.balanceOf(writer1), + uBalance + expectedUnderlyingReturnedFromRedeemClaim1, + "A -- writer1 got correct WETHLIKE collateral back from redeem" + ); + assertEq( + USDCLIKE.balanceOf(writer1), + eBalance + expectedExerciseReturnedFromRedeemClaim1, + "A -- writer1 got correct USDCLIKE collateral back from redeeem" + ); + + uBalance = WETHLIKE.balanceOf(writer3); + eBalance = USDCLIKE.balanceOf(writer3); + vm.prank(writer3); + engine.redeem(claimId3); + assertEq(engine.balanceOf(writer3, claimId3), 0, "A -- writer3 Claim is burned after redeem"); + assertEq( + WETHLIKE.balanceOf(writer3), + uBalance + expectedUnderlyingReturnedFromRedeemClaim3, + "A -- writer3 got correct WETHLIKE collateral back from redeem" + ); + assertEq( + USDCLIKE.balanceOf(writer3), + eBalance + expectedExerciseReturnedFromRedeemClaim3, + "A -- writer3 got correct USDCLIKE collateral back from redeeem" + ); + } + + function test_net_scenarioB_whenThreeWritersTwoExercisesAndNetInsufficientOnFirstTries() public { + // Scenario B, run the same actions but with a slightly different Option Type, for a different settlementSeed, + // resulting in a different assignment path + vm.startPrank(writer1); + optionId = engine.newOptionType({ + underlyingAsset: address(WETHLIKE), + underlyingAmount: 1e12, + exerciseAsset: address(USDCLIKE), + exerciseAmount: 1746, // slightly less USDC, for a different settlementSeed + exerciseTimestamp: uint40(DAWN + 2 days), + expiryTimestamp: uint40(DAWN + 9 days) // 1 more day than Option Type A, for staggered redemption ability + }); + claimId1 = engine.write(optionId, 0.01e6); + engine.write(claimId1, 0.04e6); + engine.write(claimId1, 0.95e6); + engine.safeTransferFrom(writer1, exerciser1, optionId, 1e6, ""); + engine.write(claimId1, 0.15e6); + vm.stopPrank(); + vm.startPrank(writer2); + claimId2 = engine.write(optionId, 0.1e6); + engine.safeTransferFrom(writer2, exerciser2, optionId, 0.1e6, ""); + vm.stopPrank(); + vm.warp(DAWN + 2 days); + vm.prank(exerciser1); + engine.exercise(optionId, 1e6); + vm.prank(writer3); + claimId3 = engine.write(optionId, 0.01e6); + vm.prank(writer1); + engine.write(claimId1, 0.85e6); + vm.prank(writer2); + engine.write(claimId2, 0.01e6); + vm.prank(exerciser2); + engine.exercise(optionId, 0.05e6); + vm.prank(writer1); + engine.write(claimId1, 1e6); + assertEq(engine.balanceOf(writer1, optionId), 2e6, "writer1 option balance t6"); + assertEq(engine.balanceOf(exerciser1, optionId), 0, "exerciser1 option balance t6"); + assertEq(engine.balanceOf(writer2, optionId), 0.01e6, "writer2 option balance t6"); + assertEq(engine.balanceOf(exerciser2, optionId), 0.05e6, "exerciser2 option balance t6"); + assertEq(engine.balanceOf(writer3, optionId), 0.01e6, "writer3 option balance t6"); + + claimState1 = engine.claim(claimId1); + claimState2 = engine.claim(claimId2); + claimState3 = engine.claim(claimId3); + + console.log("Scenario B ------------------"); + // console.log("Claim 1 ----------"); + // console.log("amountWritten", claimState1.amountWritten, "amountExercised", claimState1.amountExercised); + // console.log("Claim 2 ----------"); + // console.log("amountWritten", claimState2.amountWritten, "amountExercised", claimState2.amountExercised); + // console.log("Claim 3 ----------"); + // console.log("amountWritten", claimState3.amountWritten, "amountExercised", claimState3.amountExercised); + + // + // Summary of current assignment status + // + + // Scenario B -- writer2 has a claim worth 0.026 options + + // When they buy 0.0175 options from writer1, and attempt to net() + // Then sometimes they can, sometimes they can't, depending on assignment path dependence + // We will use these 2 scenarios to test the functionality of nettable + + // Options written, by claim + // 3000000000000000000000000+110000000000000000000000+10000000000000000000000 + // = 3120000.000000000000000000 + // for a total of 3.12 options written + + // Claim assignment status (in this path, Claim 3 never got assigned) + // 966000000000000000000000+84000000000000000000000+0 + // = 1050000.000000000000000000 + // for a total of 1.05 options exercised + + // + // Finally, we can test nettable() + // + + uint256 nettableWriter1B = engine.nettable(claimId1); + uint256 nettableWriter2B = engine.nettable(claimId2); + uint256 nettableWriter3B = engine.nettable(claimId3); + assertEq( + nettableWriter1B, + (claimState1.amountWritten - claimState1.amountExercised) / 1e18, + "B -- claim 1 nettable" + ); + assertEq( + nettableWriter2B, + (claimState2.amountWritten - claimState2.amountExercised) / 1e18, + "B -- claim 2 nettable" + ); + assertEq( + nettableWriter3B, + (claimState3.amountWritten - claimState3.amountExercised) / 1e18, + "B -- claim 3 nettable" + ); + + // + // Get positions for each Claim and check amounts available, before getting into actual netting + // + + position1 = engine.position(claimId1); + position2 = engine.position(claimId2); + position3 = engine.position(claimId3); + + expectedUnderlyingReturnedFromRedeemClaim1 = + (1e12 * (claimState1.amountWritten - claimState1.amountExercised)) / 1e18; + expectedExerciseReturnedFromRedeemClaim1 = (1746 * claimState1.amountExercised) / 1e18; + assertEq( + position1.underlyingAmount, + int256(expectedUnderlyingReturnedFromRedeemClaim1), + "B -- claim 1 underlying asset avail" + ); + assertEq( + position1.exerciseAmount, + int256(expectedExerciseReturnedFromRedeemClaim1), + "B -- claim 1 exercise asset avail" + ); + + expectedUnderlyingReturnedFromNetClaim2 = + (0.026e6 * 1e12 * (claimState2.amountWritten - claimState2.amountExercised)) / claimState2.amountWritten; + expectedUnderlyingReturnedFromRedeemClaim2 = + (1e12 * (claimState2.amountWritten - claimState2.amountExercised)) / 1e18; + expectedExerciseReturnedFromRedeemClaim2 = (1746 * claimState2.amountExercised) / 1e18; + assertEq( + position2.underlyingAmount, + int256(expectedUnderlyingReturnedFromRedeemClaim2), + "B -- claim 2 underlying asset avail" + ); + assertEq( + position2.exerciseAmount, + int256(expectedExerciseReturnedFromRedeemClaim2), + "B -- claim 2 exercise asset avail" + ); + + expectedUnderlyingReturnedFromRedeemClaim3 = + (1e12 * (claimState3.amountWritten - claimState3.amountExercised)) / 1e18; + expectedExerciseReturnedFromRedeemClaim3 = (1746 * claimState3.amountExercised) / 1e18; + assertEq( + position3.underlyingAmount, + int256(expectedUnderlyingReturnedFromRedeemClaim3), + "B -- claim 3 underlying asset avail" + ); + assertEq( + position3.exerciseAmount, + int256(expectedExerciseReturnedFromRedeemClaim3), + "B -- claim 3 exercise asset avail" + ); + + // + // Test net() + // + + // temporary balance values + uint256 uBalance; + uint256 eBalance; + + // Try to net too much, based on current balance (ie, 0.026e6 is nettable but writer2 doesn't hold enough) + vm.expectRevert( + abi.encodeWithSelector( + IValoremOptionsClearinghouse.CallerHoldsInsufficientClaimToNetOptions.selector, + claimId2, + 0.026e6, + engine.nettable(claimId2) + ) + ); + vm.prank(writer2); + engine.net(claimId2, 0.026e6); + + // Try to net too much, based on amount options nettable (ie, writer2 holds enough but 0.0275e6 isn't nettable) + vm.prank(writer1); + engine.safeTransferFrom(writer1, writer2, optionId, 0.0175e6, ""); + + vm.expectRevert( + abi.encodeWithSelector( + IValoremOptionsClearinghouse.CallerHoldsInsufficientClaimToNetOptions.selector, + claimId2, + 0.0275e6, + engine.nettable(claimId2) + ) + ); + vm.prank(writer2); + engine.net(claimId2, 0.0275e6); + + // Finally, we net a nettable amount (ie, writer2 holds enough and 0.026e6 is nettable) + // vm.expectEmit(true, true, true, true); // TODO re-enable and investigate; forge not playing nice + // emit ClaimNetted( + // claimId2, + // optionId, + // writer2, + // 0.026e6, + // 111 + // ); + + uBalance = WETHLIKE.balanceOf(writer2); + eBalance = USDCLIKE.balanceOf(writer2); + vm.prank(writer2); + engine.net(claimId2, 0.026e6); + assertEq(engine.balanceOf(writer2, optionId), 0.0275e6 - 0.026e6, "B -- writer2 SOME options burned after net"); + assertEq(engine.balanceOf(writer2, claimId2), 1, "B -- writer2 Claim is not burned after net"); + assertEq( + WETHLIKE.balanceOf(writer2), + uBalance + expectedUnderlyingReturnedFromNetClaim2, + "B -- writer2 got correct WETHLIKE collateral back from net" + ); + assertEq( + USDCLIKE.balanceOf(writer2), + eBalance, + "B -- writer2 got No USDCLIKE collateral back from net" + ); + + vm.warp(DAWN + 9 days); // warp to Option Type B expiry + + // Now let's redeem the rest of writer2's claim for the last little bit of collateral + uBalance = WETHLIKE.balanceOf(writer2); + eBalance = USDCLIKE.balanceOf(writer2); + vm.prank(writer2); + engine.redeem(claimId2); + assertEq(engine.balanceOf(writer2, claimId2), 0, "B -- writer2 Claim is burned after redeem"); + assertEq( + WETHLIKE.balanceOf(writer2), + uBalance + expectedUnderlyingReturnedFromRedeemClaim2, + "B -- writer2 got correct WETHLIKE collateral back from redeem" + ); + assertEq( + USDCLIKE.balanceOf(writer2), + eBalance + expectedExerciseReturnedFromRedeemClaim2, + "B -- writer2 got correct USDCLIKE collateral back from redeeem" + ); + + // Again let's redeem the other writers' claims and ensure they get back the correct collateral from redeem + uBalance = WETHLIKE.balanceOf(writer1); + eBalance = USDCLIKE.balanceOf(writer1); + vm.prank(writer1); + engine.redeem(claimId1); + assertEq(engine.balanceOf(writer1, claimId1), 0, "B -- writer1 Claim is burned after redeem"); + assertEq( + WETHLIKE.balanceOf(writer1), + uBalance + expectedUnderlyingReturnedFromRedeemClaim1, + "B -- writer1 got correct WETHLIKE collateral back from redeem" + ); + assertEq( + USDCLIKE.balanceOf(writer1), + eBalance + expectedExerciseReturnedFromRedeemClaim1, + "B -- writer1 got correct USDCLIKE collateral back from redeeem" + ); + + uBalance = WETHLIKE.balanceOf(writer3); + eBalance = USDCLIKE.balanceOf(writer3); + vm.prank(writer3); + engine.redeem(claimId3); + assertEq(engine.balanceOf(writer3, claimId3), 0, "B -- writer3 Claim is burned after redeem"); + assertEq( + WETHLIKE.balanceOf(writer3), + uBalance + expectedUnderlyingReturnedFromRedeemClaim3, + "B -- writer3 got correct WETHLIKE collateral back from redeem" + ); + assertEq( + USDCLIKE.balanceOf(writer3), + eBalance + expectedExerciseReturnedFromRedeemClaim3, + "B -- writer3 got correct USDCLIKE collateral back from redeeem" + ); + } + + function test_net_scenarioC_whenOneWriterNoExercise() public { + // Scenario C, 1 writer, 1 claim, 1 bucket, no assigment -- will burn Claim during net() + vm.startPrank(writer2); + optionId = engine.newOptionType({ + underlyingAsset: address(WETHLIKE), + underlyingAmount: 1e12, + exerciseAsset: address(USDCLIKE), + exerciseAmount: 1750, + exerciseTimestamp: uint40(DAWN + 3 days), + expiryTimestamp: uint40(DAWN + 10 days) // 1 more day than Option Type B, for staggered redemption ability + }); + claimId2 = engine.write(optionId, 1e6); + vm.stopPrank(); + + claimState2 = engine.claim(claimId2); + + console.log("Scenario C ------------------"); + // console.log("Claim 2 ----------"); + // console.log("amountWritten", claimState2.amountWritten, "amountExercised", claimState2.amountExercised); + + // + // Summary of current assignment status + // + + // Scenario C -- writer2 has a claim worth 1 option + + // Options written, by claim + // 1000000000000000000000000 + // = 1000000.000000000000000000 + // for a total of 1 option written + + // Claim assignment status + // 0 + // = 0 + // for a total of 0 options exercised + + // + // Finally, we can test nettable() + // + + uint256 nettableWriter2C = engine.nettable(claimId2); + assertEq( + nettableWriter2C, + (claimState2.amountWritten - claimState2.amountExercised) / 1e18, + "C -- claim 2 nettable" + ); + + // + // Get positions for each Claim and check amounts available, before getting into actual netting + // + + position2 = engine.position(claimId2); + expectedUnderlyingReturnedFromNetClaim2 = + (1e12 * (claimState2.amountWritten - claimState2.amountExercised)) / 1e18; + expectedExerciseReturnedFromRedeemClaim2 = (1750 * claimState2.amountExercised) / 1e18; + assertEq( + position2.underlyingAmount, + int256(expectedUnderlyingReturnedFromNetClaim2), + "C -- claim 2 underlying asset avail" + ); + assertEq( + position2.exerciseAmount, + int256(expectedExerciseReturnedFromRedeemClaim2), + "C -- claim 2 exercise asset avail" + ); + + // + // Test net() + // + + // temporary balance values + uint256 uBalance; + uint256 eBalance; + + uBalance = WETHLIKE.balanceOf(writer2); + eBalance = USDCLIKE.balanceOf(writer2); + vm.prank(writer2); + engine.net(claimId2, 1e6); + assertEq(engine.balanceOf(writer2, optionId), 0, "C -- writer2 ALL options burned after net"); + assertEq(engine.balanceOf(writer2, claimId2), 0, "C -- writer2 Claim IS burned after net"); + assertEq( + WETHLIKE.balanceOf(writer2), + uBalance + expectedUnderlyingReturnedFromNetClaim2, + "C -- writer2 got correct WETHLIKE collateral back from net" + ); + assertEq(USDCLIKE.balanceOf(writer2), eBalance, "C -- writer2 got No USDCLIKE collateral back from net"); + } + + function test_net_scenarioD_whenOneWriterOneExercise() public { + // Scenario D, 1 writer, 1 claim, 1 bucket, 1 taker takes 0.3e6 options + vm.startPrank(writer2); + optionId = engine.newOptionType({ + underlyingAsset: address(WETHLIKE), + underlyingAmount: 1e12, + exerciseAsset: address(USDCLIKE), + exerciseAmount: 1750, + exerciseTimestamp: uint40(DAWN + 4 days), + expiryTimestamp: uint40(DAWN + 11 days) // 1 more day than Option Type C, for staggered redemption ability + }); + claimId2 = engine.write(optionId, 1e6); + engine.safeTransferFrom(writer2, exerciser2, optionId, 0.3e6, ""); + vm.stopPrank(); + + // t = 2 + vm.warp(DAWN + 4 days); + vm.prank(exerciser2); + engine.exercise(optionId, 0.3e6); + + claimState2 = engine.claim(claimId2); + + console.log("Scenario D ------------------"); + // console.log("Claim 2 ----------"); + // console.log("amountWritten", claimState2.amountWritten, "amountExercised", claimState2.amountExercised); + + // + // Summary of current assignment status + // + + // Scenario D -- writer2 has a claim worth 0.7 options + + // Options written, by claim + // 1000000000000000000000000 + // = 1000000.000000000000000000 + // for a total of 1 option written + + // Claim assignment status + // 300000000000000000000000 + // = 300000.000000000000000000 + // for a total of 0.3 options exercised + + // + // Finally, we can test nettable() + // + + uint256 nettableWriter2D = engine.nettable(claimId2); + assertEq( + nettableWriter2D, + (claimState2.amountWritten - claimState2.amountExercised) / 1e18, + "D -- claim 2 nettable" + ); + + // + // Get positions for each Claim and check amounts available, before getting into actual netting + // + + position2 = engine.position(claimId2); + expectedUnderlyingReturnedFromNetClaim2 = + (1e12 * (claimState2.amountWritten - claimState2.amountExercised)) / 1e18; + expectedExerciseReturnedFromRedeemClaim2 = (1750 * claimState2.amountExercised) / 1e18; + assertEq( + position2.underlyingAmount, + int256(expectedUnderlyingReturnedFromNetClaim2), + "D -- claim 2 underlying asset avail" + ); + assertEq( + position2.exerciseAmount, + int256(expectedExerciseReturnedFromRedeemClaim2), + "D -- claim 2 exercise asset avail" + ); + + // + // Test net() + // + + // temporary balance values + uint256 uBalance; + uint256 eBalance; + + // WETH + // Expected: 999999946760287356321839+999999026649425287356321+999999999410287356321839 + // Actual: 999999934649422057264049+999999011265288087663485+999999999405289855072463 + // 2999998972819999999999999 + // 2999998945319999999999997 (0.027500000000000002 WETH less) + + // USDC + // Expected: 1000000141005747+1000001695488505+1000000001005747 + // Actual: 1000000114074761+1000001722410745+1000000001014492 + // 3000001837499999 + // 3000001837499999 (exactly correct) + + uBalance = WETHLIKE.balanceOf(writer2); + eBalance = USDCLIKE.balanceOf(writer2); + vm.prank(writer2); + engine.net(claimId2, 0.7e6); + assertEq(engine.balanceOf(writer2, optionId), 0, "D -- writer2 ALL options burned after net"); + assertEq(engine.balanceOf(writer2, claimId2), 1, "D -- writer2 Claim is not burned after net"); + assertEq( + WETHLIKE.balanceOf(writer2), + uBalance + expectedUnderlyingReturnedFromNetClaim2, + "D -- writer2 got correct WETHLIKE collateral back from net" + ); + assertEq(USDCLIKE.balanceOf(writer2), eBalance, "D -- writer2 got No USDCLIKE collateral back from net"); + + vm.warp(DAWN + 11 days); // warp to Option Type D expiry + + // Now let's redeem the rest of writer2's claim for the (assigned) exercise asset + uBalance = WETHLIKE.balanceOf(writer2); + eBalance = USDCLIKE.balanceOf(writer2); + vm.prank(writer2); + engine.redeem(claimId2); + assertEq(engine.balanceOf(writer2, claimId2), 0, "D -- writer2 Claim is burned after redeem"); + assertEq(WETHLIKE.balanceOf(writer2), uBalance, "D -- writer2 got No WETHLIKE collateral back from redeem"); + assertEq( + USDCLIKE.balanceOf(writer2), + eBalance + expectedExerciseReturnedFromRedeemClaim2, + "D -- writer2 got correct USDCLIKE collateral back from redeeem" + ); + } + + // TODO remaining scenarios + /*////////////////////////////////////////////////////////////// // redeem() early //////////////////////////////////////////////////////////////*/ @@ -304,4 +1106,14 @@ contract ValoremOptionsClearinghousev11UnitTest is BaseClearinghouseTest { vm.prank(ALICE); engine.redeem(claimId); } + + // New Events + + event ClaimNetted( + uint256 indexed claimId, + uint256 indexed optionId, + address netter, + uint256 indexed optionsAmountNetted, + uint256 underlyingAmountNetted + ); } diff --git a/test/ValoremOptionsClearinghouse.integration.t.sol b/test/ValoremOptionsClearinghouse.integration.t.sol index e7d449d..f85abe0 100644 --- a/test/ValoremOptionsClearinghouse.integration.t.sol +++ b/test/ValoremOptionsClearinghouse.integration.t.sol @@ -340,10 +340,9 @@ contract ValoremOptionsClearinghouseIntegrationTest is BaseClearinghouseTest { engine.write(daiOptionId, optionsWrittenDaiUnderlying); engine.write(usdcOptionId, optionsWrittenUsdcUnderlying); vm.stopPrank(); - expectedFees[0] = (((testUnderlyingAmount * optionsWrittenWethUnderlying) / 10_000) * engine.feeBps()); + expectedFees[0] = (((testUnderlyingAmount * optionsWrittenWethUnderlying) / 10_000) * engine.feeBps()); expectedFees[1] = (((daiUnderlyingAmount * optionsWrittenDaiUnderlying) / 10_000) * engine.feeBps()); - expectedFees[2] = (((usdcUnderlyingAmount * optionsWrittenUsdcUnderlying) / 10_000) * engine.feeBps()); for (uint256 i = 0; i < tokens.length; i++) { @@ -451,10 +450,9 @@ contract ValoremOptionsClearinghouseIntegrationTest is BaseClearinghouseTest { // taken for any of these assets, and the 1 wei-left-behind gas optimization // has already happened, therefore actual fee swept amount = true fee amount. uint256[] memory expectedFees = new uint256[](3); - expectedFees[0] = (((daiExerciseAmount * optionsWrittenDaiExercise) / 10_000) * engine.feeBps()); + expectedFees[0] = (((daiExerciseAmount * optionsWrittenDaiExercise) / 10_000) * engine.feeBps()); expectedFees[1] = (((wethExerciseAmount * optionsWrittenWethExercise) / 10_000) * engine.feeBps()); - expectedFees[2] = (((usdcExerciseAmount * optionsWrittenUsdcExercise) / 10_000) * engine.feeBps()); for (uint256 i = 0; i < tokens.length; i++) { diff --git a/test/utils/BaseClearinghouseTest.sol b/test/utils/BaseClearinghouseTest.sol index 83942b5..b798b3c 100644 --- a/test/utils/BaseClearinghouseTest.sol +++ b/test/utils/BaseClearinghouseTest.sol @@ -23,6 +23,13 @@ abstract contract BaseClearinghouseTest is Test { address internal constant ALICE = address(0xA); address internal constant BOB = address(0xB); address internal constant CAROL = address(0xC); + address internal constant userD = address(0xD); + address internal constant userE = address(0xE); + address internal constant userF = address(0xF); + address internal constant userG = address(0xFF); + address internal constant userH = address(0xFFF); + address internal constant userI = address(0xFFFF); + address internal constant userJ = address(0xFFFFF); // Admin address internal constant FEE_TO = address(0xBEEF); @@ -84,7 +91,7 @@ abstract contract BaseClearinghouseTest is Test { ERC20S.push(ERC20F); // Setup token balances and approvals - address[3] memory recipients = [ALICE, BOB, CAROL]; + address[10] memory recipients = [ALICE, BOB, CAROL, userD, userE, userF, userG, userH, userI, userJ]; for (uint256 i = 0; i < recipients.length; i++) { _mintTokensForAddress(recipients[i]); }