Skip to content

Commit

Permalink
Merge pull request #182 from opynfinance/task/exerciser-wrapper
Browse files Browse the repository at this point in the history
Add Exerciser Wrapper
  • Loading branch information
aparnakr committed Aug 19, 2020
2 parents faf007a + 9b56785 commit da70aa1
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 35 deletions.
1 change: 1 addition & 0 deletions .solcover.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ module.exports = {
'packages/Ownable.sol',
'packages/Roles.sol',
'packages/SafeMath.sol',
'packages/WETH.sol',
'mocks/MockOracle.sol',
'mocks/MockERC20.sol',
'mocks/MockCtoken.sol',
Expand Down
46 changes: 46 additions & 0 deletions contracts/Exerciser.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
pragma solidity 0.5.10;

import "./interfaces/OtokenInterface.sol";
import "./interfaces/WethInterface.sol";
import "./packages/IERC20.sol";


contract Exerciser {
WethInterface public weth;

event WrapperExercise(
address indexed otoken,
uint256 indexed otokenAmount,
uint256 indexed collateralExercised,
address user
);

constructor(address payable _weth) public {
weth = WethInterface(_weth);
}

function exercise(
address _otoken,
uint256 _amount,
address payable[] calldata _owners
) external payable returns (uint256) {
// 1. pull token from user's address
OtokenInterface otoken = OtokenInterface(_otoken);
otoken.transferFrom(msg.sender, address(this), _amount);
// 2. convert eth to weth
weth.deposit.value(msg.value)();
// 3. exercise
weth.approve(_otoken, msg.value);
otoken.exercise(_amount, _owners);
// 4. transfer collateral to user
address collateral = otoken.collateral();
uint256 amountToTakeOut = IERC20(collateral).balanceOf(address(this));
IERC20(collateral).transfer(msg.sender, amountToTakeOut);
// 5. transfer remaining weth back to user
if (weth.balanceOf(address(this)) > 0) {
weth.transfer(msg.sender, weth.balanceOf(address(this)));
}

emit WrapperExercise(_otoken, _amount, amountToTakeOut, msg.sender);
}
}
17 changes: 17 additions & 0 deletions contracts/interfaces/OtokenInterface.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
pragma solidity ^0.5.10;


interface OtokenInterface {
function exercise(
uint256 oTokensToExercise,
address payable[] calldata vaultsToExerciseFrom
) external payable;

function collateral() external view returns (address);

function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
}
14 changes: 14 additions & 0 deletions contracts/interfaces/WethInterface.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
pragma solidity ^0.5.10;


interface WethInterface {
function deposit() external payable;

function approve(address sender, uint256 amount) external;

function balanceOf(address account) external view returns (uint256);

function transfer(address recipient, uint256 amount)
external
returns (bool);
}
125 changes: 125 additions & 0 deletions contracts/packages/WETH.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright (C) 2015, 2016, 2017 Dapphub

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// SPDX-License-Identifier: GNU GPL
pragma solidity 0.5.10;


/**
* @author Opyn Team
* @title WETH contract
* @dev A wrapper to use ETH as collateral
*/
contract WETH9 {
string public name = "Wrapped Ether";
string public symbol = "WETH";
uint8 public decimals = 18;

/// @notice emmitted when a sender approve WETH transfer
event Approval(address indexed src, address indexed guy, uint256 wad);
/// @notice emmitted when a sender transfer WETH
event Transfer(address indexed src, address indexed dst, uint256 wad);
/// @notice emitted when a sender deposit ETH into this contract
event Deposit(address indexed dst, uint256 wad);
/// @notice emmited when a sender withdraw ETH from this contract
event Withdrawal(address indexed src, uint256 wad);

/// @notice mapping between address and WETH balance
mapping(address => uint256) public balanceOf;
/// @notice mapping between addresses and allowance amount
mapping(address => mapping(address => uint256)) public allowance;

/**
* @notice Wrap deposited ETH into WETH
*/
function deposit() public payable {
balanceOf[msg.sender] += msg.value;
emit Deposit(msg.sender, msg.value);
}

/**
* @notice withdraw ETH from contract
* @dev Unwrap from WETH to ETH
* @param _wad amount WETH to unwrap and withdraw
*/
function withdraw(uint256 _wad) public {
require(
balanceOf[msg.sender] >= _wad,
"WETH9: insufficient sender balance"
);
balanceOf[msg.sender] -= _wad;
msg.sender.transfer(_wad);
emit Withdrawal(msg.sender, _wad);
}

/**
* @notice get ETH total supply
* @return total supply
*/
function totalSupply() public view returns (uint256) {
return address(this).balance;
}

/**
* @notice approve transfer
* @param _guy address to approve
* @param _wad amount of WETH
* @return true if tx succeeded
*/
function approve(address _guy, uint256 _wad) public returns (bool) {
allowance[msg.sender][_guy] = _wad;
emit Approval(msg.sender, _guy, _wad);
return true;
}

/**
* @notice transfer WETH
* @param _dst destination address
* @param _wad amount to transfer
* @return true if tx succeeded
*/
function transfer(address _dst, uint256 _wad) public returns (bool) {
return transferFrom(msg.sender, _dst, _wad);
}

/**
* @notice transfer from address
* @param _src source address
* @param _dst destination address
* @param _wad amount to transfer
* @return true if tx succeeded
*/
function transferFrom(
address _src,
address _dst,
uint256 _wad
) public returns (bool) {
require(balanceOf[_src] >= _wad, "WETH9: insufficient source balance");

if (_src != msg.sender && allowance[_src][msg.sender] != uint256(-1)) {
require(
allowance[_src][msg.sender] >= _wad,
"WETH9: invalid allowance"
);
allowance[_src][msg.sender] -= _wad;
}

balanceOf[_src] -= _wad;
balanceOf[_dst] += _wad;

emit Transfer(_src, _dst, _wad);

return true;
}
}
84 changes: 49 additions & 35 deletions test/series/weth-put.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {
OptionsFactoryInstance,
OTokenInstance,
Erc20MintableInstance
Erc20MintableInstance,
ExerciserInstance,
Weth9Instance
} from '../../build/types/truffle-types';

import BigNumber from 'bignumber.js';
Expand All @@ -11,23 +13,24 @@ const OTokenContract = artifacts.require('oToken');
const OptionsFactory = artifacts.require('OptionsFactory');
const MockERC20 = artifacts.require('MockERC20');

import Reverter from '../utils/reverter';
const Exerciser = artifacts.require('Exerciser');
const WETH = artifacts.require('WETH9');

contract('OptionsContract: weth put', accounts => {
const reverter = new Reverter(web3);

const creatorAddress = accounts[0];
const firstOwner = accounts[1];
const tokenHolder = accounts[2];

let optionsFactory: OptionsFactoryInstance;
let oWeth: OTokenInstance;
// let oracle: MockwethoundOracleInstance;
let weth: Erc20MintableInstance;

let exerciser: ExerciserInstance;

let weth: Weth9Instance;
let usdc: Erc20MintableInstance;

const usdcAmount = '1000000000'; // 1000 USDC
const wethAmount = '1000000000000000000000'; // 1000 weth
const usdcAmount = '1000000000'; // 10000 USDC

const _name = 'TH put 250';
const _symbol = 'oEth 250';
Expand All @@ -39,9 +42,8 @@ contract('OptionsContract: weth put', accounts => {

// 1. Deploy mock contracts
// 1.2 Mock weth contract
weth = await MockERC20.new('weth', 'weth', 18);
await weth.mint(creatorAddress, wethAmount); // 1000 weth
await weth.mint(tokenHolder, wethAmount);
weth = await WETH.new();
exerciser = await Exerciser.new(weth.address);

// 1.3 Mock USDC contract
usdc = await MockERC20.new('USDC', 'USDC', 6);
Expand Down Expand Up @@ -71,16 +73,10 @@ contract('OptionsContract: weth put', accounts => {

const optionsContractAddr = optionsContractResult.logs[1].args[0];
oWeth = await OTokenContract.at(optionsContractAddr);

await reverter.snapshot();
});

describe('New option parameter test', () => {
it('should have basic setting', async () => {
await oWeth.setDetails(_name, _symbol, {
from: creatorAddress
});

assert.equal(await oWeth.name(), String(_name), 'set name error');
assert.equal(await oWeth.symbol(), String(_symbol), 'set symbol error');
});
Expand Down Expand Up @@ -141,41 +137,59 @@ contract('OptionsContract: weth put', accounts => {
assert.equal(vault[2].toString(), '0');
});

it('should not exercise without underlying allowance', async () => {
await oWeth.transfer(tokenHolder, '4000000', {from: firstOwner}); // transfer 80 oWeth

await expectRevert(
oWeth.exercise('4000000', [firstOwner], {
from: tokenHolder
}),
'transfer amount exceeds allowance.'
);
});

it('should be able to exercise', async () => {
it('should be able to exercise from wrapper exerciser ', async () => {
const amountToExercise = '4000000';
await oWeth.transfer(tokenHolder, amountToExercise, {from: firstOwner});
// weth
const underlyingRequired = (
await oWeth.underlyingRequiredToExercise(amountToExercise)
).toString();

await weth.approve(oWeth.address, underlyingRequired, {
// approve exerciser to spend otoken
await oWeth.approve(exerciser.address, amountToExercise, {
from: tokenHolder
});

const exerciseTx = await oWeth.exercise(amountToExercise, [firstOwner], {
from: tokenHolder
});
const usdcBefore = (await usdc.balanceOf(oWeth.address)).toString();
const wethBefore = (await weth.balanceOf(oWeth.address)).toString();

const exerciseTx = await exerciser.exercise(
oWeth.address,
amountToExercise,
[firstOwner],
{
value: underlyingRequired,
from: tokenHolder
}
);

expectEvent(exerciseTx, 'Exercise', {
amtUnderlyingToPay: underlyingRequired,
amtCollateralToPay: '1000000000'
expectEvent(exerciseTx, 'WrapperExercise', {
otoken: oWeth.address,
otokenAmount: amountToExercise,
collateralExercised: '1000000000',
user: tokenHolder
});

// test that the vault's balances have been updated.
const vault = await oWeth.getVault(firstOwner);
assert.equal(vault[0].toString(), '0');
assert.equal(vault[1].toString(), '0');
assert.equal(vault[2].toString(), underlyingRequired);

const usdcAfter = (await usdc.balanceOf(oWeth.address)).toString();
const wethAfter = (await weth.balanceOf(oWeth.address)).toString();

assert.equal(
new BigNumber(usdcBefore).minus(new BigNumber('1000000000')).toString(),
new BigNumber(usdcAfter).toString()
);

assert.equal(
new BigNumber(wethBefore)
.plus(new BigNumber(underlyingRequired))
.toString(),
new BigNumber(wethAfter).toString()
);
});
});
});

0 comments on commit da70aa1

Please sign in to comment.