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

feat: Chainlink interface #94

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
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: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
target
openapi.json
solidity/lib
solidity/out
18 changes: 10 additions & 8 deletions solidity/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ contract YourContract {
}

/**
* This method is an example of how to interact with the Pragma contract to fetch Spot Median updates. You can check the documentation to
* find the available feeds.
* @param priceUpdate The encoded data to update the contract with the latest price
*/
* This method is an example of how to interact with the Pragma contract to fetch Spot Median updates. You can check the documentation to
* find the available feeds.
* @param priceUpdate The encoded data to update the contract with the latest price
*/
function yourFunction(bytes[] calldata priceUpdate) public payable {
// Submit a priceUpdate to the Pragma contract to update the on-chain price.
// Updating the price requires paying the fee returned by getUpdateFee.
Expand All @@ -59,9 +59,11 @@ contract YourContract {
// Read the current price from a price feed if it is less than 60 seconds old.
// Each price feed (e.g., Spot Median ETH/USD) is identified by a unique identifier id.
bytes32 id = 0x4554482f555344; // ETH/USD
PragmaStructs.DataFeed memory data_feed = oracle.getSpotMedianNoOlderThan(id, 60);
PragmaStructs.DataFeed memory data_feed = oracle.getSpotMedianNoOlderThan(
id,
60
);
}

}
```

Expand All @@ -76,8 +78,8 @@ You can find [here](https://docs.pragma.build/v2/Price%20Feeds/supported-assets-
Alternatively, you can copy the `IPragma.sol` interface and `PragmaStructs.sol` inside your repository, and generate an instance using a deployed Pragma contract.

```solidity
import {IPragma} from "./interfaces/IPragma.sol";
import PragmaStructs from "./interfaces/PragmaStructs.sol";
import { IPragma } from "./interfaces/IPragma.sol";
import "./interfaces/PragmaStructs.sol" as PragmaStructs;
```

The rest remains the same as described above.
Expand Down
12 changes: 10 additions & 2 deletions solidity/package-lock.json

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

3 changes: 3 additions & 0 deletions solidity/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,8 @@
"prettier": "^2.7.1",
"prettier-plugin-solidity": "^1.0.0-rc.1",
"solc": "^0.8.25"
},
"dependencies": {
"@pragmaoracle/solidity-sdk": "^1.0.1"
}
}
3 changes: 2 additions & 1 deletion solidity/remappings.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/
@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/
@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/
@pragmaoracle/solidity-sdk/=node_modules/@pragmaoracle/solidity-sdk/
4 changes: 2 additions & 2 deletions solidity/scripts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function generateAbi(contracts) {
}

const output = JSON.parse(
solc.compile(JSON.stringify(input), { import: findImports }),
solc.compile(JSON.stringify(input), { import: findImports })
);
console.log(output);

Expand All @@ -57,7 +57,7 @@ function generateAbi(contracts) {
const abi = output.contracts[contractFile][trimedContract].abi;
fs.writeFileSync(
`abis/${trimedContract}.json`,
JSON.stringify(abi, null, 2) + "\n",
JSON.stringify(abi, null, 2) + "\n"
);
}
}
Expand Down
64 changes: 49 additions & 15 deletions solidity/src/Hyperlane.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,47 +21,67 @@
_validators = validators;
}

function parseAndVerifyHyMsg(bytes calldata encodedHyMsg)
function parseAndVerifyHyMsg(
bytes calldata encodedHyMsg
)
public
view
returns (HyMsg memory hyMsg, bool valid, string memory reason, uint256 index, bytes32 checkpointRoot)
returns (
HyMsg memory hyMsg,
bool valid,
string memory reason,
uint256 index,
bytes32 checkpointRoot
)
{
(hyMsg, index, checkpointRoot) = parseHyMsg(encodedHyMsg);
(valid, reason) = verifyHyMsg(hyMsg);
}

function verifyHyMsg(HyMsg memory hyMsg) public view returns (bool valid, string memory reason) {
function verifyHyMsg(
HyMsg memory hyMsg
) public view returns (bool valid, string memory reason) {
// TODO: fetch validators from calldata/storage
address[] memory validators = _validators;
if (validators.length == 0) {
return (false, "no validators announced");
}

// We're using a fixed point number transformation with 1 decimal to deal with rounding.
// we check that we have will be able to reach a quorum with the current signatures
if ((((validators.length * 10) / 3) * 2) / 10 + 1 > hyMsg.signatures.length) {
if (
(((validators.length * 10) / 3) * 2) / 10 + 1 >
hyMsg.signatures.length
) {
return (false, "no quorum");
}
// Verify signatures
(bool signaturesValid, string memory invalidReason) = verifySignatures(hyMsg.hash, hyMsg.signatures, validators);
(bool signaturesValid, string memory invalidReason) = verifySignatures(
hyMsg.hash,
hyMsg.signatures,
validators
);
if (!signaturesValid) {
return (false, invalidReason);
}

return (true, "");
}

Check warning

Code scanning / Slither

Divide before multiply Medium


function verifySignatures(bytes32 hash, Signature[] memory signatures, address[] memory validators)
public
pure
returns (bool valid, string memory reason)
{
function verifySignatures(
bytes32 hash,
Signature[] memory signatures,
address[] memory validators
) public pure returns (bool valid, string memory reason) {
uint8 lastIndex = 0;
// TODO: break on quorum
for (uint256 i = 0; i < signatures.length; i++) {
Signature memory sig = signatures[i];

require(i == 0 || sig.validatorIndex > lastIndex, "signature indices must be ascending");
require(
i == 0 || sig.validatorIndex > lastIndex,
"signature indices must be ascending"
);
lastIndex = sig.validatorIndex;
bytes memory signature = abi.encodePacked(sig.r, sig.s, sig.v);
if (!_verify(hash, signature, validators[sig.validatorIndex])) {
Expand All @@ -71,7 +91,9 @@
return (true, "");
}

function parseHyMsg(bytes calldata encodedHyMsg)
function parseHyMsg(
bytes calldata encodedHyMsg
)
public
pure
returns (HyMsg memory hyMsg, uint256 index, bytes32 checkPointRoot)
Expand Down Expand Up @@ -113,7 +135,13 @@
bytes32 merkleTreeHookAddress = encodedHyMsg.toBytes32(index);
index += 32;

bytes32 domainHash = keccak256(abi.encodePacked(hyMsg.emitterChainId, merkleTreeHookAddress, "HYPERLANE"));
bytes32 domainHash = keccak256(
abi.encodePacked(
hyMsg.emitterChainId,
merkleTreeHookAddress,
"HYPERLANE"
)
);

bytes32 root = encodedHyMsg.toBytes32(index);
index += 32;
Expand All @@ -125,12 +153,18 @@
index += 32;

// Hash the configuration
hyMsg.hash = keccak256(abi.encodePacked(domainHash, root, checkpointIndex, messageId));
hyMsg.hash = keccak256(
abi.encodePacked(domainHash, root, checkpointIndex, messageId)
);

hyMsg.payload = encodedHyMsg.slice(index, encodedHyMsg.length - index);
}

function _verify(bytes32 data, bytes memory signature, address account) internal pure returns (bool) {
function _verify(
bytes32 data,
bytes memory signature,
address account
) internal pure returns (bool) {
return data.toEthSignedMessageHash().recover(signature) == account;
}
}
78 changes: 60 additions & 18 deletions solidity/src/Pragma.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@
/// @author Pragma Labs
/// @custom:contact [email protected]
/// @notice The Pragma contract.
contract Pragma is Initializable, UUPSUpgradeable, OwnableUpgradeable, IPragma, PragmaDecoder {
contract Pragma is
Initializable,
UUPSUpgradeable,
OwnableUpgradeable,
IPragma,
PragmaDecoder
{
/* STORAGE */
uint256 public validTimePeriodSeconds;
uint256 public singleUpdateFeeInWei;
Expand All @@ -38,21 +44,28 @@
hyperlane = IHyperlane(_hyperlane);

for (uint256 i = 0; i < _dataSourceEmitterChainIds.length; i++) {
_isValidDataSource[keccak256(
abi.encodePacked(_dataSourceEmitterChainIds[i], _dataSourceEmitterAddresses[i])
)] = true;
_isValidDataSource[
keccak256(
abi.encodePacked(
_dataSourceEmitterChainIds[i],
_dataSourceEmitterAddresses[i]
)
)
] = true;
}
validTimePeriodSeconds = _validTimePeriodSeconds;
singleUpdateFeeInWei = _singleUpdateFeeInWei;
}

function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
function _authorizeUpgrade(
address newImplementation
) internal override onlyOwner {}

/// @inheritdoc IPragma
function updateDataFeeds(bytes[] calldata updateData) external payable {
uint256 totalNumUpdates = 0;
uint256 len = updateData.length;
for (uint256 i = 0; i < len;) {
for (uint256 i = 0; i < len; ) {
totalNumUpdates += updateDataInfoFromUpdate(updateData[i]);
unchecked {
i++;
Expand All @@ -65,68 +78,83 @@
}

/// @inheritdoc IPragma
function getUpdateFee(bytes[] calldata updateData) external view returns (uint256 feeAmount) {
function getUpdateFee(
bytes[] calldata updateData
) external view returns (uint256 feeAmount) {
return 0;
}

function getTotalFee(uint256 totalNumUpdates) private view returns (uint256 requiredFee) {
function getTotalFee(
uint256 totalNumUpdates
) private view returns (uint256 requiredFee) {
return totalNumUpdates * singleUpdateFeeInWei;
}

function getSpotMedianNoOlderThan(bytes32 id, uint256 age) external view returns (SpotMedian memory data) {
function getSpotMedianNoOlderThan(
bytes32 id,
uint256 age
) external view returns (SpotMedian memory data) {
data = spotMedianFeeds[id];
if (data.metadata.timestamp == 0) {
revert ErrorsLib.DataNotFound();
}
if (diff(block.timestamp, data.metadata.timestamp) > age) {
revert ErrorsLib.DataStale();
}
return data;
}

Check notice

Code scanning / Slither

Block timestamp Low


function getTwapNoOlderThan(bytes32 id, uint256 age) external view returns (TWAP memory data) {
function getTwapNoOlderThan(
bytes32 id,
uint256 age
) external view returns (TWAP memory data) {
data = twapFeeds[id];
if (data.metadata.timestamp == 0) {
revert ErrorsLib.DataNotFound();
}
if (diff(block.timestamp, data.metadata.timestamp) > age) {
revert ErrorsLib.DataStale();
}
}

Check notice

Code scanning / Slither

Block timestamp Low


function getRealizedVolatilityNoOlderThan(bytes32 id, uint256 age)
external
view
returns (RealizedVolatility memory data)
{
function getRealizedVolatilityNoOlderThan(
bytes32 id,
uint256 age
) external view returns (RealizedVolatility memory data) {
data = rvFeeds[id];
if (data.metadata.timestamp == 0) {
revert ErrorsLib.DataNotFound();
}
if (diff(block.timestamp, data.metadata.timestamp) > age) {
revert ErrorsLib.DataStale();
}
}

Check notice

Code scanning / Slither

Block timestamp Low


function getOptionsNoOlderThan(bytes32 id, uint256 age) external view returns (Options memory data) {
function getOptionsNoOlderThan(
bytes32 id,
uint256 age
) external view returns (Options memory data) {
data = optionsFeeds[id];
if (data.metadata.timestamp == 0) {
revert ErrorsLib.DataNotFound();
}
if (diff(block.timestamp, data.metadata.timestamp) > age) {
revert ErrorsLib.DataStale();
}
}

Check notice

Code scanning / Slither

Block timestamp Low


function getPerpNoOlderThan(bytes32 id, uint256 age) external view returns (Perp memory data) {
function getPerpNoOlderThan(
bytes32 id,
uint256 age
) external view returns (Perp memory data) {
data = perpFeeds[id];
if (data.metadata.timestamp == 0) {
revert ErrorsLib.DataNotFound();
}
if (diff(block.timestamp, data.metadata.timestamp) > age) {
revert ErrorsLib.DataStale();
}
}

Check notice

Code scanning / Slither

Block timestamp Low


/// @inheritdoc IPragma
function dataFeedExists(bytes32 id) external view returns (bool) {
Expand All @@ -150,9 +178,23 @@
return validTimePeriodSeconds;
}

/* GETTERS */
/// Even if the getters are automatically set for the public storage variable, we need to define the getter to make it
/// accessible for the interface

function getSpotMedianFeed(
bytes32 feedId
) external view returns (SpotMedian memory) {
SpotMedian memory feed = spotMedianFeeds[feedId];
if (feed.metadata.timestamp == 0) {
revert ErrorsLib.DataNotFound();
}
return feed;
}

function withdrawFunds(uint256 amount) external onlyOwner {
require(amount <= address(this).balance, "Insufficient balance");
(bool success,) = owner().call{value: amount}("");
(bool success, ) = owner().call{value: amount}("");
require(success, "Transfer failed");
}

Expand Down
Loading
Loading