Skip to content

Commit

Permalink
test integration flow with borrowable pool
Browse files Browse the repository at this point in the history
  • Loading branch information
Your Name committed Aug 15, 2022
1 parent 660d046 commit 431764b
Show file tree
Hide file tree
Showing 4 changed files with 1,397 additions and 0 deletions.
328 changes: 328 additions & 0 deletions test/integration-tests/nakedCallExpireItm-borrowableMarginPool.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,328 @@
import {
MockERC20Instance,
MarginCalculatorInstance,
AddressBookInstance,
MockOracleInstance,
OtokenInstance,
ControllerInstance,
WhitelistInstance,
MarginPoolInstance,
BorrowableMarginPoolInstance,
OtokenFactoryInstance,
} from '../../build/types/truffle-types'
import { createTokenAmount, createValidExpiry } from '../utils'
import BigNumber from 'bignumber.js'

const { time } = require('@openzeppelin/test-helpers')
const AddressBook = artifacts.require('AddressBook.sol')
const MockOracle = artifacts.require('MockOracle.sol')
const Otoken = artifacts.require('Otoken.sol')
const MockERC20 = artifacts.require('MockERC20.sol')
const MarginCalculator = artifacts.require('MarginCalculator.sol')
const Whitelist = artifacts.require('Whitelist.sol')
const MarginPool = artifacts.require('MarginPool.sol')
const BorrowableMarginPool = artifacts.require('BorrowableMarginPool.sol')
const Controller = artifacts.require('Controller.sol')
const MarginVault = artifacts.require('MarginVault.sol')
const OTokenFactory = artifacts.require('OtokenFactory.sol')
const ZERO_ADDR = '0x0000000000000000000000000000000000000000'

enum ActionType {
OpenVault,
MintShortOption,
BurnShortOption,
DepositLongOption,
WithdrawLongOption,
DepositCollateral,
WithdrawCollateral,
SettleVault,
Redeem,
Call,
}

contract('Naked Call Option expires Itm flow', ([accountOwner1, buyer]) => {
let expiry: number

let addressBook: AddressBookInstance
let calculator: MarginCalculatorInstance
let controllerProxy: ControllerInstance
let controllerImplementation: ControllerInstance
let marginPool: MarginPoolInstance
let borrowableMarginPool: BorrowableMarginPoolInstance
let whitelist: WhitelistInstance
let otokenImplementation: OtokenInstance
let otokenFactory: OtokenFactoryInstance

// oracle modulce mock
let oracle: MockOracleInstance

let usdc: MockERC20Instance
let weth: MockERC20Instance

let ethCall: OtokenInstance
const strikePrice = 300

const optionsAmount = 10
const collateralAmount = optionsAmount
let vaultCounter: number

const usdcDecimals = 6
const wethDecimals = 18

before('set up contracts', async () => {
const now = (await time.latest()).toNumber()
expiry = createValidExpiry(now, 30)

// setup usdc and weth
usdc = await MockERC20.new('USDC', 'USDC', usdcDecimals)
weth = await MockERC20.new('WETH', 'WETH', wethDecimals)
// initiate addressbook first.
addressBook = await AddressBook.new()
// setup margin pool
marginPool = await MarginPool.new(addressBook.address)
// setup margin pool v2
borrowableMarginPool = await BorrowableMarginPool.new(addressBook.address)
// setup margin vault
const lib = await MarginVault.new()
// setup controllerProxy module
await Controller.link('MarginVault', lib.address)
controllerImplementation = await Controller.new(addressBook.address)
// setup mock Oracle module
oracle = await MockOracle.new(addressBook.address)
// setup calculator
calculator = await MarginCalculator.new(oracle.address)
// setup whitelist module
whitelist = await Whitelist.new(addressBook.address)
await whitelist.whitelistCollateral(weth.address)
await whitelist.whitelistCollateral(usdc.address)
whitelist.whitelistProduct(weth.address, usdc.address, usdc.address, true)
whitelist.whitelistProduct(weth.address, usdc.address, weth.address, false)
// setup otoken
otokenImplementation = await Otoken.new()
// setup factory
otokenFactory = await OTokenFactory.new(addressBook.address)

// setup address book
await addressBook.setOracle(oracle.address)
await addressBook.setMarginCalculator(calculator.address)
await addressBook.setWhitelist(whitelist.address)
await addressBook.setMarginPool(marginPool.address)
await addressBook.setAddress(web3.utils.soliditySha3('BORROWABLE_POOL'), borrowableMarginPool.address, {
from: accountOwner1,
})
await addressBook.setOtokenFactory(otokenFactory.address)
await addressBook.setOtokenImpl(otokenImplementation.address)
await addressBook.setController(controllerImplementation.address)

const controllerProxyAddress = await addressBook.getController()
controllerProxy = await Controller.at(controllerProxyAddress)

await otokenFactory.createOtoken(
weth.address,
usdc.address,
weth.address,
createTokenAmount(strikePrice),
expiry,
false,
)

const ethCallAddress = await otokenFactory.getOtoken(
weth.address,
usdc.address,
weth.address,
createTokenAmount(strikePrice),
expiry,
false,
)

ethCall = await Otoken.at(ethCallAddress)
// mint weth to user
const account1OwnerWeth = createTokenAmount(2 * collateralAmount, wethDecimals)
await weth.mint(accountOwner1, account1OwnerWeth)

// have the user approve all the weth transfers
await weth.approve(borrowableMarginPool.address, account1OwnerWeth, { from: accountOwner1 })

const vaultCounterBefore = new BigNumber(await controllerProxy.getAccountVaultCounter(accountOwner1))
vaultCounter = vaultCounterBefore.toNumber() + 1

await borrowableMarginPool.setOptionsVaultWhitelistedStatus(accountOwner1, true, { from: accountOwner1 })
await borrowableMarginPool.setOTokenBuyerWhitelistedStatus(buyer, true, { from: accountOwner1 })
})

describe('Integration test: Close a naked call after it expires ITM', () => {
const scaledOptionsAmount = createTokenAmount(optionsAmount)
const scaledCollateralAmount = createTokenAmount(collateralAmount, wethDecimals)
const expirySpotPrice = 400
before('Seller should be able to open a short call option', async () => {
const actionArgs = [
{
actionType: ActionType.OpenVault,
owner: accountOwner1,
secondAddress: accountOwner1,
asset: ZERO_ADDR,
vaultId: vaultCounter,
amount: '0',
index: '0',
data: ZERO_ADDR,
},
{
actionType: ActionType.MintShortOption,
owner: accountOwner1,
secondAddress: accountOwner1,
asset: ethCall.address,
vaultId: vaultCounter,
amount: scaledOptionsAmount,
index: '0',
data: ZERO_ADDR,
},
{
actionType: ActionType.DepositCollateral,
owner: accountOwner1,
secondAddress: accountOwner1,
asset: weth.address,
vaultId: vaultCounter,
amount: scaledCollateralAmount,
index: '0',
data: ZERO_ADDR,
},
]

await controllerProxy.operate(actionArgs, { from: accountOwner1 })
})

it('Seller: close an ITM position after expiry', async () => {
// Keep track of balances before
const ownerWethBalanceBefore = new BigNumber(await weth.balanceOf(accountOwner1))
const borrowableMarginPoolWethBalanceBefore = new BigNumber(await weth.balanceOf(borrowableMarginPool.address))
const ownerOtokenBalanceBefore = new BigNumber(await ethCall.balanceOf(accountOwner1))
const oTokenSupplyBefore = new BigNumber(await ethCall.totalSupply())

// Check that we start at a valid state
const vaultBefore = await controllerProxy.getVaultWithDetails(accountOwner1, vaultCounter)
const vaultStateBefore = await calculator.getExcessCollateral(vaultBefore[0], vaultBefore[1])
assert.equal(vaultStateBefore[0].toString(), '0')
assert.equal(vaultStateBefore[1], true)

// Set the oracle price
if ((await time.latest()) < expiry) {
await time.increaseTo(expiry + 2)
}
const strikePriceChange = Math.max(expirySpotPrice - strikePrice, 0)
const scaledETHPrice = createTokenAmount(expirySpotPrice, 8)
const scaledUSDCPrice = createTokenAmount(1)
await oracle.setExpiryPriceFinalizedAllPeiodOver(weth.address, expiry, scaledETHPrice, true)
await oracle.setExpiryPriceFinalizedAllPeiodOver(usdc.address, expiry, scaledUSDCPrice, true)

const collateralPayout = collateralAmount - (strikePriceChange * optionsAmount) / expirySpotPrice

// Check that after expiry, the vault excess balance has updated as expected
const vaultStateBeforeSettlement = await calculator.getExcessCollateral(vaultBefore[0], vaultBefore[1])
assert.equal(
vaultStateBeforeSettlement[0].toString(),
createTokenAmount(collateralPayout, wethDecimals),
'vault before settlement collateral excess mismatch',
)
assert.equal(vaultStateBeforeSettlement[1], true)

const actionArgs = [
{
actionType: ActionType.SettleVault,
owner: accountOwner1,
secondAddress: accountOwner1,
asset: ZERO_ADDR,
vaultId: vaultCounter,
amount: '0',
index: '0',
data: ZERO_ADDR,
},
]

await controllerProxy.operate(actionArgs, { from: accountOwner1 })

// keep track of balances after
const ownerWethBalanceAfter = new BigNumber(await weth.balanceOf(accountOwner1))
const borrowableMarginPoolWethBalanceAfter = new BigNumber(await weth.balanceOf(borrowableMarginPool.address))

const ownerOtokenBalanceAfter = new BigNumber(await ethCall.balanceOf(accountOwner1))
const oTokenSupplyAfter = new BigNumber(await ethCall.totalSupply())

// check balances before and after changed as expected
assert.equal(
ownerWethBalanceBefore.plus(createTokenAmount(collateralPayout, wethDecimals)).toString(),
ownerWethBalanceAfter.toString(),
)
assert.equal(
borrowableMarginPoolWethBalanceBefore.minus(createTokenAmount(collateralPayout, wethDecimals)).toString(),
borrowableMarginPoolWethBalanceAfter.toString(),
)
assert.equal(ownerOtokenBalanceBefore.toString(), ownerOtokenBalanceAfter.toString())
assert.equal(oTokenSupplyBefore.toString(), oTokenSupplyAfter.toString())

// Check that we end at a valid state
const vaultAfter = await controllerProxy.getVaultWithDetails(accountOwner1, vaultCounter)
const vaultStateAfter = await calculator.getExcessCollateral(vaultAfter[0], vaultAfter[1])
assert.equal(vaultStateAfter[0].toString(), '0')
assert.equal(vaultStateAfter[1], true)

// Check the vault balances stored in the contract
assert.equal(vaultAfter[0].shortOtokens.length, 0, 'Length of the short otoken array in the vault is incorrect')
assert.equal(vaultAfter[0].collateralAssets.length, 0, 'Length of the collateral array in the vault is incorrect')
assert.equal(vaultAfter[0].longOtokens.length, 0, 'Length of the long otoken array in the vault is incorrect')

assert.equal(vaultAfter[0].shortAmounts.length, 0, 'Length of the short amounts array in the vault is incorrect')
assert.equal(
vaultAfter[0].collateralAmounts.length,
0,
'Length of the collateral amounts array in the vault is incorrect',
)
assert.equal(vaultAfter[0].longAmounts.length, 0, 'Length of the long amounts array in the vault is incorrect')
})

it('Buyer: redeem ITM call option after expiry', async () => {
// owner sells their call option
await ethCall.transfer(buyer, scaledOptionsAmount, { from: accountOwner1 })
// oracle orice increases
const strikePriceChange = Math.max(expirySpotPrice - strikePrice, 0)

// Keep track of balances before
const ownerWethBalanceBefore = new BigNumber(await weth.balanceOf(buyer))
const borrowableMarginPoolWethBalanceBefore = new BigNumber(await weth.balanceOf(borrowableMarginPool.address))
const ownerOtokenBalanceBefore = new BigNumber(await ethCall.balanceOf(buyer))
const oTokenSupplyBefore = new BigNumber(await ethCall.totalSupply())

const actionArgs = [
{
actionType: ActionType.Redeem,
owner: buyer,
secondAddress: buyer,
asset: ethCall.address,
vaultId: '0',
amount: scaledOptionsAmount,
index: '0',
data: ZERO_ADDR,
},
]

await controllerProxy.operate(actionArgs, { from: buyer })

// keep track of balances after
const ownerWethBalanceAfter = new BigNumber(await weth.balanceOf(buyer))
const borrowableMarginPoolWethBalanceAfter = new BigNumber(await weth.balanceOf(borrowableMarginPool.address))
const ownerOtokenBalanceAfter = new BigNumber(await ethCall.balanceOf(buyer))
const oTokenSupplyAfter = new BigNumber(await ethCall.totalSupply())

const payout = (strikePriceChange * optionsAmount) / expirySpotPrice
const scaledPayout = createTokenAmount(payout, wethDecimals)

// check balances before and after changed as expected
assert.equal(ownerWethBalanceBefore.plus(scaledPayout).toString(), ownerWethBalanceAfter.toString())
assert.equal(
borrowableMarginPoolWethBalanceBefore.minus(scaledPayout).toString(),
borrowableMarginPoolWethBalanceAfter.toString(),
)
assert.equal(ownerOtokenBalanceBefore.minus(scaledOptionsAmount).toString(), ownerOtokenBalanceAfter.toString())
assert.equal(oTokenSupplyBefore.minus(scaledOptionsAmount).toString(), oTokenSupplyAfter.toString())
})
})
})
Loading

0 comments on commit 431764b

Please sign in to comment.