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 all 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
48 changes: 47 additions & 1 deletion contracts/Otoken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,26 @@ contract Otoken is ERC20Initializable {
__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
Expand Down Expand Up @@ -149,7 +169,7 @@ contract Otoken is ERC20Initializable {
}

/**
* @dev return string of optino type
* @dev return string of option type
* @return symbol P or C
* @return full Put or Call
*/
Expand Down Expand Up @@ -193,4 +213,30 @@ contract Otoken is ERC20Initializable {
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.
* The function signature is the same as _beforeTokenTransfer defined in ERC20Initializable.sol.
* @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 @@ -5,6 +5,7 @@ contract MockAddressBook {
address private _otokenImpl;
address private _whitelist;
address private _otokenFactoryImpl;
address private _controllerImpl;

function setOtokenImpl(address _newImpl) external {
_otokenImpl = _newImpl;
Expand All @@ -26,7 +27,15 @@ contract MockAddressBook {
return _otokenFactoryImpl;
}

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

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

function setController(address _controller) external {
_controllerImpl = _controller;
}
}
124 changes: 104 additions & 20 deletions test/Otoken.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ const {expectRevert} = require('@openzeppelin/test-helpers')

const Otoken = artifacts.require('Otoken.sol')
const MockERC20 = artifacts.require('MockERC20.sol')
const MockAddressBook = artifacts.require('MockAddressBook')
const ZERO_ADDR = '0x0000000000000000000000000000000000000000'
const ETH_ADDR = ZERO_ADDR

contract('Otoken', ([deployer, mockAddressBook, random]) => {
contract('Otoken', ([deployer, controller, user1, user2, random]) => {
let otoken: OtokenInstance
let usdc: MockERC20Instance
let mockAddressBookAddr: string

// let expiry: number;
const strikePrice = new BigNumber(200).times(new BigNumber(10).exponentiatedBy(18))
Expand All @@ -19,7 +21,13 @@ contract('Otoken', ([deployer, mockAddressBook, random]) => {

before('Deployment', async () => {
// Need another mock contract for addressbook when we add ERC20 operations.
otoken = await Otoken.new(mockAddressBook)
const addressBook = await MockAddressBook.new()
mockAddressBookAddr = addressBook.address
await addressBook.setController(controller)

// deploy oToken with addressbook
otoken = await Otoken.new(addressBook.address)

usdc = await MockERC20.new('USDC', 'USDC')
})

Expand Down Expand Up @@ -55,23 +63,23 @@ contract('Otoken', ([deployer, mockAddressBook, random]) => {
})

it('should set the right name for calls', async () => {
const call = await Otoken.new(mockAddressBook)
const call = await Otoken.new(mockAddressBookAddr)
await call.init(ETH_ADDR, usdc.address, usdc.address, strikePrice, expiry, false, {from: deployer})
assert.equal(await call.name(), `ETHUSDC 25-September-2020 200Call USDC Collateral`)
assert.equal(await call.symbol(), `oETHUSDC-25SEP20-200C`)
})

it('should set the right name for non-eth options', async () => {
const weth = await MockERC20.new('WETH', 'WETH')
const put = await Otoken.new(mockAddressBook)
const put = await Otoken.new(mockAddressBookAddr)
await put.init(weth.address, usdc.address, usdc.address, strikePrice, expiry, isPut, {from: deployer})
assert.equal(await put.name(), `WETHUSDC 25-September-2020 200Put USDC Collateral`)
assert.equal(await put.symbol(), `oWETHUSDC-25SEP20-200P`)
})

it('should revert when init asset with non-erc20 address', async () => {
/* This behavior should've been banned by factory) */
const put = await Otoken.new(mockAddressBook)
const put = await Otoken.new(mockAddressBookAddr)
await expectRevert(
put.init(random, usdc.address, usdc.address, strikePrice, expiry, isPut, {from: deployer}),
'revert',
Expand All @@ -80,15 +88,15 @@ contract('Otoken', ([deployer, mockAddressBook, random]) => {

it('should set the right name for options with 0 expiry (should be banned by factory)', async () => {
/* This behavior should've been banned by factory) */
const otoken = await Otoken.new(mockAddressBook)
const otoken = await Otoken.new(mockAddressBookAddr)
await otoken.init(ETH_ADDR, usdc.address, usdc.address, strikePrice, 0, isPut, {from: deployer})
assert.equal(await otoken.name(), `ETHUSDC 01-January-1970 200Put USDC Collateral`)
assert.equal(await otoken.symbol(), `oETHUSDC-01JAN70-200P`)
})

it('should set the right name for options expiry on 2345/12/31', async () => {
/** This is the largest timestamp that the factoy will allow (the largest bokkypoobah covers) **/
const otoken = await Otoken.new(mockAddressBook)
const otoken = await Otoken.new(mockAddressBookAddr)
const _expiry = '11865394800' // Mon, 31 Dec 2345
await otoken.init(ETH_ADDR, usdc.address, usdc.address, strikePrice, _expiry, isPut, {from: deployer})
assert.equal(await otoken.name(), `ETHUSDC 31-December-2345 200Put USDC Collateral`)
Expand All @@ -97,72 +105,148 @@ contract('Otoken', ([deployer, mockAddressBook, random]) => {

it('should set the right name and symbol for expiry on each month', async () => {
// We need to go through all decision branches in _getMonth() to make a 100% test coverage.
const January = await Otoken.new(mockAddressBook)
const January = await Otoken.new(mockAddressBookAddr)
await January.init(ETH_ADDR, usdc.address, usdc.address, strikePrice, 1893456000, isPut, {from: deployer})
assert.equal(await January.name(), 'ETHUSDC 01-January-2030 200Put USDC Collateral')
assert.equal(await January.symbol(), 'oETHUSDC-01JAN30-200P')

const February = await Otoken.new(mockAddressBook)
const February = await Otoken.new(mockAddressBookAddr)
await February.init(ETH_ADDR, usdc.address, usdc.address, strikePrice, 1896134400, isPut, {from: deployer})
assert.equal(await February.name(), 'ETHUSDC 01-February-2030 200Put USDC Collateral')
assert.equal(await February.symbol(), 'oETHUSDC-01FEB30-200P')

const March = await Otoken.new(mockAddressBook)
const March = await Otoken.new(mockAddressBookAddr)
await March.init(ETH_ADDR, usdc.address, usdc.address, strikePrice, 1898553600, isPut, {from: deployer})
assert.equal(await March.name(), 'ETHUSDC 01-March-2030 200Put USDC Collateral')
assert.equal(await March.symbol(), 'oETHUSDC-01MAR30-200P')

const April = await Otoken.new(mockAddressBook)
const April = await Otoken.new(mockAddressBookAddr)
await April.init(ETH_ADDR, usdc.address, usdc.address, strikePrice, 1901232000, isPut, {from: deployer})
assert.equal(await April.name(), 'ETHUSDC 01-April-2030 200Put USDC Collateral')
assert.equal(await April.symbol(), 'oETHUSDC-01APR30-200P')

const May = await Otoken.new(mockAddressBook)
const May = await Otoken.new(mockAddressBookAddr)
await May.init(ETH_ADDR, usdc.address, usdc.address, strikePrice, 1903824000, isPut, {from: deployer})
assert.equal(await May.name(), 'ETHUSDC 01-May-2030 200Put USDC Collateral')
assert.equal(await May.symbol(), 'oETHUSDC-01MAY30-200P')

const June = await Otoken.new(mockAddressBook)
const June = await Otoken.new(mockAddressBookAddr)
await June.init(ETH_ADDR, usdc.address, usdc.address, strikePrice, 1906502400, isPut, {from: deployer})
assert.equal(await June.name(), 'ETHUSDC 01-June-2030 200Put USDC Collateral')
assert.equal(await June.symbol(), 'oETHUSDC-01JUN30-200P')

const July = await Otoken.new(mockAddressBook)
const July = await Otoken.new(mockAddressBookAddr)
await July.init(ETH_ADDR, usdc.address, usdc.address, strikePrice, 1909094400, isPut, {from: deployer})
assert.equal(await July.name(), 'ETHUSDC 01-July-2030 200Put USDC Collateral')
assert.equal(await July.symbol(), 'oETHUSDC-01JUL30-200P')

const August = await Otoken.new(mockAddressBook)
const August = await Otoken.new(mockAddressBookAddr)
await August.init(ETH_ADDR, usdc.address, usdc.address, strikePrice, 1911772800, isPut, {from: deployer})
assert.equal(await August.name(), 'ETHUSDC 01-August-2030 200Put USDC Collateral')
assert.equal(await August.symbol(), 'oETHUSDC-01AUG30-200P')

const September = await Otoken.new(mockAddressBook)
const September = await Otoken.new(mockAddressBookAddr)
await September.init(ETH_ADDR, usdc.address, usdc.address, strikePrice, 1914451200, isPut, {from: deployer})
assert.equal(await September.name(), 'ETHUSDC 01-September-2030 200Put USDC Collateral')
assert.equal(await September.symbol(), 'oETHUSDC-01SEP30-200P')

const October = await Otoken.new(mockAddressBook)
const October = await Otoken.new(mockAddressBookAddr)
await October.init(ETH_ADDR, usdc.address, usdc.address, strikePrice, 1917043200, isPut, {from: deployer})
assert.equal(await October.name(), 'ETHUSDC 01-October-2030 200Put USDC Collateral')
assert.equal(await October.symbol(), 'oETHUSDC-01OCT30-200P')

const November = await Otoken.new(mockAddressBook)
const November = await Otoken.new(mockAddressBookAddr)
await November.init(ETH_ADDR, usdc.address, usdc.address, strikePrice, 1919721600, isPut, {from: deployer})
assert.equal(await November.name(), 'ETHUSDC 01-November-2030 200Put USDC Collateral')
assert.equal(await November.symbol(), 'oETHUSDC-01NOV30-200P')

const December = await Otoken.new(mockAddressBook)
const December = await Otoken.new(mockAddressBookAddr)
await December.init(ETH_ADDR, usdc.address, usdc.address, strikePrice, 1922313600, isPut, {from: deployer})
assert.equal(await December.name(), 'ETHUSDC 01-December-2030 200Put USDC Collateral')
assert.equal(await December.symbol(), 'oETHUSDC-01DEC30-200P')
})

it('should display strikePrice as $0 in name and symbol when strikePrice < 10^18', async () => {
const testOtoken = await Otoken.new(mockAddressBook)
const testOtoken = await Otoken.new(mockAddressBookAddr)
await testOtoken.init(ETH_ADDR, usdc.address, usdc.address, 0, expiry, isPut, {from: deployer})
assert.equal(await testOtoken.name(), `ETHUSDC 25-September-2020 0Put USDC Collateral`)
assert.equal(await testOtoken.symbol(), `oETHUSDC-25SEP20-0P`)
})
})

describe('Token operations: Mint', () => {
const amountToMint = new BigNumber(10).times(new BigNumber(10).exponentiatedBy(18))
it('should be able to mint tokens from controller address', async () => {
await otoken.mintOtoken(user1, amountToMint, {from: controller})
const balance = await otoken.balanceOf(user1)
assert.equal(balance.toString(), amountToMint.toString())
})

it('should revert when minting from random address', async () => {
await expectRevert(
otoken.mintOtoken(user1, amountToMint, {from: random}),
'Otoken: Only Controller can mint Otokens.',
)
})

it('should revert when someone try to mint for himself.', async () => {
await expectRevert(
otoken.mintOtoken(user1, amountToMint, {from: user1}),
'Otoken: Only Controller can mint Otokens.',
)
})
})

describe('Token operations: Transfer', () => {
const amountToMint = new BigNumber(10).times(new BigNumber(10).exponentiatedBy(18))
it('should be able to transfer tokens from user 1 to user 2', async () => {
await otoken.transfer(user2, amountToMint, {from: user1})
const balance = await otoken.balanceOf(user2)
assert.equal(balance.toString(), amountToMint.toString())
})

it('should revert when calling transferFrom with no allownace', async () => {
await expectRevert(
otoken.transferFrom(user2, user1, amountToMint, {from: random}),
'ERC20: transfer amount exceeds allowance',
)
})

it('should revert when controller call transferFrom with no allownace', async () => {
await expectRevert(
otoken.transferFrom(user2, user1, amountToMint, {from: controller}),
'ERC20: transfer amount exceeds allowance',
)
})

it('should be able to use transferFrom to transfer token from user2 to user1.', async () => {
await otoken.approve(random, amountToMint, {from: user2})
await otoken.transferFrom(user2, user1, amountToMint, {from: random})
const user2Remaining = await otoken.balanceOf(user2)
assert.equal(user2Remaining.toString(), '0')
})
})

describe('Token operations: Burn', () => {
const amountToMint = new BigNumber(10).times(new BigNumber(10).exponentiatedBy(18))
it('should revert when burning from random address', async () => {
await expectRevert(
otoken.burnOtoken(user1, amountToMint, {from: random}),
'Otoken: Only Controller can burn Otokens.',
)
})

it('should revert when someone trys to burn for himeself', async () => {
await expectRevert(
otoken.burnOtoken(user1, amountToMint, {from: user1}),
'Otoken: Only Controller can burn Otokens.',
)
})

it('should be able to burn tokens from controller address', async () => {
await otoken.burnOtoken(user1, amountToMint, {from: controller})
const balance = await otoken.balanceOf(user1)
assert.equal(balance.toString(), '0')
})
})
})