-
Notifications
You must be signed in to change notification settings - Fork 98
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test integration flow with borrowable pool
- Loading branch information
Your Name
committed
Aug 15, 2022
1 parent
660d046
commit 431764b
Showing
4 changed files
with
1,397 additions
and
0 deletions.
There are no files selected for viewing
328 changes: 328 additions & 0 deletions
328
test/integration-tests/nakedCallExpireItm-borrowableMarginPool.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) | ||
}) | ||
}) | ||
}) |
Oops, something went wrong.