编译器
0.8.19+commit.7dd6d404
文件 1 的 6:Address.sol
pragma solidity ^0.8.1;
library Address {
function isContract(address account) internal view returns (bool) {
return account.code.length > 0;
}
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
if (returndata.length > 0) {
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
文件 2 的 6:IAggregatorV3Interface.sol
pragma solidity 0.8.19;
interface IAggregatorV3Interface {
function decimals() external view returns (uint8);
function description() external view returns (string memory);
function version() external view returns (uint256);
function getRoundData(
uint80 _roundId
)
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
function latestRoundData()
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
}
文件 3 的 6:IPrismaCore.sol
pragma solidity ^0.8.0;
interface IPrismaCore {
event FeeReceiverSet(address feeReceiver);
event GuardianSet(address guardian);
event NewOwnerAccepted(address oldOwner, address owner);
event NewOwnerCommitted(address owner, address pendingOwner, uint256 deadline);
event NewOwnerRevoked(address owner, address revokedOwner);
event Paused();
event PriceFeedSet(address priceFeed);
event Unpaused();
function acceptTransferOwnership() external;
function commitTransferOwnership(address newOwner) external;
function revokeTransferOwnership() external;
function setFeeReceiver(address _feeReceiver) external;
function setGuardian(address _guardian) external;
function setPaused(bool _paused) external;
function setPriceFeed(address _priceFeed) external;
function OWNERSHIP_TRANSFER_DELAY() external view returns (uint256);
function feeReceiver() external view returns (address);
function guardian() external view returns (address);
function owner() external view returns (address);
function ownershipTransferDeadline() external view returns (uint256);
function paused() external view returns (bool);
function pendingOwner() external view returns (address);
function priceFeed() external view returns (address);
function startTime() external view returns (uint256);
}
文件 4 的 6:PriceFeed.sol
pragma solidity 0.8.19;
import "IAggregatorV3Interface.sol";
import "Address.sol";
import "PrismaMath.sol";
import "PrismaOwnable.sol";
contract PriceFeed is PrismaOwnable {
struct OracleRecord {
IAggregatorV3Interface chainLinkOracle;
uint8 decimals;
uint32 heartbeat;
bytes4 sharePriceSignature;
uint8 sharePriceDecimals;
bool isFeedWorking;
bool isEthIndexed;
}
struct PriceRecord {
uint96 scaledPrice;
uint32 timestamp;
uint32 lastUpdated;
uint80 roundId;
}
struct FeedResponse {
uint80 roundId;
int256 answer;
uint256 timestamp;
bool success;
}
error PriceFeed__InvalidFeedResponseError(address token);
error PriceFeed__FeedFrozenError(address token);
error PriceFeed__UnknownFeedError(address token);
error PriceFeed__HeartbeatOutOfBoundsError();
event NewOracleRegistered(address token, address chainlinkAggregator, bool isEthIndexed);
event PriceFeedStatusUpdated(address token, address oracle, bool isWorking);
event PriceRecordUpdated(address indexed token, uint256 _price);
uint256 public constant TARGET_DIGITS = 18;
uint256 public constant RESPONSE_TIMEOUT_BUFFER = 1 hours;
uint256 public constant MAX_PRICE_DEVIATION_FROM_PREVIOUS_ROUND = 5e17;
mapping(address => OracleRecord) public oracleRecords;
mapping(address => PriceRecord) public priceRecords;
struct OracleSetup {
address token;
address chainlink;
uint32 heartbeat;
bytes4 sharePriceSignature;
uint8 sharePriceDecimals;
bool isEthIndexed;
}
constructor(address _prismaCore, address ethFeed, OracleSetup[] memory oracles) PrismaOwnable(_prismaCore) {
_setOracle(address(0), ethFeed, 3600, 0, 0, false);
for (uint i = 0; i < oracles.length; i++) {
OracleSetup memory o = oracles[i];
_setOracle(o.token, o.chainlink, o.heartbeat, o.sharePriceSignature, o.sharePriceDecimals, o.isEthIndexed);
}
}
function setOracle(
address _token,
address _chainlinkOracle,
uint32 _heartbeat,
bytes4 sharePriceSignature,
uint8 sharePriceDecimals,
bool _isEthIndexed
) external onlyOwner {
_setOracle(_token, _chainlinkOracle, _heartbeat, sharePriceSignature, sharePriceDecimals, _isEthIndexed);
}
function _setOracle(
address _token,
address _chainlinkOracle,
uint32 _heartbeat,
bytes4 sharePriceSignature,
uint8 sharePriceDecimals,
bool _isEthIndexed
) internal {
if (_heartbeat > 86400) revert PriceFeed__HeartbeatOutOfBoundsError();
IAggregatorV3Interface newFeed = IAggregatorV3Interface(_chainlinkOracle);
(FeedResponse memory currResponse, FeedResponse memory prevResponse, ) = _fetchFeedResponses(newFeed, 0);
if (!_isFeedWorking(currResponse, prevResponse)) {
revert PriceFeed__InvalidFeedResponseError(_token);
}
if (_isPriceStale(currResponse.timestamp, _heartbeat)) {
revert PriceFeed__FeedFrozenError(_token);
}
OracleRecord memory record = OracleRecord({
chainLinkOracle: newFeed,
decimals: newFeed.decimals(),
heartbeat: _heartbeat,
sharePriceSignature: sharePriceSignature,
sharePriceDecimals: sharePriceDecimals,
isFeedWorking: true,
isEthIndexed: _isEthIndexed
});
oracleRecords[_token] = record;
PriceRecord memory _priceRecord = priceRecords[_token];
_processFeedResponses(_token, record, currResponse, prevResponse, _priceRecord);
emit NewOracleRegistered(_token, _chainlinkOracle, _isEthIndexed);
}
function fetchPrice(address _token) public returns (uint256) {
PriceRecord memory priceRecord = priceRecords[_token];
OracleRecord memory oracle = oracleRecords[_token];
uint256 scaledPrice = priceRecord.scaledPrice;
if (priceRecord.lastUpdated != block.timestamp) {
if (priceRecord.lastUpdated == 0) {
revert PriceFeed__UnknownFeedError(_token);
}
(FeedResponse memory currResponse, FeedResponse memory prevResponse, bool updated) = _fetchFeedResponses(
oracle.chainLinkOracle,
priceRecord.roundId
);
if (updated) {
scaledPrice = _processFeedResponses(_token, oracle, currResponse, prevResponse, priceRecord);
} else {
if (_isPriceStale(priceRecord.timestamp, oracle.heartbeat)) {
revert PriceFeed__FeedFrozenError(_token);
}
priceRecord.lastUpdated = uint32(block.timestamp);
priceRecords[_token] = priceRecord;
}
}
if (oracle.isEthIndexed) {
uint256 ethPrice = fetchPrice(address(0));
return (ethPrice * scaledPrice) / 1 ether;
}
return scaledPrice;
}
function _processFeedResponses(
address _token,
OracleRecord memory oracle,
FeedResponse memory _currResponse,
FeedResponse memory _prevResponse,
PriceRecord memory priceRecord
) internal returns (uint256) {
uint8 decimals = oracle.decimals;
bool isValidResponse = _isFeedWorking(_currResponse, _prevResponse) &&
!_isPriceStale(_currResponse.timestamp, oracle.heartbeat) &&
!_isPriceChangeAboveMaxDeviation(_currResponse, _prevResponse, decimals);
if (isValidResponse) {
uint256 scaledPrice = _scalePriceByDigits(uint256(_currResponse.answer), decimals);
if (oracle.sharePriceSignature != 0) {
(bool success, bytes memory returnData) = _token.staticcall(abi.encode(oracle.sharePriceSignature));
require(success, "Share price not available");
scaledPrice = (scaledPrice * abi.decode(returnData, (uint256))) / (10 ** oracle.sharePriceDecimals);
}
if (!oracle.isFeedWorking) {
_updateFeedStatus(_token, oracle, true);
}
_storePrice(_token, scaledPrice, _currResponse.timestamp, _currResponse.roundId);
return scaledPrice;
} else {
if (oracle.isFeedWorking) {
_updateFeedStatus(_token, oracle, false);
}
if (_isPriceStale(priceRecord.timestamp, oracle.heartbeat)) {
revert PriceFeed__FeedFrozenError(_token);
}
return priceRecord.scaledPrice;
}
}
function _fetchFeedResponses(
IAggregatorV3Interface oracle,
uint80 lastRoundId
) internal view returns (FeedResponse memory currResponse, FeedResponse memory prevResponse, bool updated) {
currResponse = _fetchCurrentFeedResponse(oracle);
if (lastRoundId == 0 || currResponse.roundId > lastRoundId) {
prevResponse = _fetchPrevFeedResponse(oracle, currResponse.roundId);
updated = true;
}
}
function _isPriceStale(uint256 _priceTimestamp, uint256 _heartbeat) internal view returns (bool) {
return block.timestamp - _priceTimestamp > _heartbeat + RESPONSE_TIMEOUT_BUFFER;
}
function _isFeedWorking(
FeedResponse memory _currentResponse,
FeedResponse memory _prevResponse
) internal view returns (bool) {
return _isValidResponse(_currentResponse) && _isValidResponse(_prevResponse);
}
function _isValidResponse(FeedResponse memory _response) internal view returns (bool) {
return
(_response.success) &&
(_response.roundId != 0) &&
(_response.timestamp != 0) &&
(_response.timestamp <= block.timestamp) &&
(_response.answer != 0);
}
function _isPriceChangeAboveMaxDeviation(
FeedResponse memory _currResponse,
FeedResponse memory _prevResponse,
uint8 decimals
) internal pure returns (bool) {
uint256 currentScaledPrice = _scalePriceByDigits(uint256(_currResponse.answer), decimals);
uint256 prevScaledPrice = _scalePriceByDigits(uint256(_prevResponse.answer), decimals);
uint256 minPrice = PrismaMath._min(currentScaledPrice, prevScaledPrice);
uint256 maxPrice = PrismaMath._max(currentScaledPrice, prevScaledPrice);
uint256 percentDeviation = ((maxPrice - minPrice) * PrismaMath.DECIMAL_PRECISION) / maxPrice;
return percentDeviation > MAX_PRICE_DEVIATION_FROM_PREVIOUS_ROUND;
}
function _scalePriceByDigits(uint256 _price, uint256 _answerDigits) internal pure returns (uint256) {
if (_answerDigits == TARGET_DIGITS) {
return _price;
} else if (_answerDigits < TARGET_DIGITS) {
return _price * (10 ** (TARGET_DIGITS - _answerDigits));
} else {
return _price / (10 ** (_answerDigits - TARGET_DIGITS));
}
}
function _updateFeedStatus(address _token, OracleRecord memory _oracle, bool _isWorking) internal {
oracleRecords[_token].isFeedWorking = _isWorking;
emit PriceFeedStatusUpdated(_token, address(_oracle.chainLinkOracle), _isWorking);
}
function _storePrice(address _token, uint256 _price, uint256 _timestamp, uint80 roundId) internal {
priceRecords[_token] = PriceRecord({
scaledPrice: uint96(_price),
timestamp: uint32(_timestamp),
lastUpdated: uint32(block.timestamp),
roundId: roundId
});
emit PriceRecordUpdated(_token, _price);
}
function _fetchCurrentFeedResponse(
IAggregatorV3Interface _priceAggregator
) internal view returns (FeedResponse memory response) {
try _priceAggregator.latestRoundData() returns (
uint80 roundId,
int256 answer,
uint256 ,
uint256 timestamp,
uint80
) {
response.roundId = roundId;
response.answer = answer;
response.timestamp = timestamp;
response.success = true;
} catch {
return response;
}
}
function _fetchPrevFeedResponse(
IAggregatorV3Interface _priceAggregator,
uint80 _currentRoundId
) internal view returns (FeedResponse memory prevResponse) {
if (_currentRoundId == 0) {
return prevResponse;
}
unchecked {
try _priceAggregator.getRoundData(_currentRoundId - 1) returns (
uint80 roundId,
int256 answer,
uint256 ,
uint256 timestamp,
uint80
) {
prevResponse.roundId = roundId;
prevResponse.answer = answer;
prevResponse.timestamp = timestamp;
prevResponse.success = true;
} catch {}
}
}
}
文件 5 的 6:PrismaMath.sol
pragma solidity 0.8.19;
library PrismaMath {
uint256 internal constant DECIMAL_PRECISION = 1e18;
uint256 internal constant NICR_PRECISION = 1e20;
function _min(uint256 _a, uint256 _b) internal pure returns (uint256) {
return (_a < _b) ? _a : _b;
}
function _max(uint256 _a, uint256 _b) internal pure returns (uint256) {
return (_a >= _b) ? _a : _b;
}
function decMul(uint256 x, uint256 y) internal pure returns (uint256 decProd) {
uint256 prod_xy = x * y;
decProd = (prod_xy + (DECIMAL_PRECISION / 2)) / DECIMAL_PRECISION;
}
function _decPow(uint256 _base, uint256 _minutes) internal pure returns (uint256) {
if (_minutes > 525600000) {
_minutes = 525600000;
}
if (_minutes == 0) {
return DECIMAL_PRECISION;
}
uint256 y = DECIMAL_PRECISION;
uint256 x = _base;
uint256 n = _minutes;
while (n > 1) {
if (n % 2 == 0) {
x = decMul(x, x);
n = n / 2;
} else {
y = decMul(x, y);
x = decMul(x, x);
n = (n - 1) / 2;
}
}
return decMul(x, y);
}
function _getAbsoluteDifference(uint256 _a, uint256 _b) internal pure returns (uint256) {
return (_a >= _b) ? _a - _b : _b - _a;
}
function _computeNominalCR(uint256 _coll, uint256 _debt) internal pure returns (uint256) {
if (_debt > 0) {
return (_coll * NICR_PRECISION) / _debt;
}
else {
return 2 ** 256 - 1;
}
}
function _computeCR(uint256 _coll, uint256 _debt, uint256 _price) internal pure returns (uint256) {
if (_debt > 0) {
uint256 newCollRatio = (_coll * _price) / _debt;
return newCollRatio;
}
else {
return 2 ** 256 - 1;
}
}
function _computeCR(uint256 _coll, uint256 _debt) internal pure returns (uint256) {
if (_debt > 0) {
uint256 newCollRatio = (_coll) / _debt;
return newCollRatio;
}
else {
return 2 ** 256 - 1;
}
}
}
文件 6 的 6:PrismaOwnable.sol
pragma solidity 0.8.19;
import "IPrismaCore.sol";
contract PrismaOwnable {
IPrismaCore public immutable PRISMA_CORE;
constructor(address _prismaCore) {
PRISMA_CORE = IPrismaCore(_prismaCore);
}
modifier onlyOwner() {
require(msg.sender == PRISMA_CORE.owner(), "Only owner");
_;
}
function owner() public view returns (address) {
return PRISMA_CORE.owner();
}
function guardian() public view returns (address) {
return PRISMA_CORE.guardian();
}
}
{
"compilationTarget": {
"PriceFeed.sol": "PriceFeed"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_prismaCore","type":"address"},{"internalType":"address","name":"ethFeed","type":"address"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"chainlink","type":"address"},{"internalType":"uint32","name":"heartbeat","type":"uint32"},{"internalType":"bytes4","name":"sharePriceSignature","type":"bytes4"},{"internalType":"uint8","name":"sharePriceDecimals","type":"uint8"},{"internalType":"bool","name":"isEthIndexed","type":"bool"}],"internalType":"struct PriceFeed.OracleSetup[]","name":"oracles","type":"tuple[]"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"PriceFeed__FeedFrozenError","type":"error"},{"inputs":[],"name":"PriceFeed__HeartbeatOutOfBoundsError","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"PriceFeed__InvalidFeedResponseError","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"PriceFeed__UnknownFeedError","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"address","name":"chainlinkAggregator","type":"address"},{"indexed":false,"internalType":"bool","name":"isEthIndexed","type":"bool"}],"name":"NewOracleRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"address","name":"oracle","type":"address"},{"indexed":false,"internalType":"bool","name":"isWorking","type":"bool"}],"name":"PriceFeedStatusUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"_price","type":"uint256"}],"name":"PriceRecordUpdated","type":"event"},{"inputs":[],"name":"MAX_PRICE_DEVIATION_FROM_PREVIOUS_ROUND","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PRISMA_CORE","outputs":[{"internalType":"contract IPrismaCore","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RESPONSE_TIMEOUT_BUFFER","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TARGET_DIGITS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"fetchPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"guardian","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"oracleRecords","outputs":[{"internalType":"contract IAggregatorV3Interface","name":"chainLinkOracle","type":"address"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint32","name":"heartbeat","type":"uint32"},{"internalType":"bytes4","name":"sharePriceSignature","type":"bytes4"},{"internalType":"uint8","name":"sharePriceDecimals","type":"uint8"},{"internalType":"bool","name":"isFeedWorking","type":"bool"},{"internalType":"bool","name":"isEthIndexed","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"priceRecords","outputs":[{"internalType":"uint96","name":"scaledPrice","type":"uint96"},{"internalType":"uint32","name":"timestamp","type":"uint32"},{"internalType":"uint32","name":"lastUpdated","type":"uint32"},{"internalType":"uint80","name":"roundId","type":"uint80"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"address","name":"_chainlinkOracle","type":"address"},{"internalType":"uint32","name":"_heartbeat","type":"uint32"},{"internalType":"bytes4","name":"sharePriceSignature","type":"bytes4"},{"internalType":"uint8","name":"sharePriceDecimals","type":"uint8"},{"internalType":"bool","name":"_isEthIndexed","type":"bool"}],"name":"setOracle","outputs":[],"stateMutability":"nonpayable","type":"function"}]