Skip to content

Commit

Permalink
add Pyth network
Browse files Browse the repository at this point in the history
  • Loading branch information
JoscelynFarr committed Mar 29, 2024
1 parent ce27ef0 commit 202df4d
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 36 deletions.
43 changes: 43 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "smart-contract-evm",
"version": "1.0.0",
"description": "JOJO is a decentralized perpetual contract exchange based on an off-chain matching system that can be divided into three key components: trading, collateral lending, and funding rate arbitrage.",
"main": "index.js",
"directories": {
"lib": "lib",
"test": "test"
},
"dependencies": {
"@pythnetwork/pyth-sdk-solidity": "^2.4.1",
"dotenv": "^16.4.1",
"minimist": "^1.2.8"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
1 change: 1 addition & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
@openzeppelin/=lib/openzeppelin-contracts/
@pythnetwork/pyth-sdk-solidity/=node_modules/@pythnetwork/pyth-sdk-solidity
forge-std/=lib/forge-std/src/
52 changes: 30 additions & 22 deletions src/oracle/OracleAdaptor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ pragma solidity ^0.8.19;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/math/SafeCast.sol";
import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
import "../interfaces/internal/IChainlink.sol";

contract OracleAdaptor is Ownable {
Expand All @@ -17,31 +15,44 @@ contract OracleAdaptor is Ownable {
uint256 public immutable usdcHeartbeat;
address public immutable usdcSource;
address public immutable chainlink;
bytes32 public immutable priceId;
uint256 public roundId;
uint256 public price;
uint256 public priceThreshold;
IPyth public pyth;
bool public isSelfOracle;

// Align with chainlink
event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt);

event UpdateThreshold(uint256 oldThreshold, uint256 newThreshold);

constructor(
address _chainlink,
address _pythContract,
uint256 _decimalsCorrection,
uint256 _heartbeatInterval,
uint256 _usdcHeartbeat,
address _usdcSource,
uint256 _priceThreshold,
bytes32 _priceId
uint256 _priceThreshold
) {
chainlink = _chainlink;
pyth = IPyth(_pythContract);
decimalsCorrection = 10 ** _decimalsCorrection;
heartbeatInterval = _heartbeatInterval;
usdcHeartbeat = _usdcHeartbeat;
usdcSource = _usdcSource;
priceThreshold = _priceThreshold;
priceId = _priceId;
}

function setMarkPrice(uint256 newPrice) external onlyOwner {
price = newPrice;
emit AnswerUpdated(SafeCast.toInt256(price), roundId, block.timestamp);
roundId += 1;
}

function turnOnJOJOOracle() external onlyOwner {
isSelfOracle = true;
}

function turnOffJOJOOracle() external onlyOwner {
isSelfOracle = false;
}

function updateThreshold(uint256 newPriceThreshold) external onlyOwner {
Expand All @@ -57,29 +68,26 @@ contract OracleAdaptor is Ownable {
require(block.timestamp - updatedAt <= heartbeatInterval, "ORACLE_HEARTBEAT_FAILED");
require(block.timestamp - usdcUpdatedAt <= usdcHeartbeat, "USDC_ORACLE_HEARTBEAT_FAILED");
uint256 tokenPrice = (SafeCast.toUint256(rawPrice) * 1e8) / SafeCast.toUint256(usdcPrice);
return tokenPrice;
return (tokenPrice * 1e18) / decimalsCorrection;
}

function getPrice() internal view returns (uint256) {
uint256 chainLinkPrice = getChainLinkPrice();
try pyth.getPrice(priceId) returns (PythStructs.Price memory pythPriceStruct) {
uint256 pythPrice = SafeCast.toUint256(pythPriceStruct.price);
uint256 diff = pythPrice >= chainLinkPrice ? pythPrice - chainLinkPrice : chainLinkPrice - pythPrice;
if ((diff * 1e18) / chainLinkPrice <= priceThreshold) {
return chainLinkPrice;
} else {
return pythPrice;
}
} catch {
if (isSelfOracle) {
uint256 JOJOPrice = price;
uint256 diff = JOJOPrice >= chainLinkPrice ? JOJOPrice - chainLinkPrice : chainLinkPrice - JOJOPrice;
require((diff * 1e18) / chainLinkPrice <= priceThreshold, "deviation is too big");
return price;
} else {
return chainLinkPrice;
}
}

function getMarkPrice() external view returns (uint256) {
return (getPrice() * 1e18) / decimalsCorrection;
return getPrice();
}

function getAssetPrice() external view returns (uint256) {
return (getPrice() * 1e18) / decimalsCorrection;
return getPrice();
}
}
}
81 changes: 67 additions & 14 deletions src/oracle/PythOracleAdaptor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,80 @@
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/math/SafeCast.sol";
import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
import "../interfaces/internal/IChainlink.sol";

contract PythOracleAdaptor is Ownable {
contract OracleAdaptor is Ownable {
uint256 public immutable decimalsCorrection;
uint256 public immutable heartbeatInterval;
uint256 public immutable usdcHeartbeat;
address public immutable usdcSource;
address public immutable chainlink;
bytes32 public immutable priceId;
uint256 public price;
uint256 public priceThreshold;
IPyth public pyth;

constructor(address _pythContract) {
event UpdateThreshold(uint256 oldThreshold, uint256 newThreshold);

constructor(
address _chainlink,
address _pythContract,
uint256 _decimalsCorrection,
uint256 _heartbeatInterval,
uint256 _usdcHeartbeat,
address _usdcSource,
uint256 _priceThreshold,
bytes32 _priceId
) {
chainlink = _chainlink;
pyth = IPyth(_pythContract);
decimalsCorrection = 10 ** _decimalsCorrection;
heartbeatInterval = _heartbeatInterval;
usdcHeartbeat = _usdcHeartbeat;
usdcSource = _usdcSource;
priceThreshold = _priceThreshold;
priceId = _priceId;
}

function updateThreshold(uint256 newPriceThreshold) external onlyOwner {
priceThreshold = newPriceThreshold;
emit UpdateThreshold(priceThreshold, newPriceThreshold);
}

function getChainLinkPrice() public view returns (uint256) {
int256 rawPrice;
uint256 updatedAt;
(, rawPrice,, updatedAt,) = IChainlink(chainlink).latestRoundData();
(, int256 usdcPrice,, uint256 usdcUpdatedAt,) = IChainlink(usdcSource).latestRoundData();
require(block.timestamp - updatedAt <= heartbeatInterval, "ORACLE_HEARTBEAT_FAILED");
require(block.timestamp - usdcUpdatedAt <= usdcHeartbeat, "USDC_ORACLE_HEARTBEAT_FAILED");
uint256 tokenPrice = (SafeCast.toUint256(rawPrice) * 1e8) / SafeCast.toUint256(usdcPrice);
return tokenPrice;
}

function getPrice() internal view returns (uint256) {
uint256 chainLinkPrice = getChainLinkPrice();
try pyth.getPrice(priceId) returns (PythStructs.Price memory pythPriceStruct) {
uint256 pythPrice = SafeCast.toUint256(pythPriceStruct.price);
uint256 diff = pythPrice >= chainLinkPrice ? pythPrice - chainLinkPrice : chainLinkPrice - pythPrice;
if ((diff * 1e18) / chainLinkPrice <= priceThreshold) {
return chainLinkPrice;
} else {
return pythPrice;
}
} catch {
return chainLinkPrice;
}
}

function getMarkPrice() external view returns (uint256) {
return (getPrice() * 1e18) / decimalsCorrection;
}

function setMarkPrice(
bytes[] calldata updateData,
bytes32[] calldata priceIds,
uint64[] calldata publishTimes
)
external
payable
onlyOwner
{
// Update the on-chain Pyth price(s)
uint256 fee = pyth.getUpdateFee(updateData);
pyth.updatePriceFeedsIfNecessary{ value: fee }(updateData, priceIds, publishTimes);
function getAssetPrice() external view returns (uint256) {
return (getPrice() * 1e18) / decimalsCorrection;
}
}
32 changes: 32 additions & 0 deletions src/oracle/PythOracleUpdate.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
Copyright 2022 JOJO Exchange
SPDX-License-Identifier: BUSL-1.1
*/

pragma solidity ^0.8.19;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";

contract PythOracleAdaptor is Ownable {
IPyth public pyth;

constructor(address _pythContract) {
pyth = IPyth(_pythContract);
}

function setMarkPrice(
bytes[] calldata updateData,
bytes32[] calldata priceIds,
uint64[] calldata publishTimes
)
external
payable
onlyOwner
{
// Update the on-chain Pyth price(s)
uint256 fee = pyth.getUpdateFee(updateData);
pyth.updatePriceFeedsIfNecessary{ value: fee }(updateData, priceIds, publishTimes);
}
}

0 comments on commit 202df4d

Please sign in to comment.