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

Add Gelato web3 function deployment script for redstone oracles #208

Draft
wants to merge 5 commits into
base: LISK-1155-Set-up-RedStone-Push-Oracle-contracts
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 3 additions & 0 deletions .env.devnet
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ L1_RPC_URL=http://localhost:8545
# L2 RPC URL, e.g. Infura, Alchemy, or your own node
L2_RPC_URL=http://localhost:8546

# Provider url for testing web3 function with
PROVIDER_URLS=http://localhost:8546

Comment on lines +64 to +66
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this URL be different than what is L2_RPC_URL? Can we just use L2_RPC_URL without having a new one?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general it would be same. I am not sure where or which library picks this env var and we might have to change the lib code so maybe better to keep it separate (for now)

# *************** SMART CONTRACT VERIFICATION ***************

# Contract verifier - blockscout or etherscan. If not provided, verification will be skipped.
Expand Down
3 changes: 3 additions & 0 deletions .env.mainnet
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ L1_RPC_URL=https://eth.drpc.org
# L2 RPC URL, e.g. Infura, Alchemy, or your own node
L2_RPC_URL=https://rpc.api.lisk.com

# Provider url for testing web3 function with
PROVIDER_URLS=https://rpc.api.lisk.com

# *************** SMART CONTRACT VERIFICATION ***************

# Contract verifier - blockscout or etherscan. If not provided, verification will be skipped.
Expand Down
3 changes: 3 additions & 0 deletions .env.testnet
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ L1_RPC_URL=https://sepolia.drpc.org
# L2 RPC URL, e.g. Infura, Alchemy, or your own node
L2_RPC_URL=https://rpc.sepolia-api.lisk.com

# Provider url for testing web3 function with
PROVIDER_URLS=https://rpc.sepolia-api.lisk.com

# *************** SMART CONTRACT VERIFICATION ***************

# Contract verifier - blockscout or etherscan. If not provided, verification will be skipped.
Expand Down
94 changes: 94 additions & 0 deletions hardhat.config.ts
Incede marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { HardhatUserConfig } from "hardhat/config";

// PLUGINS
import "@gelatonetwork/web3-functions-sdk/hardhat-plugin";
import "@nomicfoundation/hardhat-chai-matchers";
import "@nomiclabs/hardhat-ethers";
import "@typechain/hardhat";
import "hardhat-deploy";

// Process Env Variables
import * as dotenv from "dotenv";
dotenv.config({ path: __dirname + "/.env" });

const PK = process.env.PK;
const ALCHEMY_ID = process.env.ALCHEMY_ID;
const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY;

// HardhatUserConfig bug
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const config: HardhatUserConfig = {
// web3 functions
w3f: {
rootDir: "./web3-functions",
debug: false,
networks: ["hardhat", "mumbai"], //(multiChainProvider) injects provider for these networks
},
// hardhat-deploy
namedAccounts: {
deployer: {
default: 0,
},
},
defaultNetwork: "liskSepolia",

networks: {
hardhat: {
forking: {
url: `https://polygon-mumbai.g.alchemy.com/v2/${ALCHEMY_ID}`,
blockNumber: 35241432,
},
},

ethereum: {
accounts: PK ? [PK] : [],
chainId: 1,
url: `https://eth-mainnet.alchemyapi.io/v2/${ALCHEMY_ID}`,
},
mumbai: {
accounts: PK ? [PK] : [],
chainId: 80001,
url: `https://polygon-mumbai.g.alchemy.com/v2/${ALCHEMY_ID}`,
},
polygon: {
accounts: PK ? [PK] : [],
chainId: 137,
url: "https://polygon-rpc.com",
},
liskSepolia: {
accounts: PK ? [PK] : [],
chainId: 4202,
url: "https://rpc.sepolia-api.lisk.com",
},
lisk: {
accounts: PK ? [PK] : [],
chainId: 1135,
url: "https://rpc.api.lisk.com",
},
},

solidity: {
compilers: [
{
version: "0.8.18",
settings: {
optimizer: { enabled: true, runs: 200 },
},
},
],
},

typechain: {
outDir: "typechain",
target: "ethers-v5",
},

// hardhat-deploy
verify: {
etherscan: {
apiKey: ETHERSCAN_API_KEY ? ETHERSCAN_API_KEY : "",
},
},
};

export default config;
67 changes: 67 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{
"name": "lens-ai-poc",
"version": "0.0.1",
"description": "Smart Oracle that pushes prices online every hour or when the price difference with the last price is greater or equal than 2%",
"scripts": {
"build": "yarn install && yarn compile && npx tsc",
"clean": "yarn hardhat clean && rm -rf node_modules && rm -rf dist",
"compile": "npx hardhat compile --force",
Incede marked this conversation as resolved.
Show resolved Hide resolved
"deploy": "npx hardhat deploy",
"test": "npx hardhat test --network liskSepolia",
"format": "prettier --write '*/**/*.*{js,json,md,ts}'",
"format:check": "prettier --check '*/**/*.*{js,json,md,ts}'",
"tinker": "npx hardhat run scripts/tinker.ts",
"lint": "eslint --cache . && yarn lint:sol",
"lint:ts": "eslint -c .eslintrc.json --ext \"**/*.ts\" \"**/*.test.ts\"",
"lint:sol": "solhint 'contracts/**/*.sol'",
"postinstall": "yarn husky install",
"w3f:deploy": "npx w3f deploy web3-functions/index.ts",
"w3f:test": "npx w3f test web3-functions/index.ts --logs --chain-id=4202"
},
"license": "ISC",
"devDependencies": {
"@ethersproject/providers": "5.7.2",
"@gelatonetwork/automate-sdk": "^2.7.0-beta",
"@gelatonetwork/web3-functions-sdk": "^2.0.3",
"@nomicfoundation/hardhat-chai-matchers": "1.0.6",
"@nomicfoundation/hardhat-network-helpers": "^1.0.8",
"@nomiclabs/hardhat-ethers": "2.2.3",
"@openzeppelin/contracts-upgradeable": "4.8.3",
"@tsconfig/recommended": "1.0.2",
"@typechain/ethers-v5": "^10.2.1",
"@typechain/hardhat": "6.1.6",
"@types/chance": "1.1.3",
"@types/mocha": "^10.0.1",
"@types/node": "16.7.10",
"@types/uuid": "9.0.1",
"@typescript-eslint/eslint-plugin": "4.30.0",
"@typescript-eslint/parser": "4.30.0",
"chai": "4.3.7",
"dotenv": "10.0.0",
"eslint": "7.32.0",
"eslint-config-prettier": "8.3.0",
"eslint-plugin-prettier": "4.0.0",
"ethers": "5.7.2",
"hardhat": "2.14.0",
"hardhat-contract-sizer": "2.8.0",
"hardhat-deploy": "0.11.29",
"husky": "8.0.3",
"lint-staged": "11.1.2",
"prettier": "2.8.8",
"prettier-plugin-solidity": "1.1.3",
"rxjs": "7.8.1",
"solhint": "3.4.1",
"solhint-plugin-prettier": "0.0.5",
"ts-node": "10.9.1",
"typechain": "^8.1.1",
"typescript": "5.0.4"
},
"lint-staged": {
"*.*{js,json,md,ts,yml,yaml}": "prettier --write",
"*.*{ts,js}": "eslint -c .eslintrc.json"
},
"dependencies": {
"@openzeppelin/contracts": "^4.9.0",
"@redstone-finance/evm-connector": "^0.0.22"
}
}
62 changes: 62 additions & 0 deletions web3-functions/index.ts
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requirements are:

  • Update LSK/USD and USDT/USD pairs if price deviation is more than 0.5%
  • Update price every 6h if there is no price update before

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Web3Function.onRun could be used to prevent price update (eg. if price deviation is less than 0.5%) but not sure if there is a way to trigger w3f from the code.

Gelato dashboard has the option to trigger w3f function based on time interval or onchain events. So one way to achieve the 1st bullet point would be deploy a contract that checks and compares the prices from our oracle and source and triggers an onchain event that could be consumed by Gelato API.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we use like it's currently implemented

const livePrice: BigNumber = await wrappedOracle.getLivePrice();
const storedPrice: BigNumber = await wrappedOracle.getStoredPrice();

to get live and stored price and compare them to see if update is needed?

For 6 hours interval, we'll probably need to store timestamp of last price update and compare it each time w3f is executed to check if it's more than 6 hours from last price update.

w3f will always be executed every 30 or 60 seconds (we still need to decide on this).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay I understand what you mean now. Your previous comment implied that the code must trigger w3f once certain conditions are met.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

W3f will be executed every 30 or 60 seconds.
Logic inside it which creates a transaction to update token pair price will be triggered when certain conditions are met.

Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {
Web3Function,
Web3FunctionContext,
} from "@gelatonetwork/web3-functions-sdk";
import { BigNumber, Contract } from "ethers";
import { WrapperBuilder } from "@redstone-finance/evm-connector";

const ORACLE_ABI = [
"function getStoredPrice() public view returns (uint256)",
"function getLivePrice() public view returns (uint256)",
"function getPriceDeviation() external view returns (uint)",
"function decimals() external pure returns (uint8)",
"function updatePrice() public",
];

Web3Function.onRun(async (context: Web3FunctionContext) => {
const { userArgs, multiChainProvider } = context;

const provider = multiChainProvider.default();

const oracleAddress = userArgs.oracleAddress as string;
const oracle = new Contract(oracleAddress, ORACLE_ABI, provider);

// Wrap contract with redstone data service
const wrappedOracle = WrapperBuilder.wrap(oracle).usingDataService(
{
dataServiceId: "redstone-rapid-demo",
uniqueSignersCount: 1,
dataFeeds: ["LSK"],
disablePayloadsDryRun: true,
},
["https://d33trozg86ya9x.cloudfront.net"]
);

// Retrieve stored & live prices
const decimals = await wrappedOracle.decimals();
const livePrice: BigNumber = await wrappedOracle.getLivePrice();
const storedPrice: BigNumber = await wrappedOracle.getStoredPrice();
console.log(`Live price: ${livePrice.toString()}`);
console.log(`Stored price: ${storedPrice.toString()}`);

// Check price deviation
const deviation: BigNumber = await wrappedOracle.getPriceDeviation();
const deviationPrct = (deviation.toNumber() / 10 ** decimals) * 100;
console.log(`Deviation: ${deviationPrct.toFixed(2)}%`);

// Only update price if deviation is above 0.2%
const minDeviation = 0.2;
if (deviationPrct < minDeviation) {
return {
canExec: false,
message: `No update: price deviation too small`,
};
}

// Craft transaction to update the price on-chain
const { data } = await wrappedOracle.populateTransaction.updatePrice();
return {
canExec: true,
callData: [{ to: oracleAddress, data: data as string }],
};
});
10 changes: 10 additions & 0 deletions web3-functions/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"web3FunctionVersion": "2.0.0",
"runtime": "js-1.0",
"memory": 128,
"timeout": 30,
"userArgs": {
"currency": "string",
"oracleAddress": "string"
}
}
4 changes: 4 additions & 0 deletions web3-functions/userArgs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"currency":"LSK",
"oracleAddress":"0x198549B829ba2Cc8c203247f25c3b3bC33df6304"
}