Skip to content

Commit

Permalink
initial structure
Browse files Browse the repository at this point in the history
  • Loading branch information
Nicholas Fett authored and Nicholas Fett committed Sep 16, 2024
1 parent 0e2ed6f commit a9e665a
Show file tree
Hide file tree
Showing 13 changed files with 7,782 additions and 1 deletion.
17 changes: 17 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
node_modules
.env

# Hardhat files
/cache
/artifacts

# TypeChain files
/typechain
/typechain-types

# solidity-coverage files
/coverage
/coverage.json

# Hardhat Ignition default folder for deployments against a local node
ignition/deployments/chain-31337
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,21 @@
# SampleLayerUser
# SampleLayerUser

The purpose of this is to show some best practices with regards on how to think about security of your data.


Areas to think about:

- The data is at least as old as 2 tellor blocks (1 sec each), and one block on the users chain (e.g. 12 sec for ethereum per block).
However as with any blockchain, the blocks can fill up and validators can select or censor certain transactions so the data is not guarunteed to be fast.
- All oracles can fail. Have a go to for if this happens (a pause or fallback if compromised, a method if the oracle just pauses).
- data definitions - what data are you actually getting? What are the dispute thresholds? Are they strict or loose?
So answer in code:

Do you take the given oracle value upon submission? What aggregate power do you require?
Do you use it optimistically if consnensus not reached? How long do you wait for disputes? Do you have a minimum power here?
How old can the data be? (Be sure to verify that you can't dispute to go back in time)
What happens if the oracle stops posting data?
Can the contract be paused? How long, what are the details?
How can users exit your contract in the case of a pause or oracle not posting data
Are there exits in your system w/ no oracle updates or stale oracle updates (locking users until valid updates prevents censorship)

81 changes: 81 additions & 0 deletions contracts/SampleCPIUser.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;

import "../interfaces/IBlobstreamO.sol";

// this contract has a pause button from a guardian
// it does not fallback to anything, but if the price is not updated for 48 hours, the guardian can change the oracle address
// the contract consensus or fallback to a 24 hour delay with no power threshold (if not sure on support)
//data can only go forward in time and must be within 5 minutes old, , must prove it's latest value

contract SampleCPIUser {
IBlobstreamO public blobstreamO;
PriceData[] public priceData;
bytes32 public queryId;
bool public paused;
address public guardian;

event OracleUpdated(uint256 price, uint256 timestamp, uint256 aggregatePower);

struct PriceData {
uint256 price;
uint256 timestamp;
uint256 aggregatePower;
uint256 previousTimestamp;
uint256 nextTimestamp;
uint256 relayTimestamp;
}

constructor(address _blobstreamO, bytes32 _queryId, address _guardian) {
blobstreamO = IBlobstreamO(_blobstreamO);
queryId = _queryId;
guardian = _guardian;
}

function pauseContract() external{
require(msg.sender == guardian, "should be guardian");
paused = true;
}

function updateOracleData(
OracleAttestationData calldata _attestData,
Validator[] calldata _currentValidatorSet,
Signature[] calldata _sigs
) external {
require(!paused, "contract paused");
require(_attestData.queryId == queryId, "Invalid queryId");
blobstreamO.verifyOracleData(_attestData, _currentValidatorSet, _sigs);
uint256 _price = abi.decode(_attestData.report.value, (uint256));
if(_attestData.report.aggregatePower < blobstreamO.powerThreshold()){//if not consensus data
require(_attestData.attestationTimestamp - _attestData.report.timestamp >= 15 minutes);//must be at least 15 minutes old
require(_attestData.report.aggregatePower > blobstreamO.powerThreshold()/2);//must have >1/3 aggregate power
require(_attestData.report.nextTimestamp == 0 ||
_attestData.attestationTimestamp - _attestData.report.nextTimestamp < 15 minutes);//cannot have newer data you can push
}else{
require(_attestData.report.nextTimestamp == 0, "should be no newer timestamp"); // must push the newest data
}
require(block.timestamp - _attestData.attestationTimestamp < 10 minutes);//data cannot be more than 10 minutes old (the relayed attestation)
require(_attestData.report.timestamp > priceData[priceData.length - 1].timestamp);//cannot go back in time
priceData.push(PriceData(
_price,
_attestData.report.timestamp,
_attestData.report.aggregatePower,
_attestData.report.previousTimestamp,
_attestData.report.nextTimestamp,
block.timestamp
)
);
}

function getCurrentPriceData() external view returns (PriceData memory) {
return priceData[priceData.length - 1];
}

function getAllPriceData() external view returns(PriceData[] memory){
return priceData;
}

function getValueCount() external view returns (uint256) {
return priceData.length;
}
}
82 changes: 82 additions & 0 deletions contracts/SampleEVMCallUser.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;

import "../interfaces/IBlobstreamO.sol";


// this contract has a pause button from a guardian
// for our oracle, if its not updated for 24 hours, the guardian can change the oracle address
// the contract is always on a delay (no finality), so you have 1 hour delay with 1/3 aggregate power threshold

//example user - liquity
contract SampleEVMCalleUser {
IBlobstreamO public blobstreamO;
PriceData[] public priceData;
bytes32 public queryId;
bool public paused;
address public guardian;

event OracleUpdated(uint256 price, uint256 timestamp, uint256 aggregatePower);

struct PriceData {
uint256 price;
uint256 timestamp;
uint256 aggregatePower;
uint256 previousTimestamp;
uint256 nextTimestamp;
uint256 relayTimestamp;
}

constructor(address _blobstreamO, bytes32 _queryId, address _guardian) {
blobstreamO = IBlobstreamO(_blobstreamO);
queryId = _queryId;
guardian = _guardian;
}

function pauseContract() external{
require(msg.sender == guardian, "should be guardian");
paused = true;
}

function updateOracleData(
OracleAttestationData calldata _attestData,
Validator[] calldata _currentValidatorSet,
Signature[] calldata _sigs
) external {
require(!paused, "contract paused");
require(_attestData.queryId == queryId, "Invalid queryId");
blobstreamO.verifyOracleData(_attestData, _currentValidatorSet, _sigs);
uint256 _price = abi.decode(_attestData.report.value, (uint256));
if(_attestData.report.aggregatePower < blobstreamO.powerThreshold()){//if not consensus data
require(_attestData.attestationTimestamp - _attestData.report.timestamp >= 15 minutes);//must be at least 15 minutes old
require(_attestData.report.aggregatePower > blobstreamO.powerThreshold()/2);//must have >1/3 aggregate power
require(_attestData.report.nextTimestamp == 0 ||
_attestData.attestationTimestamp - _attestData.report.nextTimestamp < 15 minutes);//cannot have newer data you can push
}else{
require(_attestData.report.nextTimestamp == 0, "should be no newer timestamp"); // must push the newest data
}
require(block.timestamp - _attestData.attestationTimestamp < 10 minutes);//data cannot be more than 10 minutes old (the relayed attestation)
require(_attestData.report.timestamp > priceData[priceData.length - 1].timestamp);//cannot go back in time
priceData.push(PriceData(
_price,
_attestData.report.timestamp,
_attestData.report.aggregatePower,
_attestData.report.previousTimestamp,
_attestData.report.nextTimestamp,
block.timestamp
)
);
}

function getCurrentPriceData() external view returns (PriceData memory) {
return priceData[priceData.length - 1];
}

function getAllPriceData() external view returns(PriceData[] memory){
return priceData;
}

function getValueCount() external view returns (uint256) {
return priceData.length;
}
}
103 changes: 103 additions & 0 deletions contracts/SampleFallbackOracleUser.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;

import "../interfaces/IBlobstreamO.sol";


// this contract has a pause button from a guardian. Just pauses centralized oracle for 24 hours (then 24 hr before can pause again)
// has governance contract to upgrade centralized oracle
// uses a centralized oracle, but fallsback IF:
// oracle not updated in 1 hour
// oracle frozen by guardian

// for our oracle, if its not updated for 7 days, the guardian can change the oracle address
// the contract is consensus or fallback to a 15 minute delay with 1/3 aggregate power threshold (assume a large pair, should be smaller if less supported)
// data must be newer than 5 minutes ago, must prove it's latest value

//example user - liquity
contract SampleFallbackOracleUser {
IBlobstreamO public blobstreamO;
PriceData[] public priceData;
bytes32 public queryId;
bool public paused;
uint256 public pauseTimestamp;
address public guardian;

event OracleUpdated(uint256 price, uint256 timestamp, uint256 aggregatePower);

struct PriceData {
uint256 price;
uint256 timestamp;
uint256 aggregatePower;
uint256 previousTimestamp;
uint256 nextTimestamp;
uint256 relayTimestamp;
}

constructor(address _blobstreamO, bytes32 _queryId, address _guardian, address _centralizedOracle) {
blobstreamO = IBlobstreamO(_blobstreamO);
queryId = _queryId;
guardian = _guardian;
centralizedOracle = _centralizedOracle;
}

function pauseContract() external{
require(msg.sender == guardian, "should be guardian");
paused = true;
}

function updateOracleData(
OracleAttestationData calldata _attestData,
Validator[] calldata _currentValidatorSet,
Signature[] calldata _sigs
) external {
require(_attestData.report.timestamp > priceData[priceData.length - 1].timestamp, "cannot go back in time");//cannot go back in time
uint256 _price = abi.decode(_attestData.report.value, (uint256));
if(!paused && (block.timestamp - priceData[priceData.length - 1]) < 1 hours){
require(msg.sender == centralizedOracle, "must be proper signer");
priceData.push(PriceData(
_price,
_attestData.report.timestamp,
_attestData.report.aggregatePower,
_attestData.report.previousTimestamp,
_attestData.report.nextTimestamp,
block.timestamp
)
);
return;
}
require(!paused, "contract paused");
require(_attestData.queryId == queryId, "Invalid queryId");
blobstreamO.verifyOracleData(_attestData, _currentValidatorSet, _sigs);
if(_attestData.report.aggregatePower < blobstreamO.powerThreshold()){//if not consensus data
require(_attestData.attestationTimestamp - _attestData.report.timestamp >= 15 minutes);//must be at least 15 minutes old
require(_attestData.report.aggregatePower > blobstreamO.powerThreshold()/2);//must have >1/3 aggregate power
require(_attestData.report.nextTimestamp == 0 ||
_attestData.attestationTimestamp - _attestData.report.nextTimestamp < 15 minutes);//cannot have newer data you can push
}else{
require(_attestData.report.nextTimestamp == 0, "should be no newer timestamp"); // must push the newest data
}
require(block.timestamp - _attestData.attestationTimestamp < 5 minutes);//data cannot be more than 5 minutes old (the relayed attestation)
priceData.push(PriceData(
_price,
_attestData.report.timestamp,
_attestData.report.aggregatePower,
_attestData.report.previousTimestamp,
_attestData.report.nextTimestamp,
block.timestamp
)
);
}

function getCurrentPriceData() external view returns (PriceData memory) {
return priceData[priceData.length - 1];
}

function getAllPriceData() external view returns(PriceData[] memory){
return priceData;
}

function getValueCount() external view returns (uint256) {
return priceData.length;
}
}
73 changes: 73 additions & 0 deletions contracts/SamplePredictionMarketUser.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;

import "../interfaces/IBlobstreamO.sol";

// this contract has a pause button from a guardian (if paused, it should settle invalid or go to governance)
// the contract consensus or fallback to a 24 hour delay with no aggregate power threshold (since you don't know what the question is)
// data age can be whenever (just one answer)

contract SamplePredictionMarketUser {
IBlobstreamO public blobstreamO;
PriceData[] public priceData;
bytes32 public queryId;
bool public paused;
address public guardian;

event OracleUpdated(uint256 price, uint256 timestamp, uint256 aggregatePower);

struct PriceData {
uint256 price;
uint256 timestamp;
uint256 aggregatePower;
uint256 previousTimestamp;
uint256 nextTimestamp;
uint256 relayTimestamp;
}

constructor(address _blobstreamO, bytes32 _queryId, address _guardian) {
blobstreamO = IBlobstreamO(_blobstreamO);
queryId = _queryId;
guardian = _guardian;
}

function pauseContract() external{
require(msg.sender == guardian, "should be guardian");
paused = true;
}

function updateOracleData(
OracleAttestationData calldata _attestData,
Validator[] calldata _currentValidatorSet,
Signature[] calldata _sigs
) external {
require(!paused, "contract paused");
require(_attestData.queryId == queryId, "Invalid queryId");
blobstreamO.verifyOracleData(_attestData, _currentValidatorSet, _sigs);
uint256 _price = abi.decode(_attestData.report.value, (uint256));
if(_attestData.report.aggregatePower < blobstreamO.powerThreshold()){//if not consensus data
require(_attestData.attestationTimestamp - _attestData.report.timestamp >= 24 hours);//must be at least 24 hours old
}
priceData.push(PriceData(
_price,
_attestData.report.timestamp,
_attestData.report.aggregatePower,
_attestData.report.previousTimestamp,
_attestData.report.nextTimestamp,
block.timestamp
)
);
}

function getCurrentPriceData() external view returns (PriceData memory) {
return priceData[priceData.length - 1];
}

function getAllPriceData() external view returns(PriceData[] memory){
return priceData;
}

function getValueCount() external view returns (uint256) {
return priceData.length;
}
}
Loading

0 comments on commit a9e665a

Please sign in to comment.