Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion contracts/interfaces/OpynPricerInterface.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity 0.6.10;

interface OpynPricerInterface {
function getPrice() external view returns (uint256);
function getPrice(address) external view returns (uint256);

function getHistoricalPrice(uint80 _roundId) external view returns (uint256, uint256);
}
83 changes: 83 additions & 0 deletions contracts/pricers/ManualPricer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.6.10;

import {OracleInterface} from "../interfaces/OracleInterface.sol";
import {OpynPricerInterface} from "../interfaces/OpynPricerInterface.sol";
import {SafeMath} from "../packages/oz/SafeMath.sol";

/**
* @notice A Pricer contract for one asset as reported by Manual entity
*/
contract ManualPricer is OpynPricerInterface {
using SafeMath for uint256;

/// @notice the opyn oracle address
OracleInterface public oracle;

/// @notice asset that this pricer will a get price for
address public asset;
/// @notice bot address that is allowed to call setExpiryPriceInOracle
address public bot;

/// @notice lastExpiryTimestamp last timestamp that asset price was set
uint256 public lastExpiryTimestamp;

/// @notice historicalPrice mapping of timestamp to price
mapping(uint256 => uint256) public historicalPrice;

/**
* @param _bot priveleged address that can call setExpiryPriceInOracle
* @param _asset asset that this pricer will get a price for
* @param _oracle Opyn Oracle address
*/
constructor(
address _bot,
address _asset,
address _oracle
) public {
require(_bot != address(0), "ManualPricer: Cannot set 0 address as bot");
require(_oracle != address(0), "ManualPricer: Cannot set 0 address as oracle");

bot = _bot;
oracle = OracleInterface(_oracle);
asset = _asset;
}

/**
* @notice modifier to check if sender address is equal to bot address
*/
modifier onlyBot() {
require(msg.sender == bot, "ManualPricer: unauthorized sender");

_;
}

/**
* @notice set the expiry price in the oracle, can only be called by Bot address
* @param _expiryTimestamp expiry to set a price for
* @param _price the price of the asset
*/
function setExpiryPriceInOracle(uint256 _expiryTimestamp, uint256 _price) external onlyBot {
lastExpiryTimestamp = _expiryTimestamp;
historicalPrice[_expiryTimestamp] = _price;
oracle.setExpiryPrice(asset, _expiryTimestamp, _price);
}

/**
* @notice get the live price for the asset
* @dev overides the getPrice function in OpynPricerInterface
* @return price of the asset in USD, scaled by 1e8
*/
function getPrice(address) external view override returns (uint256) {
return historicalPrice[lastExpiryTimestamp];
}

/**
* @notice get historical chainlink price
* @param _roundId chainlink round id
* @return round price and timestamp
*/
function getHistoricalPrice(uint80 _roundId) external view override returns (uint256, uint256) {
revert("ManualPricer: Deprecated");
}
}
33 changes: 33 additions & 0 deletions scripts/deployManualPricer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const yargs = require('yargs')

const ManualPricer = artifacts.require('ManualPricer.sol')

module.exports = async function(callback) {
try {
const options = yargs
.usage(
'Usage: --network <network> --bot <bot> --asset <asset> --oracle <oracle> --gasPrice <gasPrice> --gasLimit <gasLimit>',
)
.option('network', {describe: 'Network name', type: 'string', demandOption: true})
.option('bot', {describe: 'Bot address', type: 'string', demandOption: true})
.option('asset', {describe: 'Asset address', type: 'string', demandOption: true})
.option('oracle', {describe: 'Oracle module address', type: 'string', demandOption: true})
.option('gasPrice', {describe: 'Gas price in WEI', type: 'string', demandOption: false})
.option('gasLimit', {describe: 'Gas Limit in WEI', type: 'string', demandOption: false}).argv

console.log(`Deploying manual pricer contract on ${options.network} 🍕`)

const tx = await ManualPricer.new(options.bot, options.asset, options.oracle, {
gasPrice: options.gasPrice,
gas: options.gasLimit,
})

console.log('Manual pricer deployed! 🎉')
console.log(`Transaction hash: ${tx.transactionHash}`)
console.log(`Deployed contract address: ${tx.address}`)

callback()
} catch (err) {
callback(err)
}
}
97 changes: 97 additions & 0 deletions test/unit-tests/manualPricer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { ManualPricerInstance, MockOracleInstance, MockERC20Instance } from '../../build/types/truffle-types'

import { createTokenAmount } from '../utils'
const { expectRevert, time } = require('@openzeppelin/test-helpers')

const ManualPricer = artifacts.require('ManualPricer.sol')
const MockOracle = artifacts.require('MockOracle.sol')
const MockERC20 = artifacts.require('MockERC20.sol')

// address(0)
const ZERO_ADDR = '0x0000000000000000000000000000000000000000'

contract('ManualPricer', ([owner, bot, random]) => {
let oracle: MockOracleInstance
let weth: MockERC20Instance
// otoken
let pricer: ManualPricerInstance

before('Deployment', async () => {
// deploy mock contracts
oracle = await MockOracle.new({ from: owner })
weth = await MockERC20.new('WETH', 'WETH', 18)
// deploy pricer
pricer = await ManualPricer.new(bot, weth.address, oracle.address)
})

describe('constructor', () => {
it('should set the config correctly', async () => {
const asset = await pricer.asset()
assert.equal(asset, weth.address)
const bot = await pricer.bot()
assert.equal(bot, bot)
const oracleModule = await pricer.oracle()
assert.equal(oracleModule, oracle.address)
})
it('should revert if initializing oracle with 0 address', async () => {
await expectRevert(ManualPricer.new(bot, weth.address, ZERO_ADDR), 'ManualPricer: Cannot set 0 address as oracle')
})
it('should revert if initializing bot with 0 address', async () => {
await expectRevert(
ManualPricer.new(ZERO_ADDR, weth.address, oracle.address),
'ManualPricer: Cannot set 0 address as bot',
)
})
})

describe('getPrice', () => {
it('should return the new price after resetting answer', async () => {
const newPrice = createTokenAmount(400, 8)
await pricer.setExpiryPriceInOracle(1, newPrice, { from: bot })
const price = await pricer.getPrice(oracle.address)
const expectedResult = createTokenAmount(400, 8)
assert.equal(price.toString(), expectedResult.toString())
})
})

describe('setExpiryPrice', () => {
const p1 = createTokenAmount(150.333, 8)

it('should set the correct price to the oracle', async () => {
const expiryTimestamp = 5

await pricer.setExpiryPriceInOracle(expiryTimestamp, p1, { from: bot })
const priceFromOracle = await oracle.getExpiryPrice(weth.address, expiryTimestamp)
const lastExpiryTimestamp = await pricer.lastExpiryTimestamp()
assert.equal(p1.toString(), priceFromOracle[0].toString())
assert.equal(lastExpiryTimestamp.toString(), expiryTimestamp.toString())
assert.equal((await pricer.historicalPrice(lastExpiryTimestamp)).toString(), p1.toString())
})

it('should revert if sender is not bot address', async () => {
const expiryTimestamp = 5
await expectRevert(
pricer.setExpiryPriceInOracle(expiryTimestamp, p1, { from: random }),
'ManualPricer: unauthorized sender',
)
})
})

describe('get historical price', async () => {
let t0: number
// p0 = price at t0 ... etc
const p0 = createTokenAmount(100, 8)

it('should return historical price with timestamp', async () => {
await pricer.setExpiryPriceInOracle(0, p0, { from: bot })
const roundData = await pricer.historicalPrice(0)

assert.equal(roundData.toString(), p0, 'Historical round price mismatch')
})

it('should revert when no data round available', async () => {
const invalidRoundId = 2
assert.equal((await pricer.historicalPrice(2)).toString(), '0', 'Historical round timestamp mismatch')
})
})
})
13 changes: 1 addition & 12 deletions truffle-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,8 @@ module.exports = {
confirmations: 2,
timeoutBlocks: 50,
skipDryRun: false,
gasPrice: 25000000000,
},
avax: {
provider: () => new HDWalletProvider(mnemonic, 'https://api.avax.network/ext/bc/C/rpc'),
network_id: 1,
gasPrice: 100000000000,
},
fuji: {
provider: () => new HDWalletProvider(mnemonic, 'https://api.avax-test.network/ext/bc/C/rpc'),
network_id: 1,
gas: 8000000,
gasPrice: 250000000000,
skipDryRun: true,
}
},

mocha: {
Expand Down