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

Factor trade volume into hourly price instants #182

Merged
merged 4 commits into from
Jul 16, 2021
Merged
Show file tree
Hide file tree
Changes from 3 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/IPricing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ interface IPricing {

function getHourlyAvgOraclePrice(uint256 hour) external view returns (uint256);

function recordTrade(uint256 tradePrice) external;
function recordTrade(uint256 tradePrice, uint256 fillAmount) external;
}
33 changes: 22 additions & 11 deletions contracts/Pricing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import "./Interfaces/ITracerPerpetualSwaps.sol";
import "./Interfaces/IInsurance.sol";
import "./Interfaces/IOracle.sol";
import "prb-math/contracts/PRBMathSD59x18.sol";
import "prb-math/contracts/PRBMathUD60x18.sol";

contract Pricing is IPricing {
using LibMath for uint256;
Expand Down Expand Up @@ -68,8 +69,9 @@ contract Pricing is IPricing {
* @notice Updates pricing information given a trade of a certain volume at
* a set price
* @param tradePrice the price the trade executed at
* @param fillAmount the amount the trade was filled for
*/
function recordTrade(uint256 tradePrice) external override onlyTracer {
function recordTrade(uint256 tradePrice, uint256 fillAmount) external override onlyTracer {
uint256 currentOraclePrice = oracle.latestAnswer();
if (startLastHour <= block.timestamp - 1 hours) {
// emit the old hourly average
Expand Down Expand Up @@ -97,10 +99,10 @@ contract Pricing is IPricing {
}

// add new pricing entry for new hour
updatePrice(tradePrice, currentOraclePrice, true);
updatePrice(tradePrice, currentOraclePrice, fillAmount, true);
} else {
// Update old pricing entry
updatePrice(tradePrice, currentOraclePrice, false);
updatePrice(tradePrice, currentOraclePrice, fillAmount, false);
}
}

Expand All @@ -109,32 +111,41 @@ contract Pricing is IPricing {
* and an oracle price.
* @param marketPrice The price that a tracer was bought at, returned by the TracerPerpetualSwaps.sol contract when an order is filled
* @param oraclePrice The price of the underlying asset that the Tracer is based upon as returned by a Chainlink Oracle
* @param fillAmount The amount of the order that was filled at some price
* @param newRecord Bool that decides if a new hourly record should be started (true) or if a current hour should be updated (false)
*/
function updatePrice(
uint256 marketPrice,
uint256 oraclePrice,
uint256 fillAmount,
bool newRecord
) internal {
// Price records entries updated every hour
if (newRecord) {
// Make new hourly record, total = marketprice, numtrades set to 1;
Prices.PriceInstant memory newHourly = Prices.PriceInstant(marketPrice, 1);
// Make new hourly record, total = marketprice, numTrades set to the amount filled;
Prices.PriceInstant memory newHourly = Prices.PriceInstant(
PRBMathUD60x18.mul(marketPrice, fillAmount),
fillAmount
);
hourlyTracerPrices[currentHour] = newHourly;
// As above but with Oracle price
Prices.PriceInstant memory oracleHour = Prices.PriceInstant(oraclePrice, 1);
Prices.PriceInstant memory oracleHour = Prices.PriceInstant(
PRBMathUD60x18.mul(oraclePrice, fillAmount),
fillAmount
);
hourlyOraclePrices[currentHour] = oracleHour;
} else {
// If an update is needed, add the market price to a running total and increment number of trades
// If an update is needed, add the total market price of the trade to a running total
// and increment number of fill amounts
hourlyTracerPrices[currentHour].cumulativePrice =
hourlyTracerPrices[currentHour].cumulativePrice +
marketPrice;
hourlyTracerPrices[currentHour].trades = hourlyTracerPrices[currentHour].trades + 1;
PRBMathUD60x18.mul(marketPrice, fillAmount);
hourlyTracerPrices[currentHour].trades = hourlyTracerPrices[currentHour].trades + fillAmount;
// As above but with oracle price
hourlyOraclePrices[currentHour].cumulativePrice =
hourlyOraclePrices[currentHour].cumulativePrice +
oraclePrice;
hourlyOraclePrices[currentHour].trades = hourlyOraclePrices[currentHour].trades + 1;
PRBMathUD60x18.mul(oraclePrice, fillAmount);
hourlyOraclePrices[currentHour].trades = hourlyOraclePrices[currentHour].trades + fillAmount;
}
}

Expand Down
21 changes: 13 additions & 8 deletions contracts/TracerPerpetualSwaps.sol
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,6 @@ contract TracerPerpetualSwaps is ITracerPerpetualSwaps, Ownable, SafetyWithdraw
) external override onlyWhitelisted returns (bool) {
bytes32 order1Id = Perpetuals.orderId(order1);
bytes32 order2Id = Perpetuals.orderId(order2);
uint256 filled1 = ITrader(msg.sender).filled(order1Id);
uint256 filled2 = ITrader(msg.sender).filled(order2Id);

uint256 executionPrice = Perpetuals.getExecutionPrice(order1, order2);

Expand All @@ -245,20 +243,27 @@ contract TracerPerpetualSwaps is ITracerPerpetualSwaps, Ownable, SafetyWithdraw
fillAmount,
executionPrice
);
uint256 _fairPrice = pricingContract.fairPrice();
uint256 _trueMaxLeverage = trueMaxLeverage();
// validate orders can match, and outcome state is valid
if (
!Perpetuals.canMatch(order1, filled1, order2, filled2) ||
!Perpetuals.canMatch(
order1,
ITrader(msg.sender).filled(order1Id),
order2,
ITrader(msg.sender).filled(order2Id)
) ||
!Balances.marginIsValid(
newPos1,
balances[order1.maker].lastUpdatedGasPrice * LIQUIDATION_GAS_COST,
pricingContract.fairPrice(),
trueMaxLeverage()
_fairPrice,
_trueMaxLeverage
) ||
!Balances.marginIsValid(
newPos2,
balances[order2.maker].lastUpdatedGasPrice * LIQUIDATION_GAS_COST,
pricingContract.fairPrice(),
trueMaxLeverage()
_fairPrice,
_trueMaxLeverage
)
) {
// long order must have a price >= short order
Expand Down Expand Up @@ -287,7 +292,7 @@ contract TracerPerpetualSwaps is ITracerPerpetualSwaps, Ownable, SafetyWithdraw
_updateAccountLeverage(order2.maker);

// Update internal trade state
pricingContract.recordTrade(executionPrice);
pricingContract.recordTrade(executionPrice, fillAmount);

if (order1.side == Perpetuals.Side.Long) {
emit MatchedOrders(order1.maker, order2.maker, fillAmount, executionPrice, order1Id, order2Id);
Expand Down
3 changes: 2 additions & 1 deletion contracts/lib/LibPrices.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ library Prices {
if (price.trades == 0) {
return 0;
}
return price.cumulativePrice / price.trades;

return PRBMathUD60x18.div(price.cumulativePrice, price.trades);
}

/**
Expand Down
7 changes: 3 additions & 4 deletions scripts/DepositQuoteTokensKovan.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,9 @@ async function main() {
tracerInstance = await tracerInstance.connect(wallet)
if (
(
await tokenInstance.connect(wallet).allowance(
wallet.address,
tracerInstance.address
)
await tokenInstance
.connect(wallet)
.allowance(wallet.address, tracerInstance.address)
).lt(DEPOSIT_AMOUNT)
) {
const tx = await tokenInstance
Expand Down
29 changes: 19 additions & 10 deletions test/unit/LibPrices.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const calcExpectedTwaps = (oraclePrices, tracerPrices, hour) => {
let cumulativeDerivative = ethers.BigNumber.from("0")
let cumulativeUnderlying = ethers.BigNumber.from("0")
let totalTimeWeight = ethers.BigNumber.from("0")

for (i = 0; i < 8; i++) {
let currTimeWeight = 8 - i
let j = hour < i ? 24 - i + hour : hour - i
Expand All @@ -15,12 +16,19 @@ const calcExpectedTwaps = (oraclePrices, tracerPrices, hour) => {
ethers.BigNumber.from(currTimeWeight)
)
cumulativeDerivative = cumulativeDerivative.add(
tracerPrices[j][0].div(tracerPrices[j][1]).mul(currTimeWeight)
tracerPrices[j][0]
.mul(ethers.utils.parseEther("1"))
.div(tracerPrices[j][1])
.mul(currTimeWeight)
)
cumulativeUnderlying = cumulativeUnderlying.add(
oraclePrices[j][0].div(oraclePrices[j][1]).mul(currTimeWeight)
oraclePrices[j][0]
.mul(ethers.utils.parseEther("1"))
.div(oraclePrices[j][1])
.mul(currTimeWeight)
)
}

return [
cumulativeUnderlying.div(totalTimeWeight),
cumulativeDerivative.div(totalTimeWeight),
Expand Down Expand Up @@ -196,7 +204,7 @@ describe("Unit tests: LibPrices.sol", function () {
let result = await libPrices.averagePrice(price)

expect(result.toString()).to.equal(
ethers.BigNumber.from("10").toString()
ethers.utils.parseEther("10").toString()
)
})
})
Expand Down Expand Up @@ -229,16 +237,17 @@ describe("Unit tests: LibPrices.sol", function () {
for (i = 0; i < n; i++) {
prices.push([
ethers.utils.parseEther((i + 1).toString()),
ethers.BigNumber.from(i + 1),
ethers.utils.parseEther((i + 1).toString()),
])
let dayAverage = ethers.utils
.parseEther((i + 1).toString())
.div(ethers.BigNumber.from(i + 1))
.mul(ethers.utils.parseEther("1"))
.div(ethers.utils.parseEther((i + 1).toString()))
priceAverages = priceAverages.add(dayAverage.toString())
}

let averagePriceForPeriod = priceAverages.div(
ethers.BigNumber.from(n)
ethers.BigNumber.from(n.toString())
)
let result = await libPrices.averagePriceForPeriod(prices)

Expand Down Expand Up @@ -317,12 +326,12 @@ describe("Unit tests: LibPrices.sol", function () {
// generate 24 hour oracle and tracer prices
for (i = 0; i < 24; i++) {
oraclePrices.push([
ethers.utils.parseUnits((1 + 1 * i).toString(), 18),
ethers.BigNumber.from("1"),
ethers.utils.parseEther((1 + 1 * i).toString()),
ethers.utils.parseEther("1"),
])
tracerPrices.push([
ethers.utils.parseUnits((1 + 0.5 * i).toString(), 18),
ethers.BigNumber.from("1"),
ethers.utils.parseEther((1 + 0.5 * i).toString()),
ethers.utils.parseEther("1"),
])
}

Expand Down