Skip to content

Commit

Permalink
add datastreams consumer
Browse files Browse the repository at this point in the history
  • Loading branch information
Skyewwww committed Nov 26, 2024
1 parent 78ae371 commit df835fb
Show file tree
Hide file tree
Showing 9 changed files with 1,723 additions and 404 deletions.
264 changes: 264 additions & 0 deletions contracts/GasSavingPool/impl/DataStreamsConsumer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/

pragma solidity 0.8.19;

import {Common} from "@chainlink/contracts/src/v0.8/llo-feeds/libraries/Common.sol";
import {StreamsLookupCompatibleInterface} from "@chainlink/contracts/src/v0.8/automation/interfaces/StreamsLookupCompatibleInterface.sol";
import {ILogAutomation, Log} from "@chainlink/contracts/src/v0.8/automation/interfaces/ILogAutomation.sol";
import {IRewardManager} from "@chainlink/contracts/src/v0.8/llo-feeds/interfaces/IRewardManager.sol";
import {IVerifierFeeManager} from "@chainlink/contracts/src/v0.8/llo-feeds/interfaces/IVerifierFeeManager.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

struct TradeParamsStruct {
address pool;
bool isSellBase;
address recipient;
uint256 balance;
uint256 input;
string baseFeedId;
string quoteFeedId;
}

interface IVerifierProxy {
/**
* @notice Verifies that the data encoded has been signed.
* correctly by routing to the correct verifier, and bills the user if applicable.
* @param payload The encoded data to be verified, including the signed
* report.
* @param parameterPayload Fee metadata for billing. For the current implementation this is just the abi-encoded fee token ERC-20 address.
* @return verifierResponse The encoded report from the verifier.
*/
function verify(
bytes calldata payload,
bytes calldata parameterPayload
) external payable returns (bytes memory verifierResponse);

function s_feeManager() external view returns (IVerifierFeeManager);
}

interface IFeeManager {
/**
* @notice Calculates the fee and reward associated with verifying a report, including discounts for subscribers.
* This function assesses the fee and reward for report verification, applying a discount for recognized subscriber addresses.
* @param subscriber The address attempting to verify the report. A discount is applied if this address
* is recognized as a subscriber.
* @param unverifiedReport The report data awaiting verification. The content of this report is used to
* determine the base fee and reward, before considering subscriber discounts.
* @param quoteAddress The payment token address used for quoting fees and rewards.
* @return fee The fee assessed for verifying the report, with subscriber discounts applied where applicable.
* @return reward The reward allocated to the caller for successfully verifying the report.
* @return totalDiscount The total discount amount deducted from the fee for subscribers
*/
function getFeeAndReward(
address subscriber,
bytes memory unverifiedReport,
address quoteAddress
) external returns (Common.Asset memory, Common.Asset memory, uint256);

function i_linkAddress() external view returns (address);

function i_nativeAddress() external view returns (address);

function i_rewardManager() external view returns (address);
}


interface IPool {
function sellBase(TradeParamsStruct memory tradeParams) external;

function sellQuote(TradeParamsStruct memory tradeParams) external;

function _BASE_TOKEN_() external view returns (address);

function _QUOTE_TOKEN_() external view returns (address);

function adjustPrice(uint256 newPrice) external;
}

contract DataStreamsConsumer is ILogAutomation, StreamsLookupCompatibleInterface, Ownable {

IVerifierProxy public VERIFIER;
string[] public feedsHex; // An array of hexadecimal feed IDs.
mapping(string => bool) private feedIdExists;

struct ReportV3 {
bytes32 feedId; // The stream ID the report has data for.
uint32 validFromTimestamp; // Earliest timestamp for which price is applicable.
uint32 observationsTimestamp; // Latest timestamp for which price is applicable.
uint192 nativeFee; // Base cost to validate a transaction using the report, denominated in the chain’s native token (e.g., WETH/ETH).
uint192 linkFee; // Base cost to validate a transaction using the report, denominated in LINK.
uint32 expiresAt; // Latest timestamp where the report can be verified onchain.
int192 price; // DON consensus median price (8 or 18 decimals).
int192 bid; // Simulated price impact of a buy order up to the X% depth of liquidity utilisation (8 or 18 decimals).
int192 ask; // Simulated price impact of a sell order up to the X% depth of liquidity utilisation (8 or 18 decimals).
}

// ============ Events ============
event InitiateTrade(
address pool,
bool isSellBase,
address receiver,
uint256 balance,
uint256 input,
string baseFeedId,
string quoteFeedId
);

// ============ Errors ============
error InvalidFeedId(string feedId);

constructor(address verifier) {
VERIFIER = IVerifierProxy(payable(verifier));
}

function addFeedId(string[] memory feedIds) external onlyOwner {
for (uint256 i = 0; i < feedIds.length; i++) {
if (!feedIdExists[feedIds[i]]) {
feedsHex.push(feedIds[i]);
feedIdExists[feedIds[i]] = true;
}
}
}

function performUpkeep(bytes calldata performData) external {
// Decode performData
(bytes[] memory signedReports, bytes memory extraData) = abi.decode(performData, (bytes[], bytes));

// Retrieve parameters
TradeParamsStruct memory tradeParams = _getTradeParams(extraData);
bytes memory baseUnverifiedReport = signedReports[_getIdFromFeed(tradeParams.baseFeedId)];
bytes memory quoteUnverifiedReport = signedReports[_getIdFromFeed(tradeParams.quoteFeedId)];

// Decode reports
(, /* bytes32[3] reportContextData */ bytes memory baseReportData) = abi.decode(baseUnverifiedReport, (bytes32[3], bytes));
(, /* bytes32[3] reportContextData */ bytes memory quoteReportData) = abi.decode(quoteUnverifiedReport, (bytes32[3], bytes));

// Calculate fees
_handleFees(baseReportData, quoteReportData);

// Verify reports
bytes memory baseVerifiedReportData = _verifyReport(baseUnverifiedReport);
bytes memory quoteVerifiedReportData = _verifyReport(quoteUnverifiedReport);
ReportV3 memory baseVerifiedReport = abi.decode(baseVerifiedReportData, (ReportV3));
ReportV3 memory quoteVerifiedReport = abi.decode(quoteVerifiedReportData, (ReportV3));

// Update prices and execute trade
_updatePrice(tradeParams.pool, baseVerifiedReport.price, quoteVerifiedReport.price);
_executeTrade(tradeParams);
}


// ============ Helper Functions ============
function _getTradeParams(bytes memory extraData) internal pure returns (TradeParamsStruct memory tradeParams) {
Log memory log = abi.decode(extraData, (Log));

(
address pool,
bool isSellBase,
address recipient,
uint256 balance,
uint256 input,
string memory baseFeedId,
string memory quoteFeedId
) = abi.decode(log.data, (address, bool, address, uint256, uint256, string, string));
tradeParams = TradeParamsStruct(pool, isSellBase, recipient, balance, input, baseFeedId, quoteFeedId);
}

function _getIdFromFeed(string memory feedId) internal view returns (uint256 index) {
string[] storage feeds = feedsHex;
for (uint256 i = 0; i < feeds.length; i++) {
if (
keccak256(abi.encode(feeds[i])) == keccak256(abi.encode(feedId))
) {
index = i;
break;
}
if (i == feeds.length - 1) {
revert InvalidFeedId(feedId);
}
}
return index;
}

// Helper function to approve fees and manage rewards
function _handleFees(bytes memory baseReportData, bytes memory quoteReportData) internal {
IFeeManager feeManager = IFeeManager(address(VERIFIER.s_feeManager()));
IRewardManager rewardManager = IRewardManager(address(feeManager.i_rewardManager()));
address feeTokenAddress = feeManager.i_linkAddress();

(Common.Asset memory baseFee, , ) = feeManager.getFeeAndReward(address(this), baseReportData, feeTokenAddress);
(Common.Asset memory quoteFee, , ) = feeManager.getFeeAndReward(address(this), quoteReportData, feeTokenAddress);

uint totalFee = baseFee.amount + quoteFee.amount;
IERC20(feeTokenAddress).approve(address(rewardManager), totalFee);
}

// Helper function to verify report data
function _verifyReport(bytes memory unverifiedReport) internal returns (bytes memory) {
IFeeManager feeManager = IFeeManager(address(VERIFIER.s_feeManager()));
address feeTokenAddress = feeManager.i_linkAddress();
return VERIFIER.verify(unverifiedReport, abi.encode(feeTokenAddress));
}

function _updatePrice(address pool, int192 basePrice, int192 quotePrice) internal {
address baseToken = IPool(pool)._BASE_TOKEN_();
address quoteToken = IPool(pool)._QUOTE_TOKEN_();
uint256 baseDecimal = uint256(IERC20Metadata(baseToken).decimals());
uint256 quoteDecimal = uint256(IERC20Metadata(quoteToken).decimals());
uint256 newPrice = (uint256(int256(basePrice)) * 10 ** (18 - baseDecimal + quoteDecimal)) / uint256(int256(quotePrice));
IPool(pool).adjustPrice(newPrice);
}

// Helper function to execute trade based on trade parameters
function _executeTrade(TradeParamsStruct memory tradeParams) internal {
if (tradeParams.isSellBase) {
IPool(tradeParams.pool).sellBase(tradeParams);
} else {
IPool(tradeParams.pool).sellQuote(tradeParams);
}
}

function checkLog(
Log calldata log,
bytes memory /* checkData */
) external view override returns (bool /* upkeepNeeded */, bytes memory /* performData */) {
revert StreamsLookup(
"feedIDs",
feedsHex,
"timestamp",
log.timestamp,
abi.encode(log)
);
}

function checkCallback(
bytes[] memory values,
bytes memory extraData
)
external
pure
override
returns (bool upkeepNeeded, bytes memory performData)
{
return (true, abi.encode(values, extraData));
}

function checkErrorHandler(
uint256 /*errCode*/,
bytes memory /*extraData*/
)
external
pure
override
returns (bool upkeepNeeded, bytes memory performData)
{
return (true, "0");
}

receive() external payable {}
}
24 changes: 19 additions & 5 deletions contracts/GasSavingPool/impl/GSP.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ contract GSP is GSPTrader, GSPFunding {
* @param mtFeeRate The rate of mt fee, with 18 decimal
* @param i The oracle price, possible to be changed only by maintainer
* @param k The swap curve parameter
* @param isOpenTWAP Useless, always false, just for compatible with old version pool
* @param dataStreamsConsumer The data stream consumer address
* @param baseFeedId The base feed id
* @param quoteFeedId The quote feed id
*/
function init(
address maintainer,
Expand All @@ -41,14 +43,20 @@ contract GSP is GSPTrader, GSPFunding {
uint256 mtFeeRate,
uint256 i,
uint256 k,
bool isOpenTWAP
address dataStreamsConsumer,
string memory baseFeedId,
string memory quoteFeedId
) external {
// GSP can only be initialized once
require(!_GSP_INITIALIZED_, "GSP_INITIALIZED");
if (_GSP_INITIALIZED_) {
revert GSP_INITIALIZED();
}
// _GSP_INITIALIZED_ is set to true after initialization
_GSP_INITIALIZED_ = true;
// baseTokenAddress and quoteTokenAddress should not be the same
require(baseTokenAddress != quoteTokenAddress, "BASE_QUOTE_CAN_NOT_BE_SAME");
if (baseTokenAddress == quoteTokenAddress) {
revert BASE_QUOTE_CAN_NOT_BE_SAME();
}
// _BASE_TOKEN_ and _QUOTE_TOKEN_ should be valid ERC20 tokens
_BASE_TOKEN_ = IERC20(baseTokenAddress);
_QUOTE_TOKEN_ = IERC20(quoteTokenAddress);
Expand All @@ -67,7 +75,13 @@ contract GSP is GSPTrader, GSPFunding {
// _MAINTAINER_ is set when initialization, the address receives the fee
_MAINTAINER_ = maintainer;
_ADMIN_ = admin;

_PRICE_LIMIT_ = 1000;
// _IS_OPEN_TWAP_ is always false

_DATA_STREAMS_CONSUMER_ = dataStreamsConsumer;
_BASE_FEED_ID_ = baseFeedId;
_QUOTE_FEED_ID_ = quoteFeedId;
_IS_OPEN_TWAP_ = false;


Expand Down Expand Up @@ -130,6 +144,6 @@ contract GSP is GSPTrader, GSPFunding {
* @return The current version is 1.0.1
*/
function version() external pure returns (string memory) {
return "GSP 1.0.1";
return "GSP 1.0.2";
}
}
17 changes: 7 additions & 10 deletions contracts/GasSavingPool/impl/GSPFunding.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,23 +49,23 @@ contract GSPFunding is GSPVault {
quoteInput = quoteBalance - quoteReserve;

// BaseToken should be transferred to GSP before calling buyShares
require(baseInput > 0, "NO_BASE_INPUT");
if (baseInput == 0) revert ZERO_BASE_INPUT();

// Round down when withdrawing. Therefore, never be a situation occuring balance is 0 but totalsupply is not 0
// But May Happen,reserve >0 But totalSupply = 0
if (totalSupply == 0) {
// case 1. initial supply
require(quoteBalance > 0, "ZERO_QUOTE_AMOUNT");
if (quoteBalance == 0) revert ZERO_QUOTE_AMOUNT();
// The shares will be minted to user
shares = quoteBalance < DecimalMath.mulFloor(baseBalance, _I_)
? DecimalMath.divFloor(quoteBalance, _I_)
: baseBalance;
// The target will be updated
_BASE_TARGET_ = uint112(shares);
_QUOTE_TARGET_ = uint112(DecimalMath.mulFloor(shares, _I_));
require(_QUOTE_TARGET_ > 0, "QUOTE_TARGET_IS_ZERO");
if (_QUOTE_TARGET_ == 0) revert QUOTE_TARGET_IS_ZERO();
// Lock 1001 shares permanently in first deposit
require(shares > 2001, "MINT_AMOUNT_NOT_ENOUGH");
if (shares <= 2001) revert MINT_AMOUNT_NOT_ENOUGH();
_mint(address(0), 1001);
shares -= 1001;
} else if (baseReserve > 0 && quoteReserve > 0) {
Expand Down Expand Up @@ -104,9 +104,9 @@ contract GSPFunding is GSPVault {
uint256 deadline
) external nonReentrant returns (uint256 baseAmount, uint256 quoteAmount) {
// The deadline should be greater than current timestamp
require(deadline >= block.timestamp, "TIME_EXPIRED");
if (deadline < block.timestamp) revert TIME_EXPIRED();
// The amount of shares user want to sell should be less than user's balance
require(shareAmount <= _SHARES_[msg.sender], "GLP_NOT_ENOUGH");
if (shareAmount > _SHARES_[msg.sender]) revert GLP_NOT_ENOUGH();

// The balance of baseToken and quoteToken should be the balance minus the fee
uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)) - _MT_FEE_BASE_;
Expand All @@ -123,10 +123,7 @@ contract GSPFunding is GSPVault {
_QUOTE_TARGET_ = uint112(uint256(_QUOTE_TARGET_) - DecimalMath._divCeil((uint256(_QUOTE_TARGET_) * (shareAmount)), totalShares));

// The calculated baseToken and quoteToken amount should geater than minBaseToken and minQuoteToken
require(
baseAmount >= baseMinAmount && quoteAmount >= quoteMinAmount,
"WITHDRAW_NOT_ENOUGH"
);
if (baseAmount < baseMinAmount || quoteAmount < quoteMinAmount) revert WITHDRAW_NOT_ENOUGH();

// The shares will be burned from user
// The baseToken and quoteToken will be transferred to user
Expand Down
Loading

0 comments on commit df835fb

Please sign in to comment.