Skip to content

Commit

Permalink
More test coverage on sad paths
Browse files Browse the repository at this point in the history
  • Loading branch information
neodaoist authored Nov 10, 2022
1 parent 482cf9c commit fafa5ac
Show file tree
Hide file tree
Showing 2 changed files with 204 additions and 87 deletions.
2 changes: 1 addition & 1 deletion src/OptionSettlement.sol
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine {
revert InvalidOption(_optionIdU160b);
}
if (expiry <= block.timestamp) {
revert ExpiredOption(uint256(_optionIdU160b) << 96, expiry);
revert ExpiredOption(uint256(_optionIdU160b) << 96, expiry); // TODO measure gas savings and just use optionId
}

uint256 rxAmount = amount * optionRecord.underlyingAmount;
Expand Down
289 changes: 203 additions & 86 deletions test/OptionSettlement.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -941,7 +941,7 @@ contract OptionSettlementTest is Test, NFTreceiver {
});
}

function testRevertNewOptionTypeWhenInvalidAssets() public {
function testRevertNewOptionTypeWhenAssetsAreTheSame() public {
vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.InvalidAssets.selector, DAI_A, DAI_A));

_newOption({
Expand All @@ -954,51 +954,162 @@ contract OptionSettlementTest is Test, NFTreceiver {
});
}

// TODO test for Check total supplies and ensure the option will be exercisable
function testRevertNewOptionTypeWhenTotalSuppliesAreTooLowToExercise() public {
uint96 underlyingAmountExceedsTotalSupply = uint96(IERC20(DAI_A).totalSupply() + 1);

function testFailAssignExercise() public {
// Exercise an option before anyone has written it
vm.expectRevert(IOptionSettlementEngine.NoClaims.selector);
engine.exercise(testOptionId, 1);
}
vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.InvalidAssets.selector, DAI_A, WETH_A));

_newOption({
underlyingAsset: DAI_A,
exerciseTimestamp: testExerciseTimestamp,
expiryTimestamp: testExpiryTimestamp,
exerciseAsset: WETH_A,
underlyingAmount: underlyingAmountExceedsTotalSupply,
exerciseAmount: testExerciseAmount
});

function testFailWriteInvalidOption() public {
vm.expectRevert(IOptionSettlementEngine.InvalidOption.selector);
engine.write(testOptionId + 1, 1);
uint96 exerciseAmountExceedsTotalSupply = uint96(IERC20(USDC_A).totalSupply() + 1);

vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.InvalidAssets.selector, USDC_A, WETH_A));

_newOption({
underlyingAsset: USDC_A,
exerciseTimestamp: testExerciseTimestamp,
expiryTimestamp: testExpiryTimestamp,
exerciseAsset: WETH_A,
underlyingAmount: testUnderlyingAmount,
exerciseAmount: exerciseAmountExceedsTotalSupply
});
}

function testFailWriteExpiredOption() public {
vm.warp(testExpiryTimestamp);
vm.expectRevert(IOptionSettlementEngine.ExpiredOption.selector);
// write()
// L211
// L277
// L230
// L263

// exercise()
// L297
// L303
// L307

// redeem()
// L341
// L347
// L353

// underlying()
// L392
// L399–400

function testRevertWriteWhenInvalidOption() public {
uint256 invalidOptionId = testOptionId + 1;

vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.InvalidOption.selector, invalidOptionId));

engine.write(invalidOptionId, 1);
}

function testFailExerciseBeforeExcercise() public {
(, IOptionSettlementEngine.Option memory option) = _newOption(
WETH_A, // underlyingAsset
testExerciseTimestamp + 1, // exerciseTimestamp
testExpiryTimestamp + 1, // expiryTimestamp
WETH_A, // exerciseAsset
testUnderlyingAmount, // underlyingAmount
testExerciseAmount // exerciseAmount
function testRevertWriteWhenClaimIdDoesNotEncodeOptionId() public {
uint256 option1Claim1 = engine.getTokenId(0xDEADBEEF1, 0xCAFECAFE1);
uint256 option2WithoutClaim = engine.getTokenId(0xDEADBEEF2, 0x0);

vm.expectRevert(
abi.encodeWithSelector(
IOptionSettlementEngine.EncodedOptionIdInClaimIdDoesNotMatchProvidedOptionId.selector,
option1Claim1,
option2WithoutClaim
)
);
uint256 badOptionId = engine.newOptionType(
WETH_A, testExerciseTimestamp, testExpiryTimestamp, WETH_A, testUnderlyingAmount, testExerciseAmount

engine.write(option2WithoutClaim, 1, option1Claim1);
}

function testRevertWriteWhenAmountWrittenIsZero() public {
uint112 invalidWriteAmount = 0;

vm.expectRevert(IOptionSettlementEngine.AmountWrittenCannotBeZero.selector);

engine.write(testOptionId, invalidWriteAmount);
}

// TODO write() L227

function testRevertWriteExpiredOption() public {
vm.warp(testExpiryTimestamp);

vm.expectRevert(
abi.encodeWithSelector(IOptionSettlementEngine.ExpiredOption.selector, testOptionId, testExpiryTimestamp)
);

engine.write(testOptionId, 1);
}

function testRevertWriteWhenWriterDoesNotOwnClaim() public {
vm.startPrank(ALICE);
uint256 claimId = engine.write(testOptionId, 1);
vm.stopPrank();

vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.CallerDoesNotOwnClaimId.selector, claimId));

vm.prank(BOB);
engine.write(testOptionId, 1, claimId);
}

// TODO write() L263
// function testRevertWriteWhenWritingToLotAlreadyClaimed() public {
// vm.startPrank(ALICE);
// uint256 claimId = engine.write(testOptionId, 1);

// vm.warp(testExerciseTimestamp + 1 seconds);

// engine.redeem(claimId);

// vm.expectRevert(
// abi.encodeWithSelector(IOptionSettlementEngine.AlreadyClaimed.selector, claimId)
// );

// engine.write(testOptionId, 1, claimId);
// vm.stopPrank();
// }

// TODO exercise()
// function testRevertExerciseFailAssignExercise() public {
// // Exercise an option before anyone has written it
// vm.expectRevert(IOptionSettlementEngine.NoClaims.selector);
// engine.exercise(testOptionId, 1);
// }

function testRevertExerciseWhenBeforeExerciseTimestamp() public {
// Alice writes
vm.startPrank(ALICE);
engine.write(badOptionId, 1);
engine.safeTransferFrom(ALICE, BOB, badOptionId, 1, "");
engine.write(testOptionId, 1);
engine.safeTransferFrom(ALICE, BOB, testOptionId, 1, "");
vm.stopPrank();

// Bob immediately exercises before exerciseTimestamp
vm.startPrank(BOB);
vm.expectRevert(IOptionSettlementEngine.ExpiredOption.selector);
engine.exercise(badOptionId, 1);
vm.expectRevert(
abi.encodeWithSelector(
IOptionSettlementEngine.ExerciseTooEarly.selector, testOptionId, testExerciseTimestamp
)
);
engine.exercise(testOptionId, 1);
vm.stopPrank();
}

function testFailExerciseAtExpiry() public {
function testRevertExerciseWhenInvalidOptionId() public {
vm.startPrank(ALICE);
engine.write(testOptionId, 1);

uint256 invalidOptionId = testOptionId + 1;

vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.InvalidOption.selector, invalidOptionId));

engine.exercise(invalidOptionId, 1);
}

function testRevertExerciseWhenAtExpiry() public {
// Alice writes
vm.startPrank(ALICE);
engine.write(testOptionId, 1);
Expand All @@ -1010,79 +1121,109 @@ contract OptionSettlementTest is Test, NFTreceiver {

// Bob exercises
vm.startPrank(BOB);
vm.expectRevert(IOptionSettlementEngine.ExpiredOption.selector);
vm.expectRevert(
abi.encodeWithSelector(IOptionSettlementEngine.ExpiredOption.selector, testOptionId, testExpiryTimestamp)
);
engine.exercise(testOptionId, 1);
vm.stopPrank();
}

function testFailExerciseExpiredOption() public {
function testRevertExerciseWhenAfterExpiry() public {
// Alice writes
vm.startPrank(ALICE);
engine.write(testOptionId, 1);
engine.safeTransferFrom(ALICE, BOB, testOptionId, 1, "");
vm.stopPrank();

// Fast-forward to after expiry
vm.warp(testExpiryTimestamp + 1);
// Fast-forward to at expiry
vm.warp(testExpiryTimestamp + 1 seconds);

// Bob exercises
vm.startPrank(BOB);
vm.expectRevert(IOptionSettlementEngine.ExpiredOption.selector);
vm.expectRevert(
abi.encodeWithSelector(IOptionSettlementEngine.ExpiredOption.selector, testOptionId, testExpiryTimestamp)
);
engine.exercise(testOptionId, 1);
vm.stopPrank();
}

function testFailRedeemInvalidClaim() public {
vm.startPrank(ALICE);
vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.InvalidClaim.selector, abi.encode(69)));
engine.redeem(69);
function testRevertRedeemWhenInvalidClaim() public {
uint256 badClaimId = engine.getTokenId(0xDEADBEEF, 0);

vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.InvalidClaim.selector, badClaimId));

vm.prank(ALICE);
engine.redeem(badClaimId);
}

function testFailRedeemBalanceTooLow() public {
// Alice writes and transfers to bob, then alice tries to redeem
function testRevertRedeemWhenBalanceTooLow() public {
// Alice writes and transfers to Bob, then Alice tries to redeem
vm.startPrank(ALICE);
uint256 claimId = engine.write(testOptionId, 1);
engine.safeTransferFrom(ALICE, BOB, testOptionId, 1, "");
vm.expectRevert(IOptionSettlementEngine.CallerDoesNotOwnClaimId.selector);
engine.safeTransferFrom(ALICE, BOB, claimId, 1, "");

vm.warp(testExpiryTimestamp);

vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.CallerDoesNotOwnClaimId.selector, claimId));

engine.redeem(claimId);
vm.stopPrank();

// Carol feels left out and tries to redeem what she can't
vm.startPrank(CAROL);
vm.expectRevert(IOptionSettlementEngine.CallerDoesNotOwnClaimId.selector);
vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.CallerDoesNotOwnClaimId.selector, claimId));

vm.prank(CAROL);
engine.redeem(claimId);
vm.stopPrank();
// Bob redeems, which should burn, and then be unable to redeem a second time

// Bob redeems, which burns the Claim NFT, and then is unable to redeem a second time
vm.startPrank(BOB);
engine.redeem(claimId);
vm.expectRevert(IOptionSettlementEngine.CallerDoesNotOwnClaimId.selector);
engine.redeem(claimId);
}

function testFailRedeemAlreadyClaimed() public {
vm.startPrank(ALICE);
uint256 claimId = engine.write(testOptionId, 1);
// write a second option so balance will be > 0
engine.write(testOptionId, 1);
vm.warp(testExpiryTimestamp + 1);
engine.redeem(claimId);
vm.expectRevert(IOptionSettlementEngine.AlreadyClaimed.selector);
vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.CallerDoesNotOwnClaimId.selector, claimId));

engine.redeem(claimId);
vm.stopPrank();
}

function testRedeemClaimTooSoon() public {
// TODO redeem() L353
// function testRevertRedeemAlreadyClaimed() public {
// vm.startPrank(ALICE);
// uint256 claimId = engine.write(testOptionId, 1);

// // write a second option so balance will be > 0
// engine.write(testOptionId, 1);

// vm.warp(testExpiryTimestamp);

// engine.redeem(claimId);

// vm.expectRevert(
// abi.encodeWithSelector(IOptionSettlementEngine.AlreadyClaimed.selector, claimId
// ));

// engine.redeem(claimId);
// vm.stopPrank();
// }

function testRevertRedeemClaimTooSoon() public {
vm.startPrank(ALICE);
uint256 claimId = engine.write(testOptionId, 1);
vm.warp(testExerciseTimestamp - 1);

vm.warp(testExerciseTimestamp - 1 seconds);

vm.expectRevert(
abi.encodeWithSelector(IOptionSettlementEngine.ClaimTooSoon.selector, claimId, testExpiryTimestamp)
);

engine.redeem(claimId);
}

function testWriteZeroOptionsFails() public {
vm.startPrank(ALICE);
vm.expectRevert(IOptionSettlementEngine.AmountWrittenCannotBeZero.selector);
engine.write(testOptionId, 0);
function testRevertUnderlyingWhenNoOptionIsInitialized() public {
uint256 badOptionId = 123;

vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.TokenNotFound.selector, badOptionId));

engine.underlying(badOptionId);
}

function testUriFailsWithTokenIdEncodingNonexistantOptionType() public {
Expand All @@ -1091,30 +1232,6 @@ contract OptionSettlementTest is Test, NFTreceiver {
engine.uri(420);
}

function testRevertIfClaimIdDoesNotEncodeOptionId() public {
uint256 option1Claim1 = engine.getTokenId(0xDEADBEEF1, 0xCAFECAFE1);
uint256 option2 = engine.getTokenId(0xDEADBEEF2, 0x0);

vm.expectRevert(
abi.encodeWithSelector(
IOptionSettlementEngine.EncodedOptionIdInClaimIdDoesNotMatchProvidedOptionId.selector,
option1Claim1,
option2
)
);
engine.write(option2, 1, option1Claim1);
}

function testRevertIfWriterDoesNotOwnClaim() public {
vm.startPrank(ALICE);
uint256 claimId = engine.write(testOptionId, 1);
vm.stopPrank();

vm.startPrank(BOB);
vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.CallerDoesNotOwnClaimId.selector, claimId));
engine.write(testOptionId, 1, claimId);
}

// **********************************************************************
// FUZZ TESTS
// **********************************************************************
Expand Down

0 comments on commit fafa5ac

Please sign in to comment.