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

Otoken: Burn and Mint #31

Merged
merged 29 commits into from
Jul 25, 2020
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
cc5cdc1
Rearrange oz folders and init commit on oToken.sol
antoncoding Jul 21, 2020
0def9c7
Merge branch 'master' into otoken
antoncoding Jul 21, 2020
1afd524
Add descriptions for function parameters
antoncoding Jul 21, 2020
d81de7c
Add layout for otoken tests
antoncoding Jul 21, 2020
8a270dc
Remove ERC20 operation from this branch
antoncoding Jul 21, 2020
e2b37bb
Add BokkyPooBahsDateTimeLibrary and convert timestamp to date string.
antoncoding Jul 21, 2020
efdefeb
Add tests for Otoken.sol.
antoncoding Jul 21, 2020
1416525
Merge branch 'master' into otoken-init
antoncoding Jul 21, 2020
c85a46f
Merge with master
antoncoding Jul 21, 2020
80b1fc3
Remove unused function in DateTimeLibrary.
antoncoding Jul 21, 2020
d8547fe
Add comments, test different name/symbol strings
antoncoding Jul 22, 2020
0f012d0
Merge with interface change
antoncoding Jul 22, 2020
115b2ef
Add non-eth asset test
antoncoding Jul 22, 2020
59961e7
Rearrange test orders and add expiry test for now + 3000 years
antoncoding Jul 22, 2020
f35feda
Add 0 expiry and max uint256 expiry test
antoncoding Jul 22, 2020
643de3c
Use new name and symbol
antoncoding Jul 22, 2020
d3ece87
Remove mapping, refactor optino type and month string getters.
antoncoding Jul 22, 2020
3aee134
Remove args for getNameAndSymbol function
antoncoding Jul 22, 2020
59e88a0
Make test coverage 100%
antoncoding Jul 23, 2020
b7857a5
Add test for init twice with different parameters
antoncoding Jul 23, 2020
85dca18
Add mintOtoken and burnOtoken functions and tests
antoncoding Jul 22, 2020
1bcec75
Add comments
antoncoding Jul 22, 2020
bca1d5a
Rearrange function orders
antoncoding Jul 22, 2020
526805c
Simplify otoken test syntax.
antoncoding Jul 23, 2020
c3bf565
Add more comments
antoncoding Jul 23, 2020
d21b751
Merge with new test file.
antoncoding Jul 23, 2020
967b64b
Fix typos
antoncoding Jul 25, 2020
c33204f
Merge branch 'master' into otoken-erc20
antoncoding Jul 25, 2020
7096c07
Merge with master
antoncoding Jul 25, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion .solcover.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module.exports = {
'packages/oz/Ownable.sol',
'packages/oz/upgradeability/ContextUpgradeSafe.sol',
'packages/oz/upgradeability/ERC20Initializable.sol',
'packages/oz/upgradeability/Initializable.sol'
'packages/oz/upgradeability/Initializable.sol',
'packages/BokkyPooBahsDateTimeLibrary.sol'
]
}
3 changes: 2 additions & 1 deletion .solhint.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"prettier/prettier": "error",
"var-name-mixedcase": "off",
"mark-callable-contracts": "off",
"compiler-version":["error", "0.6.10"]
"compiler-version":["error", "0.6.10"],
"not-rely-on-time": "off"
}
}
244 changes: 237 additions & 7 deletions contracts/Otoken.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,242 @@
/**
* SPDX-License-Identifier: UNLICENSED
*/
pragma solidity 0.6.10;
/* SPDX-License-Identifier: UNLICENSED */
pragma solidity =0.6.10;

import {ERC20Initializable} from "./packages/oz/upgradeability/ERC20Initializable.sol";
import {SafeMath} from "./packages/oz/SafeMath.sol";
import {Strings} from "./packages/oz/Strings.sol";
import {BokkyPooBahsDateTimeLibrary} from "./packages/BokkyPooBahsDateTimeLibrary.sol";
import {AddressBookInterface} from "./interfaces/AddressBookInterface.sol";

/**
*
* @title Otoken
* @author Opyn
* @notice Otoken is the ERC20 token for an option.
* @dev The Otoken inherits ERC20Initializable because we need to use the init instead of constructor.
*/
// solhint-disable-next-line no-empty-blocks
contract Otoken {
contract Otoken is ERC20Initializable {
using SafeMath for uint256;

/// @notice address of the addressBook module
address public addressBook;

/// @notice asset that the option references
address public underlyingAsset;

/// @notice asset that the strike price is denominated in
address public strikeAsset;

/// @notice asset that is held as collateral against short/written options
address public collateralAsset;

/// @notice strike price with decimals = 18
uint256 public strikePrice;

/// @notice time of the option represented by unix timestamp
uint256 public expiry;

/// @notice is this a put option, if not it is a call
bool public isPut;

uint256 private constant STRIKE_PRICE_DIGITS = 1e18;

constructor(address _addressBook) public {
addressBook = _addressBook;
}

/**
* @notice initialize the otoken.
* @param _underlyingAsset asset that the option references
* @param _strikeAsset asset that the strike price is denominated in
* @param _collateralAsset asset that is held as collateral against short/written options
* @param _strikePrice strike price with decimals = 18
* @param _expiry time of the option represented by unix timestamp
* @param _isPut is this a put option, if not it is a call
*/
function init(
address _underlyingAsset,
address _strikeAsset,
address _collateralAsset,
uint256 _strikePrice,
uint256 _expiry,
bool _isPut
) external initializer {
underlyingAsset = _underlyingAsset;
strikeAsset = _strikeAsset;
collateralAsset = _collateralAsset;
strikePrice = _strikePrice;
expiry = _expiry;
isPut = _isPut;
(string memory name, string memory symbol) = _getNameAndSymbol();
__ERC20_init_unchained(name, symbol);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where are the decimals set?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is set in the __ERC20_init_unchained function

function __ERC20_init_unchained(string memory name, string memory symbol) internal initializer {
        _name = name;
        _symbol = symbol;
        _decimals = 18;
    }

}

/**
* @notice Mint oToken for an account.
* @dev this is a Controller only method. Access control is taken care of by _beforeTokenTransfer hook.
* @param account the account to mint token to
* @param amount the amount to mint
*/
function mintOtoken(address account, uint256 amount) external {
_mint(account, amount);
}

/**
* @notice Burn oToken from an account.
* @dev this is a Controller only method. Access control is taken care of by _beforeTokenTransfer hook.
* @param account the account to burn token from
* @param amount the amount to burn
*/
function burnOtoken(address account, uint256 amount) external {
_burn(account, amount);
}

/**
* @notice generate name and symbol for an option
* @return name ETHUSDC 05-September-2020 200 Put USDC Collateral
* @return symbol oETHUSDC-05SEP20-200P
*/
function _getNameAndSymbol() internal view returns (string memory name, string memory symbol) {
string memory underlying = _getTokenSymbol(underlyingAsset);
string memory strike = _getTokenSymbol(strikeAsset);
string memory collateral = _getTokenSymbol(collateralAsset);
uint256 displayedStrikePrice = strikePrice.div(STRIKE_PRICE_DIGITS);
// convert expiry to readable string
(uint256 year, uint256 month, uint256 day) = BokkyPooBahsDateTimeLibrary.timestampToDate(expiry);

// Get option type string
(string memory typeSymbol, string memory typeFull) = _getOptionType(isPut);

(string memory monthSymbol, string memory monthFull) = _getMonth(month);

// concat name string: ETHUSDC 05-September-2020 200 Put USDC Collateral
name = string(
abi.encodePacked(
underlying,
strike,
" ",
_uintTo2Chars(day),
"-",
monthFull,
"-",
Strings.toString(year),
" ",
Strings.toString(displayedStrikePrice),
typeFull,
" ",
collateral,
" Collateral"
)
);

// concat symbol string: oETHUSDC-05SEP20-200P
symbol = string(
abi.encodePacked(
"o",
underlying,
strike,
"-",
_uintTo2Chars(day),
monthSymbol,
_uintTo2Chars(year),
"-",
Strings.toString(displayedStrikePrice),
typeSymbol
)
);
}

/**
* @dev get token symbol
* @param token the ERC20 token address
*/
function _getTokenSymbol(address token) internal view returns (string memory) {
if (token == address(0)) return "ETH";
else return ERC20Initializable(token).symbol();
}

/**
* @dev Internal function to get the number with 2 characters.
* @return The 2 characters for the number.
*/
function _uintTo2Chars(uint256 number) internal pure returns (string memory) {
string memory str = Strings.toString(number);
if (number > 99) return Strings.toString(number % 100);
if (number < 10) {
return string(abi.encodePacked("0", str));
} else {
return str;
}
}

/**
* @dev return string of optino type
antoncoding marked this conversation as resolved.
Show resolved Hide resolved
* @return symbol P or C
* @return full Put or Call
*/
function _getOptionType(bool _isPut) internal pure returns (string memory symbol, string memory full) {
if (_isPut) {
return ("P", "Put");
} else {
return ("C", "Call");
}
}

/**
* @dev return string of month.
* @return symbol SEP, DEC ...etc
* @return full September, December ...etc
*/
function _getMonth(uint256 _month) internal pure returns (string memory symbol, string memory full) {
if (_month == 1) {
return ("JAN", "January");
} else if (_month == 2) {
return ("FEB", "February");
} else if (_month == 3) {
return ("MAR", "March");
} else if (_month == 4) {
return ("APR", "April");
} else if (_month == 5) {
return ("MAY", "May");
} else if (_month == 6) {
return ("JUN", "June");
} else if (_month == 7) {
return ("JUL", "July");
} else if (_month == 8) {
return ("AUG", "August");
} else if (_month == 9) {
return ("SEP", "September");
} else if (_month == 10) {
return ("OCT", "October");
} else if (_month == 11) {
return ("NOV", "November");
} else {
return ("DEC", "December");
}
}

/**
* @dev this function overrides the _beforeTokenTransfer hook in ERC20Initializable.sol.
* If the operation is mint or burn, requires msg.sender to be the controller.
* @dev the functino signature is the same as _beforeTokenTransfer defined in ERC20Initializable.sol.
antoncoding marked this conversation as resolved.
Show resolved Hide resolved
* @param from from address
* @param to to address
* @param amount amount to transfer
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal override {
if (from == address(0)) {
require(
msg.sender == AddressBookInterface(addressBook).getController(),
"Otoken: Only Controller can mint Otokens."
);
} else if (to == address(0)) {
require(
msg.sender == AddressBookInterface(addressBook).getController(),
"Otoken: Only Controller can burn Otokens."
);
}
}
}
9 changes: 9 additions & 0 deletions contracts/mocks/MockAddressBook.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,21 @@ pragma solidity 0.6.10;

contract MockAddressBook {
address private _otokenFactoryImpl;
address private _controllerImpl;

function getOtokenFactory() external view returns (address) {
return _otokenFactoryImpl;
}

function getController() external view returns (address) {
return _controllerImpl;
}

function setOtokenFactory(address _otokenFactory) external {
_otokenFactoryImpl = _otokenFactory;
}

function setController(address _controller) external {
_controllerImpl = _controller;
}
}
89 changes: 89 additions & 0 deletions contracts/packages/BokkyPooBahsDateTimeLibrary.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// SPDX-License-Identifier: MIT
// solhint-disable
pragma solidity ^0.6.0;

// ----------------------------------------------------------------------------
// BokkyPooBah's DateTime Library v1.01
//
// A gas-efficient Solidity date and time library
//
// https://github.com/bokkypoobah/BokkyPooBahsDateTimeLibrary
//
// Tested date range 1970/01/01 to 2345/12/31
//
// Conventions:
// Unit | Range | Notes
// :-------- |:-------------:|:-----
// timestamp | >= 0 | Unix timestamp, number of seconds since 1970/01/01 00:00:00 UTC
// year | 1970 ... 2345 |
// month | 1 ... 12 |
// day | 1 ... 31 |
// hour | 0 ... 23 |
// minute | 0 ... 59 |
// second | 0 ... 59 |
// dayOfWeek | 1 ... 7 | 1 = Monday, ..., 7 = Sunday
//
//
// Enjoy. (c) BokkyPooBah / Bok Consulting Pty Ltd 2018-2019. The MIT Licence.
// ----------------------------------------------------------------------------

library BokkyPooBahsDateTimeLibrary {
uint256 constant SECONDS_PER_DAY = 24 * 60 * 60;
int256 constant OFFSET19700101 = 2440588;

// ------------------------------------------------------------------------
// Calculate year/month/day from the number of days since 1970/01/01 using
// the date conversion algorithm from
// http://aa.usno.navy.mil/faq/docs/JD_Formula.php
// and adding the offset 2440588 so that 1970/01/01 is day 0
//
// int L = days + 68569 + offset
// int N = 4 * L / 146097
// L = L - (146097 * N + 3) / 4
// year = 4000 * (L + 1) / 1461001
// L = L - 1461 * year / 4 + 31
// month = 80 * L / 2447
// dd = L - 2447 * month / 80
// L = month / 11
// month = month + 2 - 12 * L
// year = 100 * (N - 49) + year + L
// ------------------------------------------------------------------------
function _daysToDate(uint256 _days)
internal
pure
returns (
uint256 year,
uint256 month,
uint256 day
)
{
int256 __days = int256(_days);

int256 L = __days + 68569 + OFFSET19700101;
int256 N = (4 * L) / 146097;
L = L - (146097 * N + 3) / 4;
int256 _year = (4000 * (L + 1)) / 1461001;
L = L - (1461 * _year) / 4 + 31;
int256 _month = (80 * L) / 2447;
int256 _day = L - (2447 * _month) / 80;
L = _month / 11;
_month = _month + 2 - 12 * L;
_year = 100 * (N - 49) + _year + L;

year = uint256(_year);
month = uint256(_month);
day = uint256(_day);
}

function timestampToDate(uint256 timestamp)
internal
pure
returns (
uint256 year,
uint256 month,
uint256 day
)
{
(year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY);
}
}