Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Natspec, Ontology, Interface cleanup #149

Merged
merged 15 commits into from
Dec 11, 2022
2 changes: 1 addition & 1 deletion lib/base64/src/Base64.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Base64.sol)

pragma solidity 0.8.11;
pragma solidity 0.8.16;

/**
* @dev Provides a set of functions to operate with Base64 strings.
Expand Down
2 changes: 1 addition & 1 deletion lib/solmate
174 changes: 104 additions & 70 deletions src/OptionSettlementEngine.sol

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions src/TokenURIGenerator.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL 1.1
pragma solidity 0.8.11;
pragma solidity 0.8.16;

import "base64/Base64.sol";
import "solmate/tokens/ERC20.sol";
Expand Down Expand Up @@ -107,7 +107,7 @@ contract TokenURIGenerator is ITokenURIGenerator {
function _generateHeaderSection(
string memory _underlyingSymbol,
string memory _exerciseSymbol,
IOptionSettlementEngine.Type _tokenType
IOptionSettlementEngine.TokenType _tokenType
) internal pure returns (string memory) {
return string(
abi.encodePacked(
Expand All @@ -118,7 +118,7 @@ contract TokenURIGenerator is ITokenURIGenerator {
_exerciseSymbol,
"</text>"
),
_tokenType == IOptionSettlementEngine.Type.Option
_tokenType == IOptionSettlementEngine.TokenType.Option
?
"<text x='16px' y='80px' font-size='16' fill='#fff' font-family='Helvetica' font-weight='300'>Long Call</text>"
:
Expand Down
483 changes: 222 additions & 261 deletions src/interfaces/IOptionSettlementEngine.sol

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/interfaces/ITokenURIGenerator.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL 1.1
pragma solidity 0.8.11;
pragma solidity 0.8.16;

import "./IOptionSettlementEngine.sol";

Expand All @@ -26,7 +26,7 @@ interface ITokenURIGenerator {
/// @param exerciseAmount The amount of the exercise asset required to exercise this option
uint96 exerciseAmount;
/// @param tokenType Option or Claim
IOptionSettlementEngine.Type tokenType;
IOptionSettlementEngine.TokenType tokenType;
}

/**
Expand Down
112 changes: 65 additions & 47 deletions test/OptionSettlementEngine.t.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL 1.1
pragma solidity 0.8.11;
pragma solidity 0.8.16;

import "forge-std/Test.sol";
import {IERC20} from "forge-std/interfaces/IERC20.sol";
Expand Down Expand Up @@ -61,6 +61,23 @@ contract OptionSettlementTest is Test, NFTreceiver {
IOptionSettlementEngine.Option private testOption;
ITokenURIGenerator private generator;

uint8 internal constant OPTION_ID_PADDING = 96;

uint96 internal constant CLAIM_NUMBER_MASK = 0xFFFFFFFFFFFFFFFFFFFFFFFF;

function encodeTokenId(uint160 optionKey, uint96 claimKey) internal pure returns (uint256 tokenId) {
tokenId |= uint256(optionKey) << OPTION_ID_PADDING;
tokenId |= uint256(claimKey);
}

function decodeTokenId(uint256 tokenId) internal pure returns (uint160 optionKey, uint96 claimKey) {
// move key to lsb to fit into uint160
optionKey = uint160(tokenId >> OPTION_ID_PADDING);

// grab lower 96b of id for claim number
claimKey = uint96(tokenId & CLAIM_NUMBER_MASK);
}

function setUp() public {
// Fork mainnet
vm.createSelectFork(vm.envString("RPC_URL"), 15_000_000); // specify block number to cache for future test runs
Expand Down Expand Up @@ -104,12 +121,12 @@ contract OptionSettlementTest is Test, NFTreceiver {

// Enable fee switch
vm.prank(FEE_TO);
engine.setFeeSwitch(true);
engine.protocolFees(true);
}

function testInitial() public {
assertEq(engine.feeTo(), FEE_TO);
assertEq(engine.feeSwitch(), true);
assertEq(engine.feesEnabled(), true);
}

// **********************************************************************
Expand Down Expand Up @@ -182,15 +199,15 @@ contract OptionSettlementTest is Test, NFTreceiver {
assertEq(engine.balanceOf(ALICE, claimId2), 1);

IOptionSettlementEngine.Underlying memory claimUnderlying = engine.underlying(claimId1);
(uint160 _optionId, uint96 claimIdx) = engine.decodeTokenId(claimId1);
(uint160 _optionId, uint96 claimIdx) = decodeTokenId(claimId1);
uint256 optionId = uint256(_optionId) << 96;
assertEq(optionId, testOptionId);
assertEq(claimIdx, 1);
assertEq(uint256(claimUnderlying.underlyingPosition), 69 * testUnderlyingAmount);
_assertClaimAmountExercised(claimId1, 0);

claimUnderlying = engine.underlying(claimId2);
(optionId, claimIdx) = engine.decodeTokenId(claimId2);
(optionId, claimIdx) = decodeTokenId(claimId2);
optionId = uint256(_optionId) << 96;
assertEq(optionId, testOptionId);
assertEq(claimIdx, 2);
Expand Down Expand Up @@ -576,7 +593,7 @@ contract OptionSettlementTest is Test, NFTreceiver {

// Disable fee switch
vm.prank(FEE_TO);
engine.setFeeSwitch(false);
engine.protocolFees(false);

// Write 3 more, no fee is recorded or emitted
vm.prank(ALICE);
Expand All @@ -585,7 +602,7 @@ contract OptionSettlementTest is Test, NFTreceiver {

// Re-enable
vm.prank(FEE_TO);
engine.setFeeSwitch(true);
engine.protocolFees(true);

// Write 5 more, fee is again recorded and emitted
vm.prank(ALICE);
Expand Down Expand Up @@ -630,7 +647,7 @@ contract OptionSettlementTest is Test, NFTreceiver {

// Disable fee switch
vm.prank(FEE_TO);
engine.setFeeSwitch(false);
engine.protocolFees(false);

// Exercise 3 more, no fee is recorded or emitted
vm.prank(BOB);
Expand All @@ -639,7 +656,7 @@ contract OptionSettlementTest is Test, NFTreceiver {

// Re-enable
vm.prank(FEE_TO);
engine.setFeeSwitch(true);
engine.protocolFees(true);

// Exercise 5 more, fee is again recorded and emitted
vm.prank(BOB);
Expand All @@ -653,56 +670,54 @@ contract OptionSettlementTest is Test, NFTreceiver {
// PROTOCOL ADMIN
// **********************************************************************

function testSetFeeSwitch() public {
function testprotocolFees() public {
// precondition check -- in test suite, fee switch is enabled by default
assertTrue(engine.feeSwitch());
assertTrue(engine.feesEnabled());

// disable
vm.startPrank(FEE_TO);
engine.setFeeSwitch(false);
engine.protocolFees(false);

assertFalse(engine.feeSwitch());
assertFalse(engine.feesEnabled());

// enable
engine.setFeeSwitch(true);
engine.protocolFees(true);

assertTrue(engine.feeSwitch());
assertTrue(engine.feesEnabled());
}

function testEventSetFeeSwitch() public {
function testEventprotocolFees() public {
0xAlcibiades marked this conversation as resolved.
Show resolved Hide resolved
vm.expectEmit(true, true, true, true);
emit FeeSwitchUpdated(FEE_TO, false);

// disable
vm.startPrank(FEE_TO);
engine.setFeeSwitch(false);
engine.protocolFees(false);

vm.expectEmit(true, true, true, true);
emit FeeSwitchUpdated(FEE_TO, true);

// enable
engine.setFeeSwitch(true);
engine.protocolFees(true);
}

function testRevertSetFeeSwitchWhenNotFeeTo() public {
function testRevertprotocolFeesWhenNotFeeTo() public {
0xAlcibiades marked this conversation as resolved.
Show resolved Hide resolved
vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.AccessControlViolation.selector, ALICE, FEE_TO));

vm.prank(ALICE);
engine.setFeeSwitch(true);
engine.protocolFees(true);
}

function testRevertConstructorWhenFeeToIsZeroAddress() public {
TokenURIGenerator localGenerator = new TokenURIGenerator();

vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.InvalidFeeToAddress.selector, address(0)));
vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.InvalidAddress.selector, address(0)));

new OptionSettlementEngine(address(0), address(localGenerator));
}

function testRevertConstructorWhenTokenURIGeneratorIsZeroAddress() public {
vm.expectRevert(
abi.encodeWithSelector(IOptionSettlementEngine.InvalidTokenURIGeneratorAddress.selector, address(0))
);
vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.InvalidAddress.selector, address(0)));

new OptionSettlementEngine(FEE_TO, address(0));
}
Expand Down Expand Up @@ -734,7 +749,7 @@ contract OptionSettlementTest is Test, NFTreceiver {
}

function testRevertSetFeeToWhenZeroAddress() public {
vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.InvalidFeeToAddress.selector, address(0)));
vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.InvalidAddress.selector, address(0)));
vm.prank(FEE_TO);
engine.setFeeTo(address(0));
}
Expand All @@ -752,9 +767,7 @@ contract OptionSettlementTest is Test, NFTreceiver {
}

function testRevertSetTokenURIGeneratorWhenZeroAddress() public {
vm.expectRevert(
abi.encodeWithSelector(IOptionSettlementEngine.InvalidTokenURIGeneratorAddress.selector, address(0))
);
vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.InvalidAddress.selector, address(0)));
vm.prank(FEE_TO);
engine.setTokenURIGenerator(address(0));
}
Expand All @@ -775,13 +788,13 @@ contract OptionSettlementTest is Test, NFTreceiver {
uint256 cTokenId2 = engine.write(oTokenId, 3);

// Check encoding the first claim
(uint160 decodedOptionId,) = engine.decodeTokenId(cTokenId1);
(uint160 decodedOptionId,) = decodeTokenId(cTokenId1);
0xAlcibiades marked this conversation as resolved.
Show resolved Hide resolved
uint96 expectedClaimIndex1 = 1;
assertEq(engine.encodeTokenId(decodedOptionId, expectedClaimIndex1), cTokenId1);
assertEq(encodeTokenId(decodedOptionId, expectedClaimIndex1), cTokenId1);

// Check encoding the second claim
uint96 expectedClaimIndex2 = 2;
assertEq(engine.encodeTokenId(decodedOptionId, expectedClaimIndex2), cTokenId2);
assertEq(encodeTokenId(decodedOptionId, expectedClaimIndex2), cTokenId2);
}

function testFuzzEncodeTokenId(uint256 optionId, uint256 claimIndex) public {
Expand All @@ -791,7 +804,7 @@ contract OptionSettlementTest is Test, NFTreceiver {
uint256 expectedTokenId = claimIndex;
expectedTokenId |= optionId << 96;

assertEq(engine.encodeTokenId(uint160(optionId), uint96(claimIndex)), expectedTokenId);
assertEq(encodeTokenId(uint160(optionId), uint96(claimIndex)), expectedTokenId);
}

function testDecodeTokenId() public {
Expand All @@ -805,15 +818,15 @@ contract OptionSettlementTest is Test, NFTreceiver {
vm.prank(ALICE);
uint256 cTokenId2 = engine.write(oTokenId, 3);

(uint160 decodedOptionIdFromOTokenId, uint96 decodedClaimIndexFromOTokenId) = engine.decodeTokenId(oTokenId);
(uint160 decodedOptionIdFromOTokenId, uint96 decodedClaimIndexFromOTokenId) = decodeTokenId(oTokenId);
0xAlcibiades marked this conversation as resolved.
Show resolved Hide resolved
assertEq(decodedOptionIdFromOTokenId, oTokenId >> 96);
assertEq(decodedClaimIndexFromOTokenId, 0); // no claims when initially creating a new option type

(uint160 decodedOptionIdFromCTokenId1, uint96 decodedClaimIndexFromCTokenId1) = engine.decodeTokenId(cTokenId1);
(uint160 decodedOptionIdFromCTokenId1, uint96 decodedClaimIndexFromCTokenId1) = decodeTokenId(cTokenId1);
assertEq(decodedOptionIdFromCTokenId1, oTokenId >> 96);
assertEq(decodedClaimIndexFromCTokenId1, 1); // first claim

(uint160 decodedOptionIdFromCTokenId2, uint96 decodedClaimIndexFromCTokenId2) = engine.decodeTokenId(cTokenId2);
(uint160 decodedOptionIdFromCTokenId2, uint96 decodedClaimIndexFromCTokenId2) = decodeTokenId(cTokenId2);
assertEq(decodedOptionIdFromCTokenId2, oTokenId >> 96);
assertEq(decodedClaimIndexFromCTokenId2, 2); // second claim
}
Expand All @@ -825,7 +838,7 @@ contract OptionSettlementTest is Test, NFTreceiver {
uint256 testTokenId = claimId;
testTokenId |= optionId << 96;

(uint160 decodedOptionId, uint96 decodedClaimId) = engine.decodeTokenId(testTokenId);
(uint160 decodedOptionId, uint96 decodedClaimId) = decodeTokenId(testTokenId);
assertEq(decodedOptionId, optionId);
assertEq(decodedClaimId, claimId);
}
Expand Down Expand Up @@ -869,10 +882,8 @@ contract OptionSettlementTest is Test, NFTreceiver {
uint256 oTokenId =
engine.newOptionType(DAI_A, 1, USDC_A, 100, uint40(block.timestamp), uint40(block.timestamp + 30 days));

(uint160 decodedOptionId,) = engine.decodeTokenId(oTokenId);

assertTrue(engine.isOptionInitialized(decodedOptionId));
assertFalse(engine.isOptionInitialized(1337));
_assertTokenIsOption(oTokenId);
_assertTokenIsNone(1337);
}

// **********************************************************************
Expand Down Expand Up @@ -946,7 +957,7 @@ contract OptionSettlementTest is Test, NFTreceiver {

vm.warp(testExpiryTimestamp - 1 seconds);

engine.decodeTokenId(testOptionId);
decodeTokenId(testOptionId);
uint256 expectedFeeAccruedAmount = (testExerciseAmount / 10_000) * engine.feeBps();

vm.expectEmit(true, true, true, true);
Expand Down Expand Up @@ -1283,7 +1294,7 @@ contract OptionSettlementTest is Test, NFTreceiver {
// Option ID not 0 in lower 96 b
uint256 invalidOptionId = testOptionId + 1;
// Option ID not initialized
invalidOptionId = engine.encodeTokenId(0x1, 0x0);
invalidOptionId = encodeTokenId(0x1, 0x0);
vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.InvalidOption.selector, invalidOptionId));
engine.write(invalidOptionId, 1);
}
Expand Down Expand Up @@ -1419,7 +1430,7 @@ contract OptionSettlementTest is Test, NFTreceiver {
}

function testRevertRedeemWhenInvalidClaim() public {
uint256 badClaimId = engine.encodeTokenId(0xDEADBEEF, 0);
uint256 badClaimId = encodeTokenId(0xDEADBEEF, 0);

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

Expand Down Expand Up @@ -1567,7 +1578,7 @@ contract OptionSettlementTest is Test, NFTreceiver {
assertEq(engine.balanceOf(ALICE, claimId), 1);
assertEq(uint256(claimUnderlying.underlyingPosition), testUnderlyingAmount * amount);

(uint160 optionId, uint96 claimIdx) = engine.decodeTokenId(claimId);
(uint160 optionId, uint96 claimIdx) = decodeTokenId(claimId);
assertEq(uint256(optionId) << 96, testOptionId);
assertEq(claimIdx, 1);
_assertClaimAmountExercised(claimId, 0);
Expand Down Expand Up @@ -1629,6 +1640,7 @@ contract OptionSettlementTest is Test, NFTreceiver {

vm.startPrank(ALICE);
uint256 claimId = engine.write(testOptionId, amountWrite);
_assertTokenIsClaim(claimId);

vm.warp(testExpiryTimestamp - 1);
engine.exercise(testOptionId, amountExercise);
Expand All @@ -1648,7 +1660,7 @@ contract OptionSettlementTest is Test, NFTreceiver {
assertEq(claimUnderlying.underlyingPosition, 0);
assertEq(claimUnderlying.exercisePosition, 0);

_assertTokenIsClaim(claimId);
_assertTokenIsNone(claimId);
}

struct FuzzMetadata {
Expand Down Expand Up @@ -1830,14 +1842,20 @@ contract OptionSettlementTest is Test, NFTreceiver {
// TEST HELPERS
// **********************************************************************
0xAlcibiades marked this conversation as resolved.
Show resolved Hide resolved

function _assertTokenIsNone(uint256 tokenId) internal {
if (engine.tokenType(tokenId) != IOptionSettlementEngine.TokenType.None) {
assertTrue(false);
}
}

function _assertTokenIsClaim(uint256 tokenId) internal {
if (engine.tokenType(tokenId) != IOptionSettlementEngine.Type.Claim) {
if (engine.tokenType(tokenId) != IOptionSettlementEngine.TokenType.Claim) {
assertTrue(false);
}
}

function _assertTokenIsOption(uint256 tokenId) internal {
if (engine.tokenType(tokenId) == IOptionSettlementEngine.Type.Claim) {
if (engine.tokenType(tokenId) != IOptionSettlementEngine.TokenType.Option) {
assertTrue(false);
}
}
Expand Down