├── .husky ├── .gitignore └── pre-commit ├── .solhintignore ├── docs ├── themes │ └── markdown │ │ ├── error.hbs │ │ ├── event.hbs │ │ ├── function.hbs │ │ ├── modifier.hbs │ │ ├── variable.hbs │ │ ├── user-defined-value-type.hbs │ │ ├── page.hbs │ │ ├── contract.hbs │ │ ├── enum.hbs │ │ ├── struct.hbs │ │ └── common.hbs ├── .vuepress │ ├── components │ │ ├── OtherComponent.vue │ │ ├── Foo │ │ │ └── Bar.vue │ │ └── demo-component.vue │ ├── styles │ │ ├── index.styl │ │ └── palette.styl │ ├── enhanceApp.js │ └── config.js ├── config │ └── README.md ├── guide │ ├── using-vue.md │ └── README.md └── index.md ├── declare.d.ts ├── .gitattributes ├── .prettierignore ├── scripts ├── copy_deployment_json.sh ├── pack.sh ├── build.sh ├── dataEncoder.ts ├── L1StandardBridge.ts ├── syncProxyABI.sh ├── logger.ts ├── config │ ├── mainnet.config.ts │ ├── startup.kepler.json │ └── startup.mainnet.json ├── upgrade.ts ├── verifyUpgrade.ts ├── deploy.ts ├── abi.ts ├── testOpTokenBridge.ts ├── seed.ts └── setup.ts ├── echidna.config.ci.yml ├── .env_template ├── .editorconfig ├── audits ├── SubQuery_Pte_Ltd_16022022SCAudit_Report.pdf ├── [SubQuery PTE. LTD. 14.06.2022] SC_Audit_Report.pdf └── SlowMist Audit Report - subquery-network-contracts.pdf ├── contracts ├── external │ └── ProxyAdmin.sol ├── interfaces │ ├── IDisputeManager.sol │ ├── IPermissionedExchange.sol │ ├── IVesting.sol │ ├── IConsumerRegistry.sol │ ├── ISQToken.sol │ ├── IPriceOracle.sol │ ├── IInflationController.sol │ ├── IServiceAgreement.sol │ ├── ISQTGift.sol │ ├── IEraManager.sol │ ├── IPurchaseOfferMarket.sol │ ├── IIndexerRegistry.sol │ ├── IServiceAgreementRegistry.sol │ ├── IRewardsPool.sol │ ├── ISettings.sol │ ├── IStakingAllocation.sol │ ├── IStakingManager.sol │ ├── IRewardsStaking.sol │ ├── IPlanManager.sol │ ├── IConsumer.sol │ ├── IRewardsDistributor.sol │ ├── IProjectRegistry.sol │ ├── IStaking.sol │ └── IRewardsBooster.sol ├── root │ ├── IInflationDestination.sol │ ├── SQToken.sol │ ├── VTSQToken.sol │ └── OpDestination.sol ├── utils │ ├── SQParameter.sol │ ├── StakingUtil.sol │ └── MathUtil.sol ├── mocks │ ├── MockInflationDestination2.sol │ ├── SUSD.sol │ └── MockInflationDestination.sol ├── l2 │ ├── L2SQToken.sol │ ├── UniswapPriceOracle.sol │ └── EraManager.sol ├── Constants.sol ├── Settings.sol ├── VSQToken.sol ├── SQTRedeem.sol ├── PriceOracle.sol ├── ConsumerRegistry.sol └── TokenExchange.sol ├── publish ├── vesting.json └── ABI │ ├── Settings.json │ ├── ProxyAdmin.json │ └── VSQToken.json ├── src ├── index.ts ├── deployments.ts ├── rootSdk.ts ├── networks.ts ├── contracts.ts └── sdk.ts ├── test ├── fixtures │ ├── plan.yaml │ ├── settings.yaml │ ├── indexer.yaml │ ├── booster.yaml │ ├── nfts_testnet.yaml │ ├── projects.yaml │ ├── testnet.yaml │ ├── llm_project.yaml │ ├── nfts.yaml │ ├── rpc_autonity_piccadilly.yaml │ └── rpc_projects.yaml ├── constants.ts ├── e2e │ └── startup.e2e.ts ├── setup.ts ├── SQToken.test.ts ├── ConsumerRegistry.test.ts ├── VSQToken.test.ts ├── PriceOracle.test.ts ├── Vesting.test-data.ts ├── EraManager.test.ts └── SQTGift.test.ts ├── .solhint.json ├── .yarnrc.yml ├── Cargo.toml ├── tsconfig-build.json ├── .prettierrc ├── echidna.config.yml ├── .gitignore ├── tsconfig.json ├── .eslintrc ├── .github └── workflows │ ├── pr.yml │ ├── docs-deploy.yml │ ├── release.yml │ ├── prerelease.yml │ ├── upgrade.yml │ ├── deploy.yml │ └── fuzz.yml ├── CHANGELOG.md └── test-fuzz ├── AirdropperEchidnaTest.sol └── PermissionedExchangeEchidnaTest.sol /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.solhintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /docs/themes/markdown/error.hbs: -------------------------------------------------------------------------------- 1 | {{>common}} 2 | -------------------------------------------------------------------------------- /docs/themes/markdown/event.hbs: -------------------------------------------------------------------------------- 1 | {{>common}} 2 | -------------------------------------------------------------------------------- /docs/themes/markdown/function.hbs: -------------------------------------------------------------------------------- 1 | {{>common}} 2 | -------------------------------------------------------------------------------- /docs/themes/markdown/modifier.hbs: -------------------------------------------------------------------------------- 1 | {{>common}} 2 | -------------------------------------------------------------------------------- /docs/themes/markdown/variable.hbs: -------------------------------------------------------------------------------- 1 | {{>common}} 2 | -------------------------------------------------------------------------------- /declare.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@openzeppelin/test-helpers'; 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | publish/contracts/*.json linguist-generated=true 2 | -------------------------------------------------------------------------------- /docs/themes/markdown/user-defined-value-type.hbs: -------------------------------------------------------------------------------- 1 | {{>common}} 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /docs 2 | /node_modules 3 | /.yarn 4 | /build 5 | /cache 6 | /publish -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /scripts/copy_deployment_json.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cp publish/*.json build/build/publish/ 4 | -------------------------------------------------------------------------------- /docs/themes/markdown/page.hbs: -------------------------------------------------------------------------------- 1 | {{#each items}} 2 | {{#hsection}} 3 | {{>item}} 4 | {{/hsection}} 5 | 6 | {{/each}} 7 | -------------------------------------------------------------------------------- /docs/.vuepress/components/OtherComponent.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /echidna.config.ci.yml: -------------------------------------------------------------------------------- 1 | #maximum time between generated txs; default is one week 2 | maxTimeDelay: 31556952 # approximately 1 year 3 | -------------------------------------------------------------------------------- /.env_template: -------------------------------------------------------------------------------- 1 | CHILD_ENDPOINT=http://127.0.0.1:8545 2 | ROOT_ENDPOINT 3 | PK= 4 | ETHERSCAN_API_KEY=sjdkkdndnkdssnj 5 | POLYGONSCAN_API_KEY= -------------------------------------------------------------------------------- /docs/themes/markdown/contract.hbs: -------------------------------------------------------------------------------- 1 | {{>common}} 2 | 3 | {{#each items}} 4 | {{#hsection}} 5 | {{>item}} 6 | {{/hsection}} 7 | 8 | {{/each}} 9 | -------------------------------------------------------------------------------- /scripts/pack.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | if [[ ! -d "build/build" ]]; then 3 | echo "build folder not found" 4 | fi 5 | cd build/build 6 | npm pack 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_size = 4 3 | indent_style = space 4 | 5 | [tsconfig*.json] 6 | indent_size = 2 7 | 8 | [*.yaml] 9 | indent_size = 2 10 | -------------------------------------------------------------------------------- /audits/SubQuery_Pte_Ltd_16022022SCAudit_Report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subquery/network-contracts/HEAD/audits/SubQuery_Pte_Ltd_16022022SCAudit_Report.pdf -------------------------------------------------------------------------------- /audits/[SubQuery PTE. LTD. 14.06.2022] SC_Audit_Report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subquery/network-contracts/HEAD/audits/[SubQuery PTE. LTD. 14.06.2022] SC_Audit_Report.pdf -------------------------------------------------------------------------------- /audits/SlowMist Audit Report - subquery-network-contracts.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subquery/network-contracts/HEAD/audits/SlowMist Audit Report - subquery-network-contracts.pdf -------------------------------------------------------------------------------- /docs/themes/markdown/enum.hbs: -------------------------------------------------------------------------------- 1 | {{>common}} 2 | 3 | ```solidity 4 | enum {{name}} { 5 | {{#each members}} 6 | {{name}}{{#unless @last}},{{/unless}} 7 | {{/each}} 8 | } 9 | ``` 10 | -------------------------------------------------------------------------------- /docs/.vuepress/styles/index.styl: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom Styles here. 3 | * 4 | * ref:https://v1.vuepress.vuejs.org/config/#index-styl 5 | */ 6 | 7 | .home .hero img 8 | max-width 450px!important 9 | -------------------------------------------------------------------------------- /docs/config/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: auto 3 | --- 4 | 5 | # Config 6 | 7 | ## foo 8 | 9 | - Type: `string` 10 | - Default: `/` 11 | 12 | ## bar 13 | 14 | - Type: `string` 15 | - Default: `/` 16 | -------------------------------------------------------------------------------- /docs/themes/markdown/struct.hbs: -------------------------------------------------------------------------------- 1 | {{>common}} 2 | 3 | ```solidity 4 | struct {{name}} { 5 | {{#each members}} 6 | {{{typeName.typeDescriptions.typeString}}} {{name}}; 7 | {{/each}} 8 | } 9 | ``` 10 | -------------------------------------------------------------------------------- /contracts/external/ProxyAdmin.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | import '@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol'; 7 | -------------------------------------------------------------------------------- /docs/.vuepress/styles/palette.styl: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom palette here. 3 | * 4 | * ref:https://v1.vuepress.vuejs.org/zh/config/#palette-styl 5 | */ 6 | 7 | $accentColor = #3eaf7c 8 | $textColor = #2c3e50 9 | $borderColor = #eaecef 10 | $codeBgColor = #282c34 11 | -------------------------------------------------------------------------------- /publish/vesting.json: -------------------------------------------------------------------------------- 1 | { 2 | "testnet": [ 3 | "0x4D1d1d932388E5d3015BEf071FC994A370892DEb", 4 | "0xA7b88D860cE66D2849782F0fD1413E0b1D6089f0", 5 | "0x901694bF30c351E233dE64661c52812C3913F028" 6 | ], 7 | "kepler": [], 8 | "mainnet": [] 9 | } 10 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | export * from './sdk'; 5 | export * from './rootSdk'; 6 | export * from './typechain'; 7 | export * from './types'; 8 | export * from './networks'; 9 | -------------------------------------------------------------------------------- /docs/.vuepress/components/Foo/Bar.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | -------------------------------------------------------------------------------- /contracts/interfaces/IDisputeManager.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | interface IDisputeManager { 7 | function isOnDispute(address indexer) external returns (bool); 8 | } 9 | -------------------------------------------------------------------------------- /contracts/root/IInflationDestination.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | interface IInflationDestination { 7 | function afterReceiveInflatedTokens(uint256 tokenAmount) external; 8 | } 9 | -------------------------------------------------------------------------------- /docs/.vuepress/components/demo-component.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | -------------------------------------------------------------------------------- /test/fixtures/plan.yaml: -------------------------------------------------------------------------------- 1 | - kind: PlanTemplate 2 | period: 1209600 3 | dailyReqCap: 2000000 4 | rateLimit: 250 5 | metadata: 6 | planName: Startup RPC Plan 7 | period: 14 days 8 | description: Ideal for small or growing teams looking to explore and build without significant upfront costs. 9 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "compiler-version": ["error", "^0.8.0"], 5 | "func-visibility": ["warn", { "ignoreConstructors": true }], 6 | "reason-string": ["warn", { "maxLength": 80 }], 7 | "quotes": false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /contracts/interfaces/IPermissionedExchange.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | interface IPermissionedExchange { 7 | function addQuota(address _token, address _account, uint256 _amount) external; 8 | } 9 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | changesetBaseRefs: 2 | - main 3 | - origin/main 4 | 5 | enableImmutableInstalls: false 6 | 7 | enableProgressBars: false 8 | 9 | nodeLinker: node-modules 10 | 11 | npmAuthToken: "${NPM_TOKEN:-}" 12 | 13 | npmPublishRegistry: "https://registry.npmjs.org" 14 | 15 | yarnPath: .yarn/releases/yarn-4.10.3.cjs 16 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "subql-contracts" 3 | version = "0.100.0" 4 | edition = "2021" 5 | include = ["/rust", "/publish"] 6 | 7 | [lib] 8 | path = "rust/lib.rs" 9 | 10 | [dependencies] 11 | ethers = { git = "https://github.com/gakonst/ethers-rs.git", tag = "ethers-v2.0.7" } 12 | paste = "1.0" 13 | serde_json = "1.0" 14 | -------------------------------------------------------------------------------- /contracts/utils/SQParameter.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity ^0.8.15; 5 | 6 | abstract contract SQParameter { 7 | /// @notice Emitted when parameter change. 8 | event Parameter(string name, bytes value); 9 | } 10 | -------------------------------------------------------------------------------- /contracts/mocks/MockInflationDestination2.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | pragma solidity 0.8.15; 4 | 5 | import { IInflationDestination } from '../root/IInflationDestination.sol'; 6 | 7 | contract MockInflationDestination2 { 8 | constructor() {} 9 | } 10 | -------------------------------------------------------------------------------- /contracts/interfaces/IVesting.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity ^0.8.15; 5 | 6 | interface IVesting { 7 | function allocations(address _account) external view returns (uint256); 8 | 9 | function claimed(address _account) external view returns (uint256); 10 | } 11 | -------------------------------------------------------------------------------- /contracts/interfaces/IConsumerRegistry.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity ^0.8.15; 5 | 6 | interface IConsumerRegistry { 7 | // check the consumer's controller account 8 | function isController(address consumer, address controller) external view returns (bool); 9 | } 10 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | mkdir -p build/build/publish 6 | cp -r src/* build/ 7 | cp -r publish build/ 8 | 9 | cp -r artifacts build/ 10 | cp package.json build/build/ 11 | cp tsconfig-build.json build/tsconfig.json 12 | 13 | cd build || exit 14 | 15 | tsc -b 16 | 17 | sed -i -e '/"prepare"/d' build/package.json 18 | 19 | cp publish/*.json build/publish/ -------------------------------------------------------------------------------- /contracts/l2/L2SQToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import { L2StandardERC20 } from '@eth-optimism/contracts/standards/L2StandardERC20.sol'; 5 | 6 | contract L2SQToken is L2StandardERC20 { 7 | constructor( 8 | address _l2Bridge, 9 | address _l1Token 10 | ) L2StandardERC20(_l2Bridge, _l1Token, 'SubQueryToken', 'SQT') {} 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "esModuleInterop": true, 5 | "lib": ["ES2018"], 6 | "module": "CommonJS", 7 | "moduleResolution": "node", 8 | "outDir": "build", 9 | "resolveJsonModule": true, 10 | "skipLibCheck": true, 11 | "strict": true, 12 | "target": "ES2018" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /contracts/interfaces/ISQToken.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | interface ISQToken { 7 | function mint(address destination, uint256 amount) external; 8 | 9 | function burn(uint256 amount) external; 10 | 11 | function burnFrom(address account, uint256 amount) external; 12 | } 13 | -------------------------------------------------------------------------------- /contracts/Constants.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | uint256 constant PER_MILL = 1e6; 7 | uint256 constant PER_BILL = 1e9; 8 | uint256 constant PER_TRILL = 1e12; 9 | address constant ZERO_ADDRESS = address(0); 10 | uint256 constant MAX_UINT256 = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; 11 | -------------------------------------------------------------------------------- /docs/.vuepress/enhanceApp.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Client app enhancement file. 3 | * 4 | * https://v1.vuepress.vuejs.org/guide/basic-config.html#app-level-enhancements 5 | */ 6 | 7 | export default ({ 8 | Vue, // the version of Vue being used in the VuePress app 9 | options, // the options for the root Vue instance 10 | router, // the router instance for the app 11 | siteData // site metadata 12 | }) => { 13 | // ...apply enhancements for the site. 14 | } 15 | -------------------------------------------------------------------------------- /scripts/dataEncoder.ts: -------------------------------------------------------------------------------- 1 | import { ContractSDK } from '../src'; 2 | import setup from './setup'; 3 | 4 | const main = async () => { 5 | const proxy = process.argv[2]; 6 | const newLogic = process.argv[3]; 7 | const { wallet } = await setup(['', '', '--testnet']); 8 | const sdk = await ContractSDK.create(wallet); 9 | const data = sdk.proxyAdmin.interface.encodeFunctionData('upgrade', [proxy, newLogic]); 10 | console.log(data); 11 | }; 12 | 13 | main(); 14 | -------------------------------------------------------------------------------- /contracts/interfaces/IPriceOracle.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | interface IPriceOracle { 7 | function getAssetPrice(address fromToken, address toToken) external view returns (uint256); 8 | function convertPrice( 9 | address fromToken, 10 | address toToken, 11 | uint256 amount 12 | ) external view returns (uint256); 13 | } 14 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "trailingComma": "es5", 4 | "tabWidth": 4, 5 | "singleQuote": true, 6 | "bracketSpacing": true, 7 | "overrides": [ 8 | { 9 | "files": "*.sol", 10 | "options": { 11 | "printWidth": 100, 12 | "tabWidth": 4, 13 | "useTabs": false, 14 | "singleQuote": true, 15 | "bracketSpacing": true 16 | } 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /src/deployments.ts: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | import { ContractDeployment, SubqueryNetwork } from './types'; 5 | 6 | import mainnetDeployment from './publish/mainnet.json'; 7 | import testnetDeployment from './publish/testnet.json'; 8 | 9 | export const DEPLOYMENT_DETAILS: Partial> = { 10 | mainnet: mainnetDeployment, 11 | testnet: testnetDeployment, 12 | }; 13 | -------------------------------------------------------------------------------- /echidna.config.yml: -------------------------------------------------------------------------------- 1 | #select the mode to test, which can be property, assertion, overflow, exploration, optimization 2 | testMode: assertion 3 | #maximum time between generated txs; default is one week 4 | maxTimeDelay: 31556952 # approximately 1 year 5 | #solcArgs allows special args to solc 6 | solcArgs: '--optimize --optimize-runs 200' 7 | #testLimit is the number of test sequences to run 8 | testLimit: 50000 9 | #seqLen defines how many transactions are in a test sequence 10 | seqLen: 100 11 | corpus-dir: 'corpus' 12 | -------------------------------------------------------------------------------- /contracts/interfaces/IInflationController.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | interface IInflationController { 7 | function inflationRate() external view returns (uint256); 8 | 9 | function setInflationRate(uint256 _inflationRateBP) external; 10 | 11 | function setInflationDestination(address _inflationDestination) external; 12 | 13 | function mintInflatedTokens() external; 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | cache/ 4 | .env 5 | .DS_Store 6 | 7 | # yarn v2 8 | .yarn/cache 9 | .yarn/unplugged 10 | .yarn/build-state.yml 11 | .yarn/install-state.gz 12 | .pnp.* 13 | publish/local.json 14 | publish/contracts 15 | src/typechain 16 | 17 | /coverage 18 | coverage.json 19 | 20 | artifacts 21 | 22 | Flattened.sol 23 | 24 | /target 25 | **/*.rs.bk 26 | Cargo.lock 27 | pids 28 | run 29 | docs/.vuepress/dist/ 30 | docs/contracts/ 31 | 32 | crytic-export/ 33 | corpus/ 34 | package-lock.json 35 | 36 | .vscode/settings.json 37 | -------------------------------------------------------------------------------- /contracts/interfaces/IServiceAgreement.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | // -- Data -- 7 | 8 | /** 9 | * @dev closed service agreement information 10 | */ 11 | struct ClosedServiceAgreementInfo { 12 | address consumer; 13 | address indexer; 14 | bytes32 deploymentId; 15 | uint256 lockedAmount; 16 | uint256 startDate; 17 | uint256 period; 18 | uint256 planId; 19 | uint256 planTemplateId; 20 | } 21 | -------------------------------------------------------------------------------- /contracts/mocks/SUSD.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; 7 | 8 | // For testing purposes only 9 | contract SUSD is ERC20 { 10 | constructor(uint256 initialSupply) ERC20('Stable Coin', 'SUSD') { 11 | _mint(msg.sender, initialSupply); 12 | } 13 | 14 | function decimals() public view virtual override returns (uint8) { 15 | return 6; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /docs/themes/markdown/common.hbs: -------------------------------------------------------------------------------- 1 | {{#if natspec.dev}} 2 | {{{natspec.dev}}} 3 | {{/if}} 4 | 5 | {{h}} {{name}} 6 | 7 | {{{natspec.notice}}} 8 | 9 | {{#if signature}} 10 | ```solidity 11 | {{{signature}}} 12 | ``` 13 | {{/if}} 14 | 15 | {{#if natspec.params}} 16 | | Name | Type | Description | 17 | | ---- | ---- | ----------- | 18 | {{#each params}} 19 | | {{name}} | {{type}} | {{{joinLines natspec}}} | 20 | {{/each}} 21 | {{/if}} 22 | 23 | {{#if natspec.returns}} 24 | Return: {{#each returns}} {{type}} -> {{{joinLines natspec}}} {{/each}} 25 | {{/if}} 26 | 27 | -------------------------------------------------------------------------------- /scripts/L1StandardBridge.ts: -------------------------------------------------------------------------------- 1 | export const l1StandardBridge = { 2 | // Ethereum 3 | mainnet: { 4 | innerAddress: '0x3f3c0f6bc115e698e35038e1759e9c31032e590c', 5 | address: '0x3154Cf16ccdb4C6d922629664174b904d80F2C35', 6 | bytecodeHash: '', 7 | lastUpdate: '', 8 | }, 9 | // Ethereum Sepolia 10 | testnet: { 11 | innerAddress: '0xacd2e50b877ab12924a766b8ddbd9272402c7d72 ', 12 | address: '0xfd0Bf71F60660E2f608ed56e1659C450eB113120', 13 | bytecodeHash: '', 14 | lastUpdate: '', 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /scripts/syncProxyABI.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | #PROXY_ADMIN_PATH=artifacts/contracts/ProxyAdmin.sol 6 | #mkdir -p $PROXY_ADMIN_PATH 7 | #cp node_modules/@openzeppelin/contracts/build/contracts/ProxyAdmin.json $PROXY_ADMIN_PATH/ProxyAdmin.json 8 | # 9 | #TRANSPARENT_UPGRADEABLE_PROXY_PATH=artifacts/contracts/TransparentUpgradeableProxy.sol 10 | #mkdir -p $TRANSPARENT_UPGRADEABLE_PROXY_PATH 11 | #cp node_modules/@openzeppelin/contracts/build/contracts/TransparentUpgradeableProxy.json $TRANSPARENT_UPGRADEABLE_PROXY_PATH/TransparentUpgradeableProxy.json 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "esModuleInterop": true, 5 | "lib": ["ES2020"], 6 | "module": "CommonJS", 7 | "moduleResolution": "node", 8 | "outDir": "build", 9 | "resolveJsonModule": true, 10 | "skipLibCheck": true, 11 | "strict": false, 12 | "target": "ES2020", 13 | "rootDirs": ["src", "."], 14 | "baseUrl": ".", 15 | }, 16 | "include": ["src", "test", "test/e2e", "scripts"], 17 | "files": ["./hardhat.config.ts"], 18 | } 19 | -------------------------------------------------------------------------------- /docs/guide/using-vue.md: -------------------------------------------------------------------------------- 1 | # Using Vue in Markdown 2 | 3 | ## Browser API Access Restrictions 4 | 5 | Because VuePress applications are server-rendered in Node.js when generating static builds, any Vue usage must conform to the [universal code requirements](https://ssr.vuejs.org/en/universal.html). In short, make sure to only access Browser / DOM APIs in `beforeMount` or `mounted` hooks. 6 | 7 | If you are using or demoing components that are not SSR friendly (for example containing custom directives), you can wrap them inside the built-in `` component: 8 | 9 | ## 10 | -------------------------------------------------------------------------------- /contracts/interfaces/ISQTGift.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | import '@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol'; 7 | 8 | struct GiftSeries { 9 | uint256 maxSupply; 10 | uint256 totalSupply; 11 | bool active; 12 | string tokenURI; 13 | } 14 | 15 | struct Gift { 16 | uint256 seriesId; 17 | } 18 | 19 | interface ISQTGift is IERC721Upgradeable { 20 | function getSeries(uint256 tokenId) external view returns (uint256); 21 | } 22 | -------------------------------------------------------------------------------- /contracts/utils/StakingUtil.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity ^0.8.15; 5 | 6 | import '../interfaces/IStaking.sol'; 7 | 8 | /** 9 | * @title Staking helper utils. 10 | * @dev 11 | */ 12 | library StakingUtil { 13 | function currentStaking( 14 | StakingAmount memory amount, 15 | uint256 era 16 | ) internal pure returns (uint256) { 17 | if (amount.era < era) { 18 | return amount.valueAfter; 19 | } 20 | return amount.valueAt; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /scripts/logger.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@subql/utils'; 2 | import Pino from 'pino'; 3 | 4 | /* eslint-disable no-unused-vars */ 5 | export enum TextColor { 6 | RED = 31, 7 | GREEN, 8 | YELLOW, 9 | BLUE, 10 | MAGENTA, 11 | CYAN, 12 | } 13 | 14 | export function colorText(text: string, color = TextColor.CYAN): string { 15 | return `\u001b[${color}m${text}\u001b[39m`; 16 | } 17 | 18 | const logger = new Logger({ level: 'info', outputFormat: 'colored', nestedKey: 'payload' }); 19 | 20 | export function getLogger(category: string): Pino.Logger { 21 | return logger.getLogger(category); 22 | } 23 | -------------------------------------------------------------------------------- /scripts/config/mainnet.config.ts: -------------------------------------------------------------------------------- 1 | const config = { 2 | multiSig: { 3 | root: { 4 | foundation: '0x623D1426f5F45D39A1D9EbD3A5f6abeE0c8eC469', 5 | teamAllocation: '0x32aB17a7d990F4afA8AD01cbFcbf49c26CFC0494', 6 | foundationAllocation: '0x16F94a7719994303125bc7cEB2Dac0Cca2e9b787', 7 | }, 8 | child: { 9 | foundation: '0x31E99bdA5939bA2e7528707507b017f43b67F89B', 10 | treasury: '0xd043807A0f41EE95fD66A523a93016a53456e79B', 11 | council: '0xDD93Add934dCc40b54f3d701C5666CFf1C9FD0Df', 12 | }, 13 | }, 14 | }; 15 | 16 | export default config; 17 | -------------------------------------------------------------------------------- /test/fixtures/settings.yaml: -------------------------------------------------------------------------------- 1 | - kind: BoosterRewardSetting 2 | queryRewardRatios: 3 | - projectType: 0 # subquery project 4 | ratio: 500000 # 50%, denominator: 1e9 5 | - projectType: 1 # subquery project 6 | ratio: 900000 # 50%, denominator: 1e9 7 | issuancePerBlock: '6341958400000000000' # 6.34 8 | - kind: ProjectTypeCreatorRestricted 9 | value: 10 | - projectType: 0 # subquery project 11 | restricted: true 12 | - projectType: 1 # rpc 13 | restricted: true 14 | - projectType: 2 # dict 15 | restricted: true 16 | - projectType: 3 # subgraqh 17 | restricted: true 18 | -------------------------------------------------------------------------------- /contracts/interfaces/IEraManager.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | interface IEraManager { 7 | function eraStartTime() external view returns (uint256); 8 | 9 | function eraPeriod() external view returns (uint256); 10 | 11 | function eraNumber() external view returns (uint256); 12 | 13 | function safeUpdateAndGetEra() external returns (uint256); 14 | 15 | function timestampToEraNumber(uint256 timestamp) external view returns (uint256); 16 | 17 | function maintenance() external returns (bool); 18 | } 19 | -------------------------------------------------------------------------------- /test/fixtures/indexer.yaml: -------------------------------------------------------------------------------- 1 | - kind: Account 2 | name: indexer1 3 | seed: trick scale shop mountain any van develop blame sport grid equal garbage 4 | derive: /0 5 | - kind: Account 6 | name: controller1 7 | seed: trick scale shop mountain any van develop blame sport grid equal garbage 8 | derive: /2 9 | - kind: Faucet 10 | account: indexer1 11 | amounts: 12 | SQT: 10000 13 | Fee: 100 14 | - kind: Faucet 15 | account: controller1 16 | amounts: 17 | Fee: 100 18 | - kind: Indexer 19 | account: indexer1 20 | stake: 1000 21 | commissionRate: 0.2 22 | - kind: IndexerController 23 | indexer: indexer1 24 | controller: controller1 25 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": [ 4 | "eslint:recommended", 5 | "plugin:@typescript-eslint/recommended" 6 | ], 7 | "parserOptions": { 8 | "ecmaVersion": 2020, 9 | "sourceType": "module" 10 | }, 11 | "ignorePatterns": ["node_modules/", "build/", "artifacts/", "src/typechain/", "*.js", "docs/"], 12 | "rules": { 13 | "@typescript-eslint/no-var-requires": "off", 14 | "@typescript-eslint/no-unused-vars": ["warn", { "varsIgnorePattern": "^_" }] 15 | }, 16 | "overrides": [ 17 | { 18 | "files": ["src/*.ts", "test/**/*.ts"], 19 | "rules": {} 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /contracts/interfaces/IPurchaseOfferMarket.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | interface IPurchaseOfferMarket { 7 | function createPurchaseOffer( 8 | bytes32 _deploymentId, 9 | uint256 _planTemplateId, 10 | uint256 _deposit, 11 | uint16 _limit, 12 | uint256 _minimumAcceptHeight, 13 | uint256 _minimumStakingAmount, 14 | uint256 _expireDate 15 | ) external; 16 | 17 | function cancelPurchaseOffer(uint256 _offerId) external; 18 | 19 | function acceptPurchaseOffer(uint256 _offerId, bytes32 _poi) external; 20 | } 21 | -------------------------------------------------------------------------------- /contracts/interfaces/IIndexerRegistry.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | interface IIndexerRegistry { 7 | function isIndexer(address _address) external view returns (bool); 8 | 9 | function getController(address indexer) external view returns (address); 10 | 11 | function minimumStakingAmount() external view returns (uint256); 12 | 13 | function getCommissionRate(address indexer) external view returns (uint256); 14 | 15 | function setInitialCommissionRate(address indexer, uint256 rate) external; 16 | 17 | function setCommissionRate(uint256 rate) external; 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: PR 2 | on: 3 | pull_request: 4 | paths-ignore: 5 | 6 | jobs: 7 | pr: 8 | name: pr 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: install node v18 13 | uses: actions/setup-node@v1 14 | with: 15 | node-version: 18 16 | - run: corepack enable 17 | - run: yarn 18 | - name: clean cache 19 | run: yarn clean 20 | - name: build 21 | run: yarn build 22 | - name: lint 23 | run: yarn lint 24 | - name: test 25 | run: yarn test:all 26 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [0.10.1] - 2022-08-16 11 | 12 | ### Added 13 | 14 | - improve sdk init speed 15 | 16 | ## [0.10.0] - 2022-08-16 17 | 18 | ### Added 19 | 20 | - Release version for Kepler 21 | 22 | [Unreleased]: https://github.com/subquery/network-contracts/compare/v0.10.1...HEAD 23 | [0.10.1]: https://github.com/subquery/network-contracts/compare/v0.10.0...v0.10.1 24 | [0.10.0]: https://github.com/subquery/network-contracts/releases/tag/v0.10.0 25 | -------------------------------------------------------------------------------- /.github/workflows/docs-deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Doucments 2 | on: 3 | workflow_dispatch: 4 | 5 | jobs: 6 | build-and-deploy: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@master 11 | 12 | - name: Yarn 13 | run: corepack enable && yarn 14 | 15 | - name: Generate Contract Documents 16 | run: yarn docs:generate 17 | 18 | - name: Deploy documents to Github Page 19 | uses: jenkey2011/vuepress-deploy@master 20 | env: 21 | ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | BUILD_SCRIPT: yarn docs:build 23 | BUILD_DIR: docs/.vuepress/dist/ 24 | -------------------------------------------------------------------------------- /contracts/interfaces/IServiceAgreementRegistry.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | import './IServiceAgreement.sol'; 7 | 8 | interface IServiceAgreementRegistry { 9 | function getClosedServiceAgreement( 10 | uint256 agreementId 11 | ) external view returns (ClosedServiceAgreementInfo memory); 12 | 13 | function nextServiceAgreementId() external view returns (uint256); 14 | 15 | function createClosedServiceAgreement( 16 | ClosedServiceAgreementInfo memory agreement 17 | ) external returns (uint256); 18 | 19 | function hasOngoingClosedServiceAgreement( 20 | address indexer, 21 | bytes32 deploymentId 22 | ) external view returns (bool); 23 | } 24 | -------------------------------------------------------------------------------- /docs/guide/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | VuePress is composed of two parts: a [minimalistic static site generator](https://github.com/vuejs/vuepress/tree/master/packages/%40vuepress/core) with a Vue-powered [theming system](https://v1.vuepress.vuejs.org/theme/) and [Plugin API](https://v1.vuepress.vuejs.org/plugin/), and a [default theme](https://v1.vuepress.vuejs.org/theme/default-theme-config.html) optimized for writing technical documentation. It was created to support the documentation needs of Vue's own sub projects. 4 | 5 | Each page generated by VuePress has its own pre-rendered static HTML, providing great loading performance and is SEO-friendly. Once the page is loaded, however, Vue takes over the static content and turns it into a full Single-Page Application (SPA). Additional pages are fetched on demand as the user navigates around the site. 6 | -------------------------------------------------------------------------------- /test/fixtures/booster.yaml: -------------------------------------------------------------------------------- 1 | - kind: Account 2 | name: user1 3 | # address: 0x5e15cE35a3821e15d36988D9E0DD181c7c371A07 4 | pk: '' 5 | # Eth Private Rpc 6 | - kind: DeploymentBooster 7 | deploymentId: QmTeutqL8wQqzAdG8MNWMXxoz1LDkkRZkY6HEw1HbdT2um 8 | user: user1 9 | amount: 10000 10 | # Polkadot Staking 11 | - kind: DeploymentBooster 12 | deploymentId: QmNYsNZvM9XZuzkF3n6XcqFVxvMLfWYtEQHzszMFfNCkgt 13 | user: user1 14 | amount: 1000 15 | 16 | - kind: Account 17 | name: user2 18 | # address: 0x23ac7D57cC5cA16E12AC09BbC293b0495768a061 19 | pk: '' 20 | # Eth Private Rpc 21 | - kind: DeploymentBooster 22 | deploymentId: QmTeutqL8wQqzAdG8MNWMXxoz1LDkkRZkY6HEw1HbdT2um 23 | user: user2 24 | amount: 5000 25 | # Polkadot Staking 26 | - kind: DeploymentBooster 27 | deploymentId: QmNYsNZvM9XZuzkF3n6XcqFVxvMLfWYtEQHzszMFfNCkgt 28 | user: user2 29 | amount: 3000 30 | -------------------------------------------------------------------------------- /contracts/interfaces/IRewardsPool.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity ^0.8.15; 5 | 6 | interface IRewardsPool { 7 | function getReward( 8 | bytes32 deploymentId, 9 | uint256 era, 10 | address indexer 11 | ) external returns (uint256, uint256); 12 | 13 | function labor(bytes32 deploymentId, address indexer, uint256 amount) external; 14 | 15 | function collect(bytes32 deploymentId, address indexer) external; 16 | 17 | function collectEra(uint256 era, bytes32 deploymentId, address indexer) external; 18 | 19 | function batchCollectEra(uint256 era, address indexer) external; 20 | 21 | function isClaimed(uint256 era, address indexer) external returns (bool); 22 | 23 | function getUnclaimDeployments( 24 | uint256 era, 25 | address indexer 26 | ) external view returns (bytes32[] memory); 27 | } 28 | -------------------------------------------------------------------------------- /test/fixtures/nfts_testnet.yaml: -------------------------------------------------------------------------------- 1 | - kind: SQTGiftAllowList 2 | seriesId: 0 3 | list: 4 | - address: '0x5e15cE35a3821e15d36988D9E0DD181c7c371A07' 5 | amount: 1 6 | - address: '0x142811C55F47e6e5FDa281a522D42308527f7262' 7 | amount: 2 8 | - address: '0x6604aBf2E71C4d407C853e0AD62D9366D7Fe593A' 9 | amount: 3 10 | - kind: SQTGiftAllowList 11 | seriesId: 1 12 | list: 13 | - address: '0x5e15cE35a3821e15d36988D9E0DD181c7c371A07' 14 | amount: 1 15 | - address: '0x142811C55F47e6e5FDa281a522D42308527f7262' 16 | amount: 2 17 | - address: '0x6604aBf2E71C4d407C853e0AD62D9366D7Fe593A' 18 | amount: 3 19 | - kind: SQTGiftAllowList 20 | seriesId: 2 21 | list: 22 | - address: '0x5e15cE35a3821e15d36988D9E0DD181c7c371A07' 23 | amount: 1 24 | - kind: Account 25 | name: user1 26 | # address: 0x142811C55F47e6e5FDa281a522D42308527f7262 27 | pk: '' 28 | - kind: SQTGiftClaim 29 | seriesId: 0 30 | user: user1 31 | -------------------------------------------------------------------------------- /contracts/interfaces/ISettings.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | enum SQContracts { 7 | SQToken, 8 | Staking, 9 | StakingManager, 10 | IndexerRegistry, 11 | ProjectRegistry, 12 | EraManager, 13 | PlanManager, 14 | ServiceAgreementRegistry, 15 | RewardsDistributor, 16 | RewardsPool, 17 | RewardsStaking, 18 | RewardsHelper, 19 | InflationController, 20 | Vesting, 21 | DisputeManager, 22 | StateChannel, 23 | ConsumerRegistry, 24 | PriceOracle, 25 | Treasury, 26 | RewardsBooster, 27 | StakingAllocation 28 | } 29 | 30 | interface ISettings { 31 | function setBatchAddress(SQContracts[] calldata sq, address[] calldata _address) external; 32 | 33 | function setContractAddress(SQContracts sq, address _address) external; 34 | 35 | function getContractAddress(SQContracts sq) external view returns (address); 36 | } 37 | -------------------------------------------------------------------------------- /contracts/mocks/MockInflationDestination.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | pragma solidity 0.8.15; 4 | 5 | import '@openzeppelin/contracts/utils/introspection/ERC165.sol'; 6 | import { IInflationDestination } from '../root/IInflationDestination.sol'; 7 | 8 | contract MockInflationDestination is IInflationDestination, ERC165 { 9 | event HookCalled(); 10 | constructor() {} 11 | 12 | /** 13 | * @notice Check ERC165 interface 14 | * @param interfaceId interface ID 15 | * @return Result of support or not 16 | */ 17 | function supportsInterface( 18 | bytes4 interfaceId 19 | ) public view virtual override(ERC165) returns (bool) { 20 | return 21 | interfaceId == type(IInflationDestination).interfaceId || 22 | super.supportsInterface(interfaceId); 23 | } 24 | 25 | function afterReceiveInflatedTokens(uint256 tokenAmount) external { 26 | emit HookCalled(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /contracts/utils/MathUtil.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | library MathUtil { 7 | function max(uint256 a, uint256 b) internal pure returns (uint256) { 8 | return a > b ? a : b; 9 | } 10 | 11 | function min(uint256 x, uint256 y) internal pure returns (uint256) { 12 | return x > y ? y : x; 13 | } 14 | 15 | function divUp(uint256 x, uint256 y) internal pure returns (uint256) { 16 | return (x - 1) / y + 1; 17 | } 18 | 19 | function mulDiv(uint256 x, uint256 y, uint256 z) internal pure returns (uint256) { 20 | return (x * y) / z; 21 | } 22 | 23 | function sub(uint256 x, uint256 y) internal pure returns (uint256) { 24 | if (x < y) { 25 | return 0; 26 | } 27 | return x - y; 28 | } 29 | 30 | function diffOrZero(uint256 x, uint256 y) internal pure returns (uint256) { 31 | return (x > y) ? x - y : 0; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/interfaces/IStakingAllocation.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | // the summary for runner allocation 7 | struct RunnerAllocation { 8 | uint256 total; 9 | uint256 used; 10 | uint256 overflowTime; 11 | uint256 overflowAt; 12 | } 13 | 14 | interface IStakingAllocation { 15 | function onStakeUpdate(address _runner) external; 16 | 17 | function allocatedTokens(address _runner, bytes32 _deployment) external view returns (uint256); 18 | 19 | function runnerAllocation(address _runner) external view returns (RunnerAllocation memory); 20 | 21 | function overAllocationTime(address _runner) external view returns (uint256); 22 | 23 | function isOverAllocation(address _runner) external view returns (bool); 24 | 25 | // total allocations on the deployment 26 | function deploymentAllocations(bytes32 _deploymentId) external view returns (uint256); 27 | 28 | function stopService(bytes32 _deployment, address _runner) external; 29 | } 30 | -------------------------------------------------------------------------------- /contracts/interfaces/IStakingManager.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | interface IStakingManager { 7 | function stake(address _runner, uint256 _amount) external; 8 | 9 | function unstake(address _runner, uint256 _amount) external; 10 | 11 | function slashRunner(address _runner, uint256 _amount) external; 12 | 13 | function getTotalStakingAmount(address _runner) external view returns (uint256); 14 | 15 | function getEffectiveTotalStake(address _runner) external view returns (uint256); 16 | 17 | function getAfterDelegationAmount( 18 | address _delegator, 19 | address _runner 20 | ) external view returns (uint256); 21 | 22 | function getDelegationAmount( 23 | address _delegator, 24 | address _runner 25 | ) external view returns (uint256); 26 | 27 | function getEraDelegationAmount( 28 | address _delegator, 29 | address _runner, 30 | uint256 _era 31 | ) external view returns (uint256); 32 | } 33 | -------------------------------------------------------------------------------- /contracts/interfaces/IRewardsStaking.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | interface IRewardsStaking { 7 | function onStakeChange(address indexer, address user) external; 8 | 9 | function onICRChange(address indexer, uint256 startEra) external; 10 | 11 | function applyStakeChange(address indexer, address staker) external; 12 | 13 | function applyICRChange(address indexer) external; 14 | 15 | function checkAndReflectSettlement( 16 | address indexer, 17 | uint256 lastClaimEra 18 | ) external returns (bool); 19 | 20 | function getTotalStakingAmount(address _indexer) external view returns (uint256); 21 | 22 | function getLastSettledEra(address indexer) external view returns (uint256); 23 | 24 | function getDelegationAmount(address source, address indexer) external view returns (uint256); 25 | 26 | function applyRunnerWeightChange(address _runner) external; 27 | 28 | function applyRedelegation(address runner, address staker) external; 29 | } 30 | -------------------------------------------------------------------------------- /contracts/Settings.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol'; 7 | 8 | import './interfaces/ISettings.sol'; 9 | 10 | contract Settings is ISettings, Initializable, OwnableUpgradeable { 11 | mapping(SQContracts => address) public contractAddresses; 12 | 13 | function initialize() external initializer { 14 | __Ownable_init(); 15 | } 16 | 17 | function setContractAddress(SQContracts sq, address _address) public { 18 | contractAddresses[sq] = _address; 19 | } 20 | 21 | function getContractAddress(SQContracts sq) public view returns (address) { 22 | return contractAddresses[sq]; 23 | } 24 | 25 | function setBatchAddress(SQContracts[] calldata _sq, address[] calldata _address) external { 26 | require(_sq.length == _address.length, 'ST001'); 27 | for (uint256 i = 0; i < _sq.length; i++) { 28 | contractAddresses[_sq[i]] = _address[i]; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/fixtures/projects.yaml: -------------------------------------------------------------------------------- 1 | - kind: Account 2 | name: consumer1 3 | seed: trick scale shop mountain any van develop blame sport grid equal garbage 4 | derive: /1 5 | - kind: Project 6 | account: consumer1 7 | projectType: 0 8 | metadata: 9 | name: Staking Threshold - Polkadot 10 | description: Tracking the miminum staking requirements for being validator in polkadot 11 | websiteUrl: https://subquery.network 12 | codeUrl: https://github.com/subquery/tutorials-validator-threshold 13 | deployments: 14 | - deploymentId: QmTQTnBTcvv3Eb3M6neDiwuubWVDAoqyAgKmXtTtJKAHoH 15 | version: 16 | version: 0.1.0 17 | description: init 18 | - kind: Project 19 | account: consumer1 20 | projectType: 0 21 | metadata: 22 | name: Staking Threshold - Kusama 23 | description: Tracking the miminum staking requirements for being validator in Kusama 24 | websiteUrl: https://subquery.network 25 | codeUrl: https://github.com/subquery/tutorials-validator-threshold 26 | deployments: 27 | - deploymentId: QmVcksANdHhnA2YEguJ8LvsftNkipTwkgV9eJyDPZFUqHm 28 | version: 29 | version: 0.1.0 30 | description: init 31 | -------------------------------------------------------------------------------- /contracts/interfaces/IPlanManager.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | /** 7 | * @notice Plan is created by an Indexer, 8 | * a service agreement will be created once a consumer accept a plan. 9 | */ 10 | struct Plan { 11 | address indexer; 12 | uint256 price; 13 | uint256 templateId; 14 | bytes32 deploymentId; 15 | bool active; 16 | } 17 | 18 | /** 19 | * @notice PlanTemplate is created and maintained by the owner, 20 | * the owner provides a set of PlanTemplates for indexers to choose. 21 | * For Indexer and Consumer to create the Plan and Purchase Offer. 22 | */ 23 | struct PlanTemplateV2 { 24 | uint256 period; 25 | uint256 dailyReqCap; 26 | uint256 rateLimit; 27 | address priceToken; 28 | bytes32 metadata; 29 | bool active; 30 | } 31 | 32 | interface IPlanManager { 33 | function getPlan(uint256 planId) external view returns (Plan memory); 34 | 35 | function getLimits(address indexer, bytes32 deploymentId) external view returns (uint256); 36 | 37 | function getPlanTemplate(uint256 templateId) external view returns (PlanTemplateV2 memory); 38 | } 39 | -------------------------------------------------------------------------------- /scripts/upgrade.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { upgradeContracts } from './deployContracts'; 3 | import setup from './setup'; 4 | import yargs from 'yargs/yargs'; 5 | import { hideBin } from 'yargs/helpers'; 6 | 7 | const main = async () => { 8 | const { name, wallet, childProvider, rootProvider, target, confirms, checkOnly, implementationOnly } = 9 | await setup(); 10 | const filePath = `${__dirname}/../publish/${name}.json`; 11 | const deployment = JSON.parse(fs.readFileSync(filePath, { encoding: 'utf8' })); 12 | const matcher = yargs(hideBin(process.argv)).command('$0 [matcher]', '', (opt) => 13 | opt.positional('matcher', { 14 | type: 'string', 15 | }) 16 | ).argv.matcher; 17 | await upgradeContracts({ 18 | wallet: wallet.connect(target === 'root' ? rootProvider : childProvider), 19 | deployment, 20 | confirms, 21 | checkOnly, 22 | implementationOnly, 23 | target, 24 | matcher: matcher ? matcher.split(',') : undefined, 25 | network: name, 26 | }); 27 | 28 | // writeFileSync(filePath, JSON.stringify(deployment, null, 4)); 29 | console.log('Exported the deployment result to ', filePath); 30 | }; 31 | 32 | main(); 33 | -------------------------------------------------------------------------------- /scripts/config/startup.kepler.json: -------------------------------------------------------------------------------- 1 | { 2 | "planTemplates": [], 3 | "QRCreator": [], 4 | "AirdropController": [], 5 | "airdrops": [], 6 | "exchange": [], 7 | "multiSign": "0x34c35136ECe9CBD6DfDf2F896C6e29be01587c0C", 8 | "projects": [ 9 | { 10 | "name": "Polkadot Dictionary", 11 | "deploymentId": "QmVUpDjYQJkSFB2vNsJbkdZ6JUw51v23S3jx7QuU9igr9v", 12 | "deploymentMetadata": "QmNr5DAJ7dQ51hB8J2jeQeSvvGwUQ8dFQYojvu9syMqMoQ", 13 | "projectMetadata": "QmQMadZByP9RHeULtpJDH3qNBVGPv5CsawGaZV2vUSFvYE" 14 | }, 15 | { 16 | "name": "Moonbeam Dictionary", 17 | "deploymentId": "QmXhc36szFrKYVBJkmiXDFM2PDsFADzfb2QF6UBYGsNfH3", 18 | "deploymentMetadata": "QmNr5DAJ7dQ51hB8J2jeQeSvvGwUQ8dFQYojvu9syMqMoQ", 19 | "projectMetadata": "QmQ4cBHRabyy3ExkvfVDFHZSneF5j8ySf843zMifzfY7Cj" 20 | }, 21 | { 22 | "name": "Kusama Dictionary", 23 | "deploymentId": "QmZJ9whrhrMvn2HU8supcbGxCyCymdpzqWDgpaNuXCYXnk", 24 | "deploymentMetadata": "QmNr5DAJ7dQ51hB8J2jeQeSvvGwUQ8dFQYojvu9syMqMoQ", 25 | "projectMetadata": "Qmcf1Yp3NeLkfM78KjcK4JKdtY7N2eMEodCFKd1ZyQUdAX" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /scripts/verifyUpgrade.ts: -------------------------------------------------------------------------------- 1 | import Web3 from "web3"; 2 | 3 | import { argv } from "./setup"; 4 | import MainnetImpl from '../publish/mainnet.json'; 5 | import contracts from '../src/contracts'; 6 | import { getLogger } from './logger'; 7 | 8 | const main = async () => { 9 | const target = argv.target ?? 'child'; 10 | const contractsImpl = MainnetImpl[target]; 11 | const web3 = new Web3('https://base.llamarpc.com'); 12 | const logger = getLogger('verifyUpgrade'); 13 | 14 | for (const name in contractsImpl) { 15 | const contract = contractsImpl[name]; 16 | const address = contract.innerAddress; 17 | if (!address) continue; 18 | 19 | const contractInfo = contracts[name]; 20 | if (!contractInfo) { 21 | logger.error(`Contract not found: ${name}`); 22 | continue; 23 | } 24 | 25 | const localBytecode = contractInfo.deployedBytecode; 26 | const remoteBytecode = await web3.eth.getCode(address); 27 | if (remoteBytecode !== localBytecode) { 28 | logger.info(`🚨 Bytecode mismatch for contract: ${name}`); 29 | } else { 30 | logger.info(`🚀 Contract verification succeed: ${name}`); 31 | } 32 | } 33 | } 34 | 35 | main(); -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | const { description } = require('../../package') 2 | 3 | module.exports = { 4 | base: "/network-contracts/", 5 | /** 6 | * Ref:https://v1.vuepress.vuejs.org/config/#title 7 | */ 8 | title: 'SubQuery Network API', 9 | /** 10 | * Ref:https://v1.vuepress.vuejs.org/config/#description 11 | */ 12 | description: description, 13 | 14 | /** 15 | * Extra tags to be injected to the page HTML `` 16 | * 17 | * ref:https://v1.vuepress.vuejs.org/config/#head 18 | */ 19 | head: [ 20 | ['meta', { name: 'theme-color', content: '#3eaf7c' }], 21 | ['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }], 22 | ['meta', { name: 'apple-mobile-web-app-status-bar-style', content: 'black' }] 23 | ], 24 | 25 | /** 26 | * Theme configuration, here is the default theme configuration for VuePress. 27 | * 28 | * ref:https://v1.vuepress.vuejs.org/theme/default-theme-config.html 29 | */ 30 | themeConfig: { 31 | repo: '', 32 | editLinks: false, 33 | docsDir: '', 34 | editLinkText: '', 35 | lastUpdated: true 36 | }, 37 | 38 | /** 39 | * Apply plugins,ref:https://v1.vuepress.vuejs.org/zh/plugin/ 40 | */ 41 | plugins: [ 42 | '@vuepress/plugin-back-to-top', 43 | '@vuepress/plugin-medium-zoom', 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /test/fixtures/testnet.yaml: -------------------------------------------------------------------------------- 1 | - kind: Account 2 | name: indexer1 3 | seed: trick scale shop mountain any van develop blame sport grid equal garbage 4 | derive: /0 5 | - kind: Account 6 | name: consumer1 7 | seed: trick scale shop mountain any van develop blame sport grid equal garbage 8 | derive: /1 9 | - kind: Faucet 10 | account: indexer1 11 | amounts: 12 | SQT: 10000 13 | Fee: 100 14 | - kind: Faucet 15 | account: consumer1 16 | amounts: 17 | SQT: 10000 18 | Fee: 100 19 | - kind: Project 20 | account: consumer1 21 | metadata: 22 | name: Polkadot Gift 23 | description: Tracking gift transactions in polkadot 24 | websiteUrl: https://subquery.network 25 | codeUrl: https://github.com/subquery/polkadot-gift-subql 26 | deployments: 27 | - deploymentId: QmbAGhy2MrBFJ6roF511hUNGYKAv8TbSELdoenjsN9Grm8 28 | version: 29 | version: 0.1.0 30 | description: init 31 | - kind: Indexer 32 | account: indexer1 33 | stake: 1000 34 | commissionRate: 0.2 35 | 36 | - kind: QueryAction 37 | account: indexer1 38 | action: index 39 | deploymentId: QmbAGhy2MrBFJ6roF511hUNGYKAv8TbSELdoenjsN9Grm8 40 | #- kind: QueryAction 41 | # account: indexer1 42 | # action: ready 43 | # deploymentId: QmbAGhy2MrBFJ6roF511hUNGYKAv8TbSELdoenjsN9Grm8 44 | 45 | -------------------------------------------------------------------------------- /scripts/deploy.ts: -------------------------------------------------------------------------------- 1 | import setup from './setup'; 2 | import { deployContracts, deployRootContracts, saveDeployment } from './deployContracts'; 3 | 4 | const main = async () => { 5 | try { 6 | const { name, config, rootProvider, childProvider, target, wallet, confirms, history } = await setup(); 7 | let result; 8 | if (target === 'root') { 9 | result = await deployRootContracts(wallet.connect(rootProvider), config.contracts, { 10 | network: name, 11 | confirms, 12 | history, 13 | }); 14 | if (!result) { 15 | console.log('Failed to deploy contracts!'); 16 | return; 17 | } 18 | } else if (target === 'child') { 19 | result = await deployContracts(wallet.connect(childProvider), config.contracts, { 20 | network: name, 21 | confirms, 22 | history, 23 | }); 24 | if (!result) { 25 | console.log('Failed to deploy contracts!'); 26 | return; 27 | } 28 | } 29 | 30 | const [deployment] = result; 31 | saveDeployment(name, deployment); 32 | } catch (error) { 33 | console.log(`Failed to deploy contracts: ${error}`); 34 | } 35 | }; 36 | 37 | main(); 38 | -------------------------------------------------------------------------------- /contracts/root/SQToken.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; 7 | import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; 8 | import '@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol'; 9 | import '@openzeppelin/contracts/access/Ownable.sol'; 10 | 11 | contract SQToken is ERC20, Ownable, ERC20Burnable { 12 | using SafeERC20 for IERC20; 13 | address public minter; 14 | 15 | modifier isMinter() { 16 | require(minter == msg.sender, 'Not minter'); 17 | _; 18 | } 19 | 20 | constructor(address _minter, uint256 totalSupply) ERC20('SubQueryToken', 'SQT') Ownable() { 21 | minter = _minter; 22 | _mint(msg.sender, totalSupply); 23 | } 24 | 25 | function mint(address destination, uint256 amount) external isMinter { 26 | _mint(destination, amount); 27 | } 28 | 29 | /// #if_succeeds {:msg "minter should be set"} minter == _minter; 30 | /// #if_succeeds {:msg "owner functionality"} old(msg.sender == address(owner)); 31 | function setMinter(address _minter) external onlyOwner { 32 | minter = _minter; 33 | } 34 | 35 | function getMinter() external view returns (address) { 36 | return minter; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /contracts/interfaces/IConsumer.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity ^0.8.15; 5 | 6 | //import '@openzeppelin/contracts/utils/introspection/IERC165.sol'; 7 | 8 | interface IConsumer { 9 | // Params: msg sender 10 | function isSigner(address signer) external view returns (bool); 11 | 12 | // Params: channel id, msg sender, amount, callback info. 13 | function paid( 14 | uint256 channelId, 15 | address sender, 16 | uint256 amount, 17 | bytes memory callback 18 | ) external; 19 | 20 | // Params: channel id, msg sender, amount. 21 | function claimed(uint256 channelId, uint256 amount) external; 22 | 23 | // Params: channel id, signature 24 | function checkSign( 25 | uint256 channelId, 26 | bytes32 payload, 27 | bytes memory sign 28 | ) external view returns (bool); 29 | 30 | // Params: channel id, sender 31 | function checkSender(uint256 channelId, address sender) external view returns (bool); 32 | 33 | // Params: channel id 34 | function channelConsumer(uint256 channelId) external view returns (address); 35 | 36 | function setChannelConsumer(uint256 channelId, bytes memory callback) external; 37 | 38 | function decodeConsumerCallback( 39 | bytes memory callback 40 | ) external pure returns (address, bytes memory); 41 | } 42 | -------------------------------------------------------------------------------- /contracts/interfaces/IRewardsDistributor.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | import './IServiceAgreementRegistry.sol'; 7 | 8 | // Reward info for query. 9 | struct IndexerRewardInfo { 10 | uint256 accSQTPerStake; 11 | uint256 lastClaimEra; 12 | uint256 eraReward; 13 | } 14 | 15 | interface IRewardsDistributor { 16 | function setLastClaimEra(address indexer, uint256 era) external; 17 | 18 | function setRewardDebt(address indexer, address delegator, uint256 amount) external; 19 | 20 | function resetEraReward(address indexer, uint256 era) external; 21 | 22 | function collectAndDistributeRewards(address indexer) external; 23 | 24 | function collectAndDistributeEraRewards( 25 | uint256 era, 26 | address indexer 27 | ) external returns (uint256); 28 | 29 | function increaseAgreementRewards(uint256 agreementId) external; 30 | 31 | function addInstantRewards( 32 | address indexer, 33 | address sender, 34 | uint256 amount, 35 | uint256 era 36 | ) external; 37 | 38 | function claim(address indexer) external; 39 | 40 | function claimFrom(address indexer, address user) external returns (uint256); 41 | 42 | function userRewards(address indexer, address user) external view returns (uint256); 43 | 44 | function getRewardInfo(address indexer) external view returns (IndexerRewardInfo memory); 45 | 46 | function claimForDelegate(address runner, address user) external returns (uint256); 47 | } 48 | -------------------------------------------------------------------------------- /contracts/root/VTSQToken.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; 7 | import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; 8 | import '@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol'; 9 | import '@openzeppelin/contracts/access/Ownable.sol'; 10 | 11 | contract VTSQToken is ERC20, Ownable, ERC20Burnable { 12 | using SafeERC20 for IERC20; 13 | address public minter; 14 | 15 | modifier isMinter() { 16 | require(minter == msg.sender, 'Not minter'); 17 | _; 18 | } 19 | 20 | constructor(address _minter) ERC20('VTSubQueryToken', 'vtSQT') Ownable() { 21 | minter = _minter; 22 | } 23 | 24 | function mint(address destination, uint256 amount) external isMinter { 25 | _mint(destination, amount); 26 | } 27 | 28 | /// #if_succeeds {:msg "minter should be set"} minter == _minter; 29 | /// #if_succeeds {:msg "owner functionality"} old(msg.sender == address(owner)); 30 | function setMinter(address _minter) external onlyOwner { 31 | minter = _minter; 32 | } 33 | 34 | function getMinter() external view returns (address) { 35 | return minter; 36 | } 37 | 38 | // @notice skip spender check when it is requested by minter (vesting contract) 39 | function burnFrom(address account, uint256 amount) public override { 40 | if (_msgSender() != minter) { 41 | _spendAllowance(account, _msgSender(), amount); 42 | } 43 | _burn(account, amount); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /contracts/interfaces/IProjectRegistry.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | enum ServiceStatus { 7 | TERMINATED, 8 | READY 9 | } 10 | 11 | enum ProjectType { 12 | SUBQUERY, 13 | RPC, 14 | SQ_DICT, 15 | SUBGRAPH, 16 | LLM 17 | } 18 | 19 | struct ProjectInfo { 20 | bytes32 latestDeploymentId; 21 | ProjectType projectType; 22 | } 23 | 24 | struct DeploymentInfo { 25 | uint256 projectId; 26 | bytes32 metadata; 27 | } 28 | 29 | interface IProjectRegistry { 30 | function numberOfDeployments(address _address) external view returns (uint256); 31 | 32 | function isServiceAvailable(bytes32 deploymentId, address indexer) external view returns (bool); 33 | 34 | function createProject( 35 | string memory projectMetadataUri, 36 | bytes32 deploymentMetdata, 37 | bytes32 deploymentId, 38 | ProjectType projectType 39 | ) external; 40 | 41 | function updateProjectMetadata(uint256 projectId, string memory metadataUri) external; 42 | 43 | function addOrUpdateDeployment( 44 | uint256 projectId, 45 | bytes32 deploymentId, 46 | bytes32 metadata, 47 | bool updateLatest 48 | ) external; 49 | 50 | function setProjectLatestDeployment(uint256 projectId, bytes32 deploymentId) external; 51 | 52 | function startService(bytes32 deploymentId) external; 53 | 54 | function stopService(bytes32 deploymentId) external; 55 | 56 | function getDeploymentProjectType(bytes32 deploymentId) external view returns (ProjectType); 57 | 58 | function isDeploymentRegistered(bytes32 deploymentId) external view returns (bool); 59 | } 60 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: 'Release' 2 | on: 3 | push: 4 | branches: 5 | - main 6 | - kepler-network 7 | - develop 8 | paths-ignore: 9 | - '.github/workflows/**' 10 | workflow_dispatch: 11 | 12 | jobs: 13 | Build-Publish: 14 | name: Build-Publish 15 | if: "!startsWith(github.event.head_commit.message, '[SKIP CI]') && startsWith(github.event.head_commit.message, '[release]') && github.repository == 'subquery/network-contracts'" 16 | runs-on: ubuntu-latest 17 | steps: 18 | #Check out 19 | - uses: actions/checkout@v2 20 | with: 21 | fetch-depth: 100 22 | 23 | - uses: actions/setup-node@v1 24 | with: 25 | node-version: 18 26 | registry-url: 'https://registry.npmjs.org' 27 | 28 | #Identify changes 29 | - uses: marceloprado/has-changed-path@v1 30 | id: changed 31 | with: 32 | paths: package.json 33 | 34 | - run: corepack enable 35 | - run: yarn 36 | 37 | - name: build 38 | run: yarn build 39 | #Publish to npm 40 | - uses: JS-DevTools/npm-publish@v1 41 | name: Publish 42 | with: 43 | token: ${{ secrets.NPM_TOKEN }} 44 | tag: latest 45 | access: public 46 | package: build/build/package.json 47 | - uses: christophebedard/tag-version-commit@v1 48 | with: 49 | token: ${{ secrets.GITHUB_TOKEN }} 50 | version_tag_prefix: 'v' 51 | version_regex: '([0-9]+\.[0-9]+\.[0-9]+)' 52 | -------------------------------------------------------------------------------- /.github/workflows/prerelease.yml: -------------------------------------------------------------------------------- 1 | name: 'Prerelease' 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths-ignore: 7 | - '.github/workflows/**' 8 | workflow_dispatch: 9 | 10 | jobs: 11 | Bump-Prerelease-Publish: 12 | name: Bump-Prerelease-Publish 13 | if: "!startsWith(github.event.head_commit.message, '[SKIP CI]') && !startsWith(github.event.head_commit.message, '[release]') && github.repository == 'subquery/network-contracts'" 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | with: 18 | fetch-depth: 100 19 | token: ${{ secrets.GITHUB_TOKEN }} 20 | 21 | - uses: actions/setup-node@v1 22 | with: 23 | node-version: 18 24 | registry-url: 'https://registry.npmjs.org' 25 | 26 | - uses: marceloprado/has-changed-path@v1 27 | id: changed 28 | with: 29 | paths: contracts src publish 30 | 31 | - run: corepack enable 32 | - run: yarn 33 | 34 | - name: Bump & build 35 | #if: steps.changed.outputs.changed == 'true' 36 | run: echo "Changes found" && npm version prerelease --no-git-tag-version && yarn build 37 | 38 | - uses: JS-DevTools/npm-publish@v1 39 | name: Publish 40 | #if: steps.changed.outputs.changed == 'true' 41 | with: 42 | token: ${{ secrets.NPM_TOKEN }} 43 | tag: dev 44 | access: public 45 | package: build/build/package.json 46 | 47 | - name: Commit changes 48 | uses: EndBug/add-and-commit@v5 49 | with: 50 | add: 'package.json' 51 | message: '[SKIP CI] Prerelease' 52 | env: 53 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 54 | -------------------------------------------------------------------------------- /scripts/abi.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync } from 'fs'; 2 | 3 | const main = async () => { 4 | try { 5 | const contracts = [ 6 | 'Settings', 7 | 'VSQToken', 8 | 'SQTGift', 9 | 'SQTRedeem', 10 | 'Staking', 11 | 'StakingManager', 12 | 'StakingAllocation', 13 | 'IndexerRegistry', 14 | 'ProjectRegistry', 15 | 'PlanManager', 16 | 'PurchaseOfferMarket', 17 | 'ServiceAgreementRegistry', 18 | 'RewardsDistributor', 19 | 'RewardsPool', 20 | 'RewardsStaking', 21 | 'RewardsHelper', 22 | 'RewardsBooster', 23 | 'StateChannel', 24 | 'Airdropper', 25 | 'PermissionedExchange', 26 | 'ConsumerHost', 27 | 'DisputeManager', 28 | 'ConsumerRegistry', 29 | 'PriceOracle', 30 | 'TokenExchange', 31 | ]; 32 | const childContracts = ['L2SQToken', 'EraManager']; 33 | const rootContracts = ['SQToken', 'Vesting', 'VTSQToken', 'InflationController', 'OpDestination']; 34 | const proxyContracts = ['ProxyAdmin']; 35 | const run = (input: string[], rootDir) => { 36 | input.forEach(function (name) { 37 | const readPath = `${rootDir}/${name}.sol/${name}.json`; 38 | const contract = JSON.parse(readFileSync(readPath, 'utf8')); 39 | 40 | const savePath = `${__dirname}/../publish/ABI/${name}.json`; 41 | writeFileSync(savePath, JSON.stringify(contract.abi, null, 4)); 42 | }); 43 | }; 44 | run(contracts, `${__dirname}/../artifacts/contracts`); 45 | run(rootContracts, `${__dirname}/../artifacts/contracts/root`); 46 | run(childContracts, `${__dirname}/../artifacts/contracts/l2`); 47 | run(proxyContracts, `${__dirname}/../artifacts/@openzeppelin/contracts/proxy/transparent`); 48 | console.log(`Generated ABI files completed`); 49 | } catch (e) { 50 | console.log(`e`, e); 51 | } 52 | }; 53 | 54 | main(); 55 | -------------------------------------------------------------------------------- /contracts/interfaces/IStaking.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | /** 7 | * @dev Total staking amount information. One per Indexer. 8 | * Stake amount change need to be applied at next Era. 9 | */ 10 | struct StakingAmount { 11 | uint256 era; // last update era 12 | uint256 valueAt; // value at the era 13 | uint256 valueAfter; // value to be refreshed from next era 14 | } 15 | 16 | /** 17 | * @dev Unbond amount information. One per request per Delegator. 18 | * Delegator can withdraw the unbond amount after the lockPeriod. 19 | */ 20 | struct UnbondAmount { 21 | address indexer; // the indexer before delegate. 22 | uint256 amount; // pending unbonding amount 23 | uint256 startTime; // unbond start time 24 | } 25 | 26 | enum UnbondType { 27 | Undelegation, 28 | Unstake, 29 | Commission, 30 | Merge 31 | } 32 | 33 | /** 34 | * @dev Instant delegation quota tracking. One per Delegator. 35 | * Tracks quota usage within current era, auto-resets on era change. 36 | */ 37 | struct InstantQuotaUsage { 38 | uint256 era; // era of quota usage 39 | uint256 amount; // quota used in this era 40 | } 41 | 42 | interface IStaking { 43 | function lockedAmount(address _delegator) external view returns (uint256); 44 | 45 | function unbondCommission(address _runner, uint256 _amount) external; 46 | 47 | function addDelegation( 48 | address _source, 49 | address _runner, 50 | uint256 _amount, 51 | bool instant 52 | ) external; 53 | 54 | function transferDelegationTokens(address _source, uint256 _amount) external; 55 | 56 | function updateInstantQuotaUsed(address delegator, uint256 era, uint256 amount) external; 57 | 58 | function setInstantDelegationParams(uint256 _perEraQuota, uint256 _windowPercent) external; 59 | 60 | function getInstantQuotaRemaining( 61 | address delegator, 62 | uint256 era 63 | ) external view returns (uint256); 64 | 65 | function instantDelegationQuota() external view returns (uint256); 66 | 67 | function instantEraWindowPercent() external view returns (uint256); 68 | } 69 | -------------------------------------------------------------------------------- /scripts/testOpTokenBridge.ts: -------------------------------------------------------------------------------- 1 | import moduleAlias from 'module-alias'; 2 | moduleAlias.addAlias('./artifacts', '../artifacts'); 3 | moduleAlias.addAlias('./publish', '../publish'); 4 | 5 | import { networks } from '../src'; 6 | // @ts-expect-error path changed with hmoduleAlias 7 | import mainnetDeployment from './publish/mainnet.json'; 8 | import { setupCommon } from './setup'; 9 | import { ethers } from 'ethers'; 10 | import { CrossChainMessenger, MessageStatus, SignerOrProviderLike } from '@eth-optimism/sdk'; 11 | 12 | const dryRun = false; 13 | 14 | const deposit = async () => { 15 | const { wallet, rootProvider, childProvider } = await setupCommon(networks.mainnet); 16 | const crossChainMessenger = new CrossChainMessenger({ 17 | l1ChainId: (await rootProvider.getNetwork()).chainId, 18 | l2ChainId: (await childProvider.getNetwork()).chainId, 19 | l1SignerOrProvider: wallet.connect(rootProvider) as unknown as SignerOrProviderLike, 20 | l2SignerOrProvider: wallet.connect(childProvider) as unknown as SignerOrProviderLike, 21 | }); 22 | const amount = ethers.utils.parseEther('414395576.3'); 23 | const rootToken = mainnetDeployment.root.SQToken.address; 24 | const l2Token = mainnetDeployment.child.L2SQToken.address; 25 | 26 | console.log( 27 | `request deposit approval: signer: ${wallet.address}, amount: ${amount.toString()}, root token:${rootToken}, l2 token = ${l2Token}` 28 | ); 29 | if (!dryRun) { 30 | const depositApproveTx = await crossChainMessenger.approveERC20(rootToken, l2Token, amount); 31 | console.log(`tx: ${depositApproveTx.hash}`); 32 | await depositApproveTx.wait(); 33 | console.log('deposit approval done'); 34 | 35 | console.log('request deposit'); 36 | const depositTx = await crossChainMessenger.depositERC20(rootToken, l2Token, amount); 37 | console.log(`deposit hash: ${depositTx.hash}`); 38 | await depositTx.wait(); 39 | console.log('deposit done'); 40 | 41 | console.log('wait for deposit status'); 42 | await crossChainMessenger.waitForMessageStatus(depositTx.hash, MessageStatus.RELAYED); 43 | console.log('deposit status done'); 44 | } 45 | }; 46 | 47 | deposit(); 48 | -------------------------------------------------------------------------------- /contracts/VSQToken.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity ^0.8.15; 5 | 6 | import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol'; 7 | import '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol'; 8 | import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; 9 | 10 | import './interfaces/ISettings.sol'; 11 | import './interfaces/IStaking.sol'; 12 | import './interfaces/IVesting.sol'; 13 | 14 | contract VSQToken is Initializable, OwnableUpgradeable { 15 | string private _name = 'VotingSubQueryToken'; 16 | string private _symbol = 'VSQT'; 17 | uint8 private _decimals = 18; 18 | ISettings public settings; 19 | 20 | function initialize(ISettings _settings) external initializer { 21 | __Ownable_init(); 22 | settings = _settings; 23 | } 24 | 25 | /** 26 | * @notice Update setting state. 27 | * @param _settings ISettings contract 28 | */ 29 | function setSettings(ISettings _settings) external onlyOwner { 30 | settings = _settings; 31 | } 32 | 33 | function name() public view returns (string memory) { 34 | return _name; 35 | } 36 | 37 | function symbol() public view returns (string memory) { 38 | return _symbol; 39 | } 40 | 41 | function decimals() public pure returns (uint8) { 42 | return 18; 43 | } 44 | 45 | function balanceOf(address account) public view returns (uint256) { 46 | uint256 balanceAmount = IERC20(settings.getContractAddress(SQContracts.SQToken)).balanceOf( 47 | account 48 | ); 49 | uint256 stakeAmount = IStaking(settings.getContractAddress(SQContracts.Staking)) 50 | .lockedAmount(account); 51 | // TODO: vesting may not live on child network 52 | IVesting vesting = IVesting(settings.getContractAddress(SQContracts.Vesting)); 53 | uint256 returnBalance = balanceAmount + stakeAmount; 54 | if (address(vesting) != address(0)) { 55 | uint256 vestingAmount = vesting.allocations(account) - vesting.claimed(account); 56 | returnBalance = returnBalance + vestingAmount; 57 | } 58 | return returnBalance; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /scripts/seed.ts: -------------------------------------------------------------------------------- 1 | import moduleAlias from 'module-alias'; 2 | moduleAlias.addAlias('./publish', '../publish'); 3 | moduleAlias.addAlias('./artifacts', '../artifacts'); 4 | 5 | import { JsonRpcProvider } from '@ethersproject/providers'; 6 | import { ContractSDK, SubqueryNetwork, networks } from '../src'; 7 | import assert from 'assert'; 8 | import { Wallet } from '@ethersproject/wallet'; 9 | import { create } from 'ipfs-http-client'; 10 | import yaml from 'js-yaml'; 11 | import { Context, loaders } from '../test/fixtureLoader'; 12 | import fs from 'fs'; 13 | import yargs from 'yargs/yargs'; 14 | import { hideBin } from 'yargs/helpers'; 15 | 16 | export const { argv } = yargs(hideBin(process.argv)).options({ 17 | network: { 18 | demandOption: true, 19 | describe: 'network', 20 | type: 'string', 21 | choices: ['testnet', 'mainnet'], 22 | }, 23 | }); 24 | 25 | async function init(): Promise { 26 | const endpoint = process.env['ENDPOINT'] ?? networks[argv.network].child.rpcUrls[0]; 27 | 28 | const provider = new JsonRpcProvider(endpoint); 29 | const sdk = await ContractSDK.create(provider, { network: argv.network as SubqueryNetwork }); 30 | const rootAccountPK = process.env['PK']; 31 | assert(rootAccountPK, `can't find $PK in env`); 32 | return { 33 | sdk, 34 | provider, 35 | accounts: {}, 36 | rootAccount: new Wallet(rootAccountPK, provider), 37 | ipfs: create({ url: 'https://unauthipfs.subquery.network/ipfs/api/v0' }), 38 | }; 39 | } 40 | 41 | async function main() { 42 | const context = await init(); 43 | const fixtureFile = process.argv[process.argv.length - 1]; 44 | const fixture = yaml.load(fs.readFileSync(fixtureFile, { encoding: 'utf-8' })); 45 | if (fixture instanceof Array) { 46 | for (const item of fixture) { 47 | const loader = loaders[item.kind]; 48 | if (loader) { 49 | try { 50 | await loader(item, context); 51 | } catch (e) { 52 | console.error(`loader ${item.kind} failed`, e); 53 | } 54 | } else { 55 | console.error(`loader not found for ${item.kind}`); 56 | } 57 | } 58 | } 59 | } 60 | 61 | main(); 62 | -------------------------------------------------------------------------------- /test/constants.ts: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | export const METADATA_HASH = '0xab3921276c8067fe0c82def3e5ecfd8447f1961bc85768c2a56e6bd26d3c0c55'; 5 | 6 | export const METADATA_1_HASH = '0xcc3921276c8067fe0c82def3e5ecfd8447f1961bc85768c2a56e6bd26d3c0c55'; 7 | 8 | export const DEPLOYMENT_ID = '0xbec921276c8067fe0c82def3e5ecfd8447f1961bc85768c2a56e6bd26d3c0c55'; 9 | 10 | export const VERSION = '0xaec921276c8067fe0c82def3e5ecfd8447f1961bc85768c2a56e6bd26d3c0c55'; 11 | 12 | export const POI = '0xab3921276c8067fe0c82def3e5ecfd8447f1961bc85768c2a56e6bd26d3c0c55'; 13 | 14 | export const projectMetadatas = [ 15 | 'QmZGAZQ7e1oZgfuK4V29Fa5gveYK3G2zEwvUzTZKNvSBsm', 16 | 'QmeeqBHdVu7iYnhVE9ZiYEKTWe4jXVUD5pVoGXT6LbCP2t', 17 | 'QmeHdVHdVuHdVnhVE9ZiYEKTWe4jXVUD5HdVGXT6LbCHdV', 18 | 'QmeHdVHdVuHdVnhVE9ZiYEKTWe4jXVUD5HdVGXT6LbCHdd', 19 | ]; 20 | 21 | export const metadatas = [ 22 | '0xab3921276c8067fe0c82def3e5ecfd8447f1961bc85768c2a56e6bd26d3c0c55', 23 | '0xdec921276c8067fe0c82def3e5ecfd8447f1961bc85768c2a56e6bd26d3c0c53', 24 | '0xcec921276c8067fe0c82def3e5ecfd8447f1961bc85768c2a56e6bd26d3c0c55', 25 | '0xcec921276c8067fe0c82def3e5ecfd8447f1961bc85768c2a56e6bd26d3c0c56', 26 | ]; 27 | 28 | export const deploymentMetadatas = [ 29 | '0xaec921276c8067fe0c82def3e5ecfd8447f1961bc85768c2a56e6bd26d3c0c55', 30 | '0xccc921276c8067fe0c82def3e5ecfd8447f1961bc85768c2a56e6bd26d3c0c55', 31 | '0xccc921276c8067fe0c82def3e5ecfd8447f1961bc85768c2a56e6bd26d3c0c5a', 32 | '0xacc921276c8067fe0c82def3e5ecfd8447f1961bc85768c2a56e6bd26d3c0c57', 33 | ]; 34 | 35 | export const deploymentIds = [ 36 | '0xbec921276c8067fe0c82def3e5ecfd8447f1961bc85768c2a56e6bd26d3c0c55', 37 | '0xccc921276c8067fe0c82def3e5ecfd8447f1961bc85768c2a56e6bd26d3c0c56', 38 | '0xddc921276c8067fe0c82def3e5ecfd8447f1961bc85768c2a56e6bd26d3c0c57', 39 | '0xddc921276c8067fe0c82def3e5ecfd8447f1961bc85768c2a56e6bd26d3c0c58', 40 | '0xddc921276c8067fe0c82def3e5ecfd8447f1961bc85768c2a56e6bd26d3c0c59', 41 | ]; 42 | 43 | export const poi = '0xab3921276c8067fe0c82def3e5ecfd8447f1961bc85768c2a56e6bd26d3c0c55'; 44 | 45 | export const PER_MILL = 1e6; 46 | 47 | export const PER_BILL = 1e9; 48 | 49 | export const PER_TRILL = 1e12; 50 | 51 | export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; 52 | -------------------------------------------------------------------------------- /test/e2e/startup.e2e.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ethers, waffle } from 'hardhat'; 3 | 4 | import config from 'scripts/config/startup.testnet.json'; 5 | import { airdrop, createPlanTemplates, createProjects } from '../../scripts/startup'; 6 | import { cidToBytes32 } from '../helper'; 7 | import { deployContracts } from '../setup'; 8 | 9 | // this test need to update once the new contracts are deployed to mumbai 10 | describe.skip('startup script', () => { 11 | let sdk; 12 | let wallet; 13 | 14 | before(async () => { 15 | // deploy contracts 16 | const mockProvider = waffle.provider; 17 | [wallet] = await ethers.getSigners(); 18 | const deployment = await deployContracts(wallet, wallet); 19 | sdk = { 20 | sqToken: deployment.token, 21 | airdropper: deployment.airdropper, 22 | planManager: deployment.planManager, 23 | projectRegistry: deployment.projectRegistry, 24 | permissionedExchange: deployment.permissionedExchange, 25 | }; 26 | 27 | await createPlanTemplates(sdk, mockProvider); 28 | await createProjects(sdk, mockProvider); 29 | await airdrop(sdk, mockProvider); 30 | }); 31 | 32 | describe('startup', async () => { 33 | it('planTemplate setups should work', async () => { 34 | expect(await sdk.planManager.nextTemplateId()).to.be.equal(5); 35 | const { planTemplates } = config; 36 | 37 | for (const [i, p] of planTemplates.entries()) { 38 | expect((await sdk.planManager.getPlanTemplate(i)).period).to.be.equal(p.period); 39 | } 40 | }); 41 | 42 | it('dictionaries should be created', async () => { 43 | const { projects } = config; 44 | for (const [i, d] of projects.entries()) { 45 | const info = await sdk.projectRegistry.projectInfos(i); 46 | const version = await sdk.projectRegistry.deploymentInfos(info.latestDeploymentId); 47 | const metadata = await sdk.projectRegistry.tokenURI(i + 1); 48 | expect(info.latestDeploymentId).to.be.equal(cidToBytes32(d.deploymentId)); 49 | expect(version.metadata).to.be.equal(`ipfs://${d.deploymentMetadata}`); 50 | expect(metadata).to.be.equal(`ipfs://${d.projectMetadata}`); 51 | } 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /test/setup.ts: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | import moduleAlias from 'module-alias'; 5 | moduleAlias.addAlias('./publish', '../publish'); 6 | moduleAlias.addAlias('./artifacts', '../artifacts'); 7 | 8 | import { Wallet as EthWallet, constants, utils } from 'ethers'; 9 | import { ZERO_ADDRESS } from './constants'; 10 | import { Wallet, etherParse } from './helper'; 11 | 12 | import { deployContracts as deploy, deployRootContracts as deployRoot } from '../scripts/deployContracts'; 13 | import { SQContracts } from '../src'; 14 | 15 | export const deployContracts = async (wallet: Wallet, wallet1: Wallet, treasury = wallet) => { 16 | const signer = wallet as EthWallet; 17 | const [_, contracts] = await deploy(signer, { 18 | InflationController: [1000, wallet1.address], 19 | SQToken: [constants.AddressZero, etherParse('10000000000').toString()], 20 | Staking: [1000, 1e3], 21 | Airdropper: [ZERO_ADDRESS], 22 | EraManager: [60 * 60 * 24], 23 | ServiceAgreementRegistry: [], 24 | PurchaseOfferMarket: [1e5, ZERO_ADDRESS], 25 | IndexerRegistry: [etherParse('1000').toString()], 26 | ConsumerHost: [10000], 27 | DisputeManager: [etherParse('1000').toString()], 28 | Settings: [], 29 | VSQToken: [], 30 | StakingManager: [], 31 | ProjectRegistry: [], 32 | PlanManager: [], 33 | RewardsDistributor: [], 34 | RewardsPool: [], 35 | RewardsStaking: [], 36 | RewardsHelper: [], 37 | ProxyAdmin: [], 38 | StateChannel: [], 39 | PermissionedExchange: [], 40 | TokenExchange: [], 41 | Vesting: [], 42 | ConsumerRegistry: [], 43 | PriceOracle: [], 44 | RewardsBooster: [utils.parseEther('10').toString(), utils.parseEther('10000').toString()], // _issuancePerBlock, _minimumDeploymentBooster 45 | UniswapPriceOracle: ['0x222ca98f00ed15b1fae10b61c277703a194cf5d2'], 46 | }); 47 | await contracts.settings.setContractAddress(SQContracts.Treasury, treasury.address); 48 | 49 | return contracts; 50 | }; 51 | 52 | export const deployRootContracts = async (wallet: Wallet, wallet1: Wallet) => { 53 | const signer = wallet as EthWallet; 54 | const [_, contracts] = await deployRoot(signer, { 55 | InflationController: [1000, wallet1.address], 56 | SQToken: [etherParse('10000000000').toString()], 57 | VTSQToken: [0], 58 | }); 59 | 60 | return contracts; 61 | }; 62 | -------------------------------------------------------------------------------- /test/SQToken.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | import { expect } from 'chai'; 5 | import { ethers, waffle } from 'hardhat'; 6 | import { InflationController, SQToken } from '../src'; 7 | import { etherParse } from './helper'; 8 | import { deployRootContracts } from './setup'; 9 | 10 | describe('SQToken Contract', () => { 11 | let wallet_0, wallet_1; 12 | let inflationController: InflationController; 13 | let token: SQToken; 14 | 15 | const deployer = () => deployRootContracts(wallet_0, wallet_1); 16 | before(async () => { 17 | [wallet_0, wallet_1] = await ethers.getSigners(); 18 | }); 19 | 20 | beforeEach(async () => { 21 | const deployment = await waffle.loadFixture(deployer); 22 | inflationController = deployment.inflationController; 23 | token = deployment.rootToken; 24 | }); 25 | 26 | describe('Genesis Config', () => { 27 | it('check genesis config', async () => { 28 | expect(await token.getMinter()).to.equal(inflationController.address); 29 | expect(await token.balanceOf(wallet_0.address)).to.equal(etherParse('10000000000')); 30 | }); 31 | }); 32 | 33 | describe('Mint Tokens', () => { 34 | it('mint with personal wallet should fail', async () => { 35 | await expect(token.mint(wallet_0.address, etherParse('1'))).to.be.revertedWith('Not minter'); 36 | }); 37 | }); 38 | 39 | describe('Burn Tokens', () => { 40 | beforeEach(async () => { 41 | await token.transfer(wallet_1.address, etherParse('10')); 42 | }); 43 | 44 | it('burn tokens with current account should work', async () => { 45 | const balance = await token.balanceOf(wallet_1.address); 46 | await token.connect(wallet_1).burn(etherParse('1')); 47 | expect(await token.balanceOf(wallet_1.address)).to.equal(balance.sub(etherParse('1'))); 48 | }); 49 | 50 | it('burn tokens from given account should work', async () => { 51 | const balance = await token.balanceOf(wallet_1.address); 52 | await token.connect(wallet_1).approve(wallet_0.address, etherParse('10')); 53 | await token.burnFrom(wallet_1.address, etherParse('1')); 54 | 55 | expect(await token.allowance(wallet_1.address, wallet_0.address)).to.equal(etherParse('9')); 56 | expect(await token.balanceOf(wallet_1.address)).to.equal(balance.sub(etherParse('1'))); 57 | }); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /contracts/interfaces/IRewardsBooster.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity ^0.8.15; 5 | 6 | interface IRewardsBooster { 7 | /// @notice Project Running Reward Pool 8 | struct DeploymentPool { 9 | // total booster for the deployment 10 | uint256 boosterPoint; 11 | // account => booster points staked 12 | mapping(address => uint256) accountBooster; 13 | // account => query rewards 14 | mapping(address => BoosterQueryReward) boosterQueryRewards; 15 | mapping(address => RunnerDeploymentReward) runnerAllocationRewards; 16 | // update when booster point changed, to calculate deployment rewards, including allocation and query reward 17 | uint256 accRewardsForDeployment; 18 | // update when allocation changed, so new allocation are not entitled for earlier rewards, for allocation only 19 | uint256 accRewardsForDeploymentSnapshot; 20 | // ?? not sure if needed?? update when booster point changed, including allocation and query reward 21 | uint256 accRewardsPerBooster; 22 | // update when booster changed, snapshot for claimed 23 | uint256 accRewardsPerBoosterSnapshot; 24 | // update when allocation changed, so new allocation are not entitled for earlier rewards 25 | uint256 accRewardsPerAllocatedToken; 26 | // update when booster changed, used to calc query booster rewards 27 | uint256 accQueryRewardsPerBooster; 28 | } 29 | 30 | struct BoosterQueryReward { 31 | // update when booster changed 32 | uint256 accQueryRewardsPerBoosterSnapshot; 33 | // add when booster change 34 | uint256 accQueryRewards; 35 | uint256 spentQueryRewards; 36 | } 37 | 38 | struct RunnerDeploymentReward { 39 | uint256 accRewardsPerToken; 40 | uint256 lastClaimedAt; 41 | uint256 overflowTimeSnapshot; 42 | // missedLabor 43 | uint256 missedLaborTime; 44 | uint256 lastMissedLaborReportAt; 45 | bool disabled; 46 | } 47 | 48 | function collectAllocationReward(bytes32 _deploymentId, address _runner) external; 49 | 50 | function spendQueryRewards( 51 | bytes32 _deploymentId, 52 | address _spender, 53 | uint256 _amount, 54 | bytes calldata data 55 | ) external returns (uint256); 56 | 57 | function refundQueryRewards( 58 | bytes32 _deploymentId, 59 | address _spender, 60 | uint256 _amount, 61 | bytes calldata data 62 | ) external; 63 | } 64 | -------------------------------------------------------------------------------- /.github/workflows/upgrade.yml: -------------------------------------------------------------------------------- 1 | name: 'Upgrade' 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths-ignore: 7 | - '.github/workflows/**' 8 | workflow_dispatch: 9 | inputs: 10 | endpoint: 11 | description: 'endpoint used to deploy contracts' 12 | default: https://polygon-mumbai.infura.io/v3/4458cf4d1689497b9a38b1d6bbf05e78 13 | required: true 14 | network: 15 | type: choice 16 | description: 'network used to publish contracts' 17 | default: testnet 18 | required: true 19 | options: 20 | - testnet 21 | 22 | jobs: 23 | Upgrade-Contracts: 24 | name: Upgrade-Contracts 25 | if: "startsWith(github.event.head_commit.message, '[upgrade-testnet]') && github.repository == 'subquery/network-contracts' || github.event_name == 'workflow_dispatch'" 26 | runs-on: ubuntu-latest 27 | env: 28 | SEED: ${{ secrets.SEED }} 29 | steps: 30 | #Check out 31 | - uses: actions/checkout@v2 32 | with: 33 | fetch-depth: 100 34 | 35 | - uses: actions/setup-node@v1 36 | with: 37 | node-version: 18 38 | registry-url: 'https://registry.npmjs.org' 39 | 40 | - run: corepack enable 41 | - run: yarn 42 | 43 | - name: Bump & Build 44 | run: npm version prerelease --no-git-tag-version && yarn build 45 | 46 | # Deploy contracts 47 | - name: Upgrade Changed Contracts 48 | run: yarn upgrade --${{ github.event.inputs.network }} && scripts/copy_deployment_json.sh # Uses SEED env var 49 | env: 50 | ENDPOINT: ${{ github.event.inputs.endpoint }} 51 | DEPLOY_PRINT: true 52 | 53 | - uses: JS-DevTools/npm-publish@v1 54 | name: Publish 55 | with: 56 | token: ${{ secrets.NPM_TOKEN }} 57 | tag: dev 58 | access: public 59 | package: build/build/package.json 60 | 61 | # Commit updated contract addresses, this should trigger the prerelease action 62 | - name: Commit changes 63 | uses: EndBug/add-and-commit@v5 64 | with: 65 | add: publish/*.json package.json 66 | message: '[SKIP CI] Deploy to testnet and Prerelease' 67 | env: 68 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 69 | -------------------------------------------------------------------------------- /test-fuzz/AirdropperEchidnaTest.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity ^0.8.15; 5 | 6 | import '../contracts/Airdropper.sol'; 7 | import "../contracts/root/SQToken.sol"; 8 | 9 | contract AirdropperEchidnaTest { 10 | Airdropper internal airdropper; 11 | SQToken internal SQT; 12 | 13 | constructor() { 14 | SQT = new SQToken(address(this)); 15 | airdropper = new Airdropper(); 16 | } 17 | 18 | // --- Math --- 19 | function add(uint256 x, uint256 y) internal pure returns (uint256 z) { 20 | z = x + y; 21 | assert(z >= x); // check if there is an addition overflow 22 | } 23 | 24 | function sub(uint256 x, uint256 y) internal pure returns (uint256 z) { 25 | z = x - y; 26 | assert(z <= x); // check if there is a subtraction overflow 27 | } 28 | 29 | function test_workflow(uint256 _start, uint256 _end, uint256 _amount) public { 30 | //test createRound 31 | uint256 roundId = test_createRound(_start, _end); 32 | //test batchAirdrop 33 | address[] memory addrs = new address[](1); 34 | addrs[0] = address(this); 35 | uint256[] memory roundIds = new uint256[](1); 36 | roundIds[0] = roundId; 37 | uint256[] memory amounts = new uint256[](1); 38 | amounts[0] = _amount; 39 | 40 | (, , , uint256 unclaimedAmountBefore) = airdropper.roundRecord(roundId); 41 | test_batchAirdrop(addrs, roundIds, amounts); 42 | (, , , uint256 unclaimedAmount) = airdropper.roundRecord(roundId); 43 | assert(unclaimedAmount == add(unclaimedAmountBefore, _amount)); 44 | } 45 | 46 | function test_createRound(uint256 _start, uint256 _end) public returns (uint256){ 47 | uint256 roundId = airdropper.createRound(address(SQT), add(block.timestamp, _start), add(block.timestamp, _end)); 48 | assert(roundId == sub(airdropper.nextRoundId(), 1)); 49 | (address tokenAddress, uint256 roundStartTime, uint256 roundDeadline, uint256 unclaimedAmount ) = airdropper.roundRecord(roundId); 50 | assert(tokenAddress == address(SQT)); 51 | assert(roundStartTime == add(block.timestamp, _start)); 52 | assert(roundDeadline == add(block.timestamp, _end)); 53 | assert(unclaimedAmount == 0); 54 | return roundId; 55 | } 56 | 57 | function test_batchAirdrop( 58 | address[] memory _addr, 59 | uint256[] memory _roundId, 60 | uint256[] memory _amount 61 | ) public { 62 | airdropper.batchAirdrop(_addr, _roundId, _amount); 63 | assert(airdropper.airdropRecord(_addr[0], _roundId[0]) == _amount[0]); 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /test/ConsumerRegistry.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | import { expect } from 'chai'; 5 | import { ethers, waffle } from 'hardhat'; 6 | import { ConsumerHost, ConsumerRegistry } from '../src'; 7 | import { deployContracts } from './setup'; 8 | 9 | describe('ConsumerRegistry Contract', () => { 10 | let wallet_0, wallet_1, wallet_2; 11 | let consumerRegistry: ConsumerRegistry; 12 | let consumerHost: ConsumerHost; 13 | 14 | const deployer = () => deployContracts(wallet_0, wallet_1); 15 | before(async () => { 16 | [wallet_0, wallet_1, wallet_2] = await ethers.getSigners(); 17 | }); 18 | 19 | beforeEach(async () => { 20 | const deployment = await waffle.loadFixture(deployer); 21 | consumerRegistry = deployment.consumerRegistry; 22 | consumerHost = deployment.consumerHost; 23 | }); 24 | 25 | describe('whitelist management', () => { 26 | it('add & remove consumer whitelist', async () => { 27 | const account = wallet_0.address; 28 | await expect(consumerRegistry.addWhitelist(account)).to.be.emit(consumerRegistry, 'WhitelistUpdated').withArgs(account, true); 29 | expect(await consumerRegistry.whitelist(account)).to.equal(true); 30 | 31 | await expect(consumerRegistry.removeWhitelist(account)).to.be.emit(consumerRegistry, 'WhitelistUpdated').withArgs(account, false); 32 | expect(await consumerRegistry.whitelist(account)).to.equal(false); 33 | }); 34 | }); 35 | 36 | describe('controller check', () => { 37 | it('add & remove common controller', async () => { 38 | await consumerRegistry.addController(wallet_0.address, wallet_1.address); 39 | expect(await consumerRegistry.isController(wallet_0.address, wallet_1.address)).to.equal(true); 40 | 41 | await consumerRegistry.removeController(wallet_0.address, wallet_1.address); 42 | expect(await consumerRegistry.isController(wallet_0.address, wallet_1.address)).to.equal(false); 43 | }); 44 | it('add & remove consumer controller', async () => { 45 | await consumerHost.addSigner(wallet_1.address); 46 | 47 | await consumerRegistry.connect(wallet_1).addController(consumerHost.address, wallet_2.address); 48 | expect(await consumerRegistry.isController(consumerHost.address, wallet_2.address)).to.equal(true); 49 | 50 | await consumerRegistry.connect(wallet_1).removeController(consumerHost.address, wallet_2.address); 51 | expect(await consumerRegistry.isController(consumerHost.address, wallet_2.address)).to.equal(false); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /test/fixtures/llm_project.yaml: -------------------------------------------------------------------------------- 1 | - kind: Project 2 | projectType: 4 # llm 3 | id: 53 4 | metadata: 5 | name: Gemma2 6 | description: Gemma2 is an open-weights large language model (LLM) developed by the Gemma team at Google DeepMind. 7 | websiteUrl: https://github.com/google/gemma 8 | codeUrl: https://github.com/google/gemma 9 | deployments: 10 | - deployment: | 11 | kind: LLM 12 | specVersion: "1.0.0" 13 | model: 14 | name: gemma2 15 | runner: 16 | name: ollama 17 | parameter: 18 | temperature: 1 19 | num_ctx: 4096 20 | system: You can call me SubQuery Helper. I am here to help you with your SubQuery projects. 21 | version: 22 | version: 1.0.0 23 | description: | 24 | Runner: ollama 25 | Parameter: temperature=1, num_ctx=4096 26 | - kind: Project 27 | projectType: 4 # llm 28 | metadata: 29 | name: llama3.1-8B 30 | description: Llama 3.1 is an open-weights large language model (LLM) developed by Meta AI. It's designed to be accessible, adaptable, and powerful, allowing researchers, developers, and individuals to explore the capabilities of artificial intelligence and build innovative applications. 31 | websiteUrl: https://ai.facebook.com/ 32 | codeUrl: https://ai.facebook.com/ 33 | deployments: 34 | - deployment: | 35 | kind: LLM 36 | specVersion: "1.0.0" 37 | model: 38 | name: llama3.1 39 | runner: 40 | name: ollama 41 | system: You can call me SubQuery Helper. I am here to help you with your SubQuery projects. 42 | version: 43 | version: 1.0.0 44 | description: | 45 | Runner: ollama 46 | Parameters: 8B 47 | - kind: Project 48 | projectType: 4 # llm 49 | metadata: 50 | name: Mistral-7B 51 | description: Mistral is an AI company known for developing advanced, efficient language models. They focus on creating powerful NLP models that are optimized for performance and accessible to a wide audience. Mistral is committed to open-source principles, allowing others to build on their work, and their models are versatile, suitable for various applications across industries. 52 | websiteUrl: https://mistral.ai 53 | codeUrl: https://mistral.ai 54 | deployments: 55 | - deployment: | 56 | kind: LLM 57 | specVersion: "1.0.0" 58 | model: 59 | name: mistral 60 | runner: 61 | name: ollama 62 | system: You can call me SubQuery Helper. I am here to help you with your SubQuery projects. 63 | version: 64 | version: 1.0.0 65 | description: | 66 | Runner: ollama 67 | Parameters: 7B -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: 'Deploy' 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths-ignore: 7 | - '.github/workflows/**' 8 | workflow_dispatch: 9 | inputs: 10 | endpoint: 11 | description: 'endpoint used to deploy contracts' 12 | default: https://polygon-mumbai.infura.io/v3/4458cf4d1689497b9a38b1d6bbf05e78 13 | required: true 14 | network: 15 | type: choice 16 | description: 'network used to publish contracts' 17 | default: testnet 18 | required: true 19 | options: 20 | - testnet 21 | 22 | jobs: 23 | Deploy-Contracts: 24 | name: Deploy-Contracts 25 | if: "startsWith(github.event.head_commit.message, '[deploy-testnet]') && github.repository == 'subquery/network-contracts' || github.event_name == 'workflow_dispatch'" 26 | runs-on: ubuntu-latest 27 | env: 28 | SEED: ${{ secrets.SEED }} 29 | steps: 30 | #Check out 31 | - uses: actions/checkout@v2 32 | with: 33 | fetch-depth: 100 34 | 35 | - uses: actions/setup-node@v1 36 | with: 37 | node-version: 18 38 | registry-url: 'https://registry.npmjs.org' 39 | 40 | - run: corepack enable 41 | - run: yarn 42 | 43 | - name: Bump & Build 44 | run: npm version prerelease --no-git-tag-version && yarn build 45 | 46 | # Deploy contracts 47 | - name: Deploy contracts 48 | run: yarn deploy --${{ github.event.inputs.network }} && scripts/copy_deployment_json.sh # Uses SEED env var 49 | env: 50 | ENDPOINT: ${{ github.event.inputs.endpoint }} 51 | DEPLOY_PRINT: true 52 | 53 | - name: Setup contracts 54 | run: yarn setup --${{ github.event.inputs.network }} 55 | 56 | - uses: JS-DevTools/npm-publish@v1 57 | name: Publish 58 | with: 59 | token: ${{ secrets.NPM_TOKEN }} 60 | tag: latest 61 | access: public 62 | package: build/build/package.json 63 | 64 | # Commit updated contract addresses, this should trigger the prerelease action 65 | - name: Commit changes 66 | uses: EndBug/add-and-commit@v5 67 | with: 68 | add: publish/*.json package.json 69 | message: '[SKIP CI] Deploy to testnet and Prerelease' 70 | env: 71 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 72 | -------------------------------------------------------------------------------- /test/fixtures/nfts.yaml: -------------------------------------------------------------------------------- 1 | - kind: SQTGiftSeries 2 | seriesId: 0 3 | maxSupply: 1000 4 | metadata: 5 | { 6 | 'description': 'This NFT grants the holder the right for USD $10 of SQT when the SQT token is distributed', 7 | 'external_url': 'https://subquery.network', 8 | 'image': 'https://static.subquery.network/token/sqt-nft/sqt-nft-10.png', 9 | 'name': 'SubQuery $10 SQT Airdrop', 10 | } 11 | - kind: SQTGiftSeries 12 | seriesId: 1 13 | maxSupply: 1000 14 | metadata: 15 | { 16 | 'description': 'This NFT grants the holder the right for USD $20 of SQT when the SQT token is distributed', 17 | 'external_url': 'https://subquery.network', 18 | 'image': 'https://static.subquery.network/token/sqt-nft/sqt-nft-20.png', 19 | 'name': 'SubQuery $20 SQT Airdrop', 20 | } 21 | - kind: SQTGiftSeries 22 | seriesId: 2 23 | maxSupply: 1000 24 | metadata: 25 | { 26 | 'description': 'This NFT grants the holder the right for USD $50 of SQT when the SQT token is distributed', 27 | 'external_url': 'https://subquery.network', 28 | 'image': 'https://static.subquery.network/token/sqt-nft/sqt-nft-50.png', 29 | 'name': 'SubQuery $50 SQT Airdrop', 30 | } 31 | - kind: SQTGiftSeries 32 | seriesId: 3 33 | maxSupply: 10000 34 | metadata: 35 | { 36 | 'name': 'SubQuery Genesis - Private Sale Participant', 37 | 'description': "This NFT is granted for all those that participated in SubQuery's private sale, helping us innovate in Web3 infrastructure so builders can decentralise the future.", 38 | 'external_url': 'https://subquery.network', 39 | 'image': 'https://static.subquery.network/token/sqt-nft/private-sale.png', 40 | } 41 | - kind: SQTGiftSeries 42 | seriesId: 4 43 | maxSupply: 20000 44 | metadata: 45 | { 46 | 'name': 'SubQuery Genesis - Public Sale Participant', 47 | 'description': "This NFT is granted for all those that participated in SubQuery's public sale, helping us innovate in Web3 infrastructure so builders can decentralise the future.", 48 | 'external_url': 'https://subquery.network', 49 | 'image': 'https://static.subquery.network/token/sqt-nft/public-sale.png', 50 | } 51 | #- kind: SQTGiftAllowList 52 | # seriesId: 2 53 | # list: 54 | # - address: '0xa27f6a138a98d250451c368cf977359609bab8ab' 55 | # amount: 1 56 | # - address: '0xdbf3e3cab815b6b2f86eb2dfd8b9e4a8a789544b' 57 | # amount: 1 58 | # - address: '0x30d14691b357568e53c69b9bea127f334c9f13cf' 59 | # amount: 1 60 | # - address: '0x5c2236dc7a282d722e5c6cc100133065e231c7ec' 61 | # amount: 1 62 | # - address: '0xee239f61d5b03ec7eca99c8ed5e1c8bdc77effc8' 63 | # amount: 1 64 | # - address: '0x951e90c02409f4b153c22b1fe3cb8a826b7379cf' 65 | # amount: 1 66 | -------------------------------------------------------------------------------- /contracts/root/OpDestination.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | pragma solidity 0.8.15; 4 | 5 | import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; 6 | import '@openzeppelin/contracts/utils/introspection/ERC165.sol'; 7 | import '@openzeppelin/contracts/access/Ownable.sol'; 8 | import '@eth-optimism/contracts/L1/messaging/IL1ERC20Bridge.sol'; 9 | 10 | import { IInflationDestination } from './IInflationDestination.sol'; 11 | import '../interfaces/ISettings.sol'; 12 | 13 | contract OpDestination is IInflationDestination, Ownable, ERC165 { 14 | /// @dev ### STATES 15 | 16 | /// @notice Address of the ERC20 on layer 1 chain 17 | address public l1Token; 18 | 19 | /// @notice Address of the ERC20 on layer 2 chain 20 | address public l2Token; 21 | 22 | /// @notice Address of the L1 token bridge contract 23 | address public l1StandardBridge; 24 | 25 | /// @notice Address of token recipient on layer 2 chain 26 | address public xcRecipient; 27 | 28 | constructor(address _l1Token, address _l2Token, address _l1StandardBridge) Ownable() { 29 | l1Token = _l1Token; 30 | l2Token = _l2Token; 31 | l1StandardBridge = _l1StandardBridge; 32 | } 33 | 34 | /** 35 | * @notice Set the address of token address on layer 2 chain 36 | * @param _l2Token Address of l2 token 37 | */ 38 | function setL2Token(address _l2Token) external onlyOwner { 39 | l2Token = _l2Token; 40 | } 41 | 42 | /** 43 | * @notice Set the address of token recipient on layer 2 chain 44 | * @param _xcRecipient Address of token recipient on layer 2 chain 45 | */ 46 | function setXcRecipient(address _xcRecipient) external onlyOwner { 47 | xcRecipient = _xcRecipient; 48 | } 49 | 50 | /** 51 | * @notice Check ERC165 interface 52 | * @param interfaceId interface ID 53 | * @return Result of support or not 54 | */ 55 | function supportsInterface( 56 | bytes4 interfaceId 57 | ) public view virtual override(ERC165) returns (bool) { 58 | return 59 | interfaceId == type(IInflationDestination).interfaceId || 60 | super.supportsInterface(interfaceId); 61 | } 62 | 63 | /** 64 | * @notice Deposit tokens to layer 2 chain recipient 65 | * @param amount Amount of tokens 66 | */ 67 | function afterReceiveInflatedTokens(uint256 amount) external { 68 | require(l2Token != address(0), 'OPD01'); 69 | ERC20(l1Token).increaseAllowance(l1StandardBridge, amount); 70 | IL1ERC20Bridge(l1StandardBridge).depositERC20To( 71 | l1Token, 72 | l2Token, 73 | xcRecipient, 74 | amount, 75 | 300000, 76 | new bytes(0) 77 | ); 78 | } 79 | 80 | function withdraw(address _token) external onlyOwner { 81 | uint256 amount = ERC20(_token).balanceOf(address(this)); 82 | ERC20(_token).transfer(owner(), amount); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/rootSdk.ts: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | import type { Provider as AbstractProvider } from '@ethersproject/abstract-provider'; 5 | import { Signer } from 'ethers'; 6 | import { DEPLOYMENT_DETAILS } from './deployments'; 7 | import { AirdropperLite, ERC20, InflationController, SQToken, Vesting, OpDestination } from './typechain'; 8 | import { CONTRACT_FACTORY, ContractDeploymentInner, ContractName, FactoryContstructor, SdkOptions } from './types'; 9 | import assert from 'assert'; 10 | 11 | // HOTFIX: Contract names are not consistent between deployments and privous var names 12 | const contractNameConversion: Record = { 13 | sQToken: 'sqToken', 14 | vTSQToken: 'vtSQToken', 15 | opDestination: 'inflationDestination', 16 | }; 17 | 18 | const ROOT_CONTRACTS = ['SQToken', 'Vesting', 'VTSQToken', 'AirdropperLite']; 19 | 20 | export class RootContractSDK { 21 | private _contractDeployments: ContractDeploymentInner; 22 | private _signerOrProvider: AbstractProvider | Signer; 23 | 24 | readonly sqToken!: SQToken; 25 | readonly vtSQToken!: ERC20; 26 | readonly vesting!: Vesting; 27 | readonly inflationController!: InflationController; 28 | readonly inflationDestination!: OpDestination; 29 | readonly airdropperLite!: AirdropperLite; 30 | 31 | constructor( 32 | private readonly signerOrProvider: AbstractProvider | Signer, 33 | public readonly options: SdkOptions 34 | ) { 35 | assert( 36 | this.options.deploymentDetails || DEPLOYMENT_DETAILS[options.network], 37 | ' missing contract deployment info' 38 | ); 39 | this._contractDeployments = this.options.deploymentDetails ?? DEPLOYMENT_DETAILS[options.network]!.root; 40 | this._signerOrProvider = signerOrProvider; 41 | this._init(); 42 | } 43 | 44 | static create(signerOrProvider: AbstractProvider | Signer, options: SdkOptions) { 45 | return new RootContractSDK(signerOrProvider, options); 46 | } 47 | 48 | private async _init() { 49 | const contracts = Object.entries(this._contractDeployments).map(([name, contract]) => ({ 50 | address: contract.address, 51 | factory: CONTRACT_FACTORY[name as ContractName] as FactoryContstructor, 52 | name: name as ContractName, 53 | })); 54 | 55 | for (const { name, factory, address } of contracts) { 56 | if (!factory) continue; 57 | const contractInstance = factory.connect(address, this._signerOrProvider); 58 | if (contractInstance) { 59 | const key = name.charAt(0).toLowerCase() + name.slice(1); 60 | const contractName = contractNameConversion[key] ?? key; 61 | Object.defineProperty(this, contractName, { 62 | get: () => contractInstance, 63 | }); 64 | } else { 65 | throw new Error(`${name} contract not found`); 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /test/VSQToken.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | import { expect } from 'chai'; 5 | import { ethers, waffle } from 'hardhat'; 6 | 7 | import { EraManager, IndexerRegistry, ERC20, Staking, StakingManager, VSQToken } from '../src'; 8 | import { etherParse, registerRunner, startNewEra } from './helper'; 9 | import { deployContracts } from './setup'; 10 | 11 | describe('VSQToken Contract', () => { 12 | let root, runner, runner2, delegator; 13 | let token: ERC20; 14 | let staking: Staking; 15 | let stakingManager: StakingManager; 16 | let eraManager: EraManager; 17 | let indexerRegistry: IndexerRegistry; 18 | let vtoken: VSQToken; 19 | 20 | const amount = '2000'; 21 | 22 | const deployer = () => deployContracts(root, root); 23 | before(async () => { 24 | [root, runner, runner2, delegator] = await ethers.getSigners(); 25 | }); 26 | 27 | beforeEach(async () => { 28 | const deployment = await waffle.loadFixture(deployer); 29 | token = deployment.token; 30 | staking = deployment.staking; 31 | stakingManager = deployment.stakingManager; 32 | eraManager = deployment.eraManager; 33 | indexerRegistry = deployment.indexerRegistry; 34 | vtoken = deployment.vtoken; 35 | 36 | //register indexer1: Indexer1 balance: 10 sqt, Indexer1 staked amount: 10 sqt 37 | await registerRunner(token, indexerRegistry, staking, root, runner, etherParse(amount)); 38 | //register indexer2: Indexer2 balance: 10 sqt, Indexer2 staked amount: 10 sqt 39 | await registerRunner(token, indexerRegistry, staking, root, runner2, etherParse(amount)); 40 | //setup delegator: delegator balance: 15 sqt 41 | await token.connect(root).transfer(delegator.address, etherParse('15')); 42 | await token.connect(delegator).increaseAllowance(staking.address, etherParse('15')); 43 | }); 44 | 45 | it('get balance of VSQT Token should work', async () => { 46 | await stakingManager.connect(delegator).delegate(runner.address, etherParse('5')); 47 | await stakingManager.connect(delegator).delegate(runner2.address, etherParse('5')); 48 | expect(await token.balanceOf(runner.address)).to.equal(0); 49 | expect(await vtoken.balanceOf(runner.address)).to.equal(etherParse(amount)); 50 | expect(await token.balanceOf(runner2.address)).to.equal(0); 51 | expect(await vtoken.balanceOf(runner2.address)).to.equal(etherParse(amount)); 52 | expect(await token.balanceOf(delegator.address)).to.equal(etherParse('5')); 53 | expect(await vtoken.balanceOf(delegator.address)).to.equal(etherParse('15')); 54 | 55 | await stakingManager.connect(delegator).undelegate(runner.address, etherParse('2')); 56 | await startNewEra(eraManager); 57 | expect(await vtoken.balanceOf(delegator.address)).to.equal(etherParse('15')); 58 | await stakingManager.connect(delegator).widthdraw(); 59 | expect(await token.balanceOf(delegator.address)).to.equal(etherParse('6.998')); 60 | expect(await vtoken.balanceOf(delegator.address)).to.equal(etherParse('14.998')); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /.github/workflows/fuzz.yml: -------------------------------------------------------------------------------- 1 | name: Fuzz Test 2 | on: 3 | push: 4 | branches: 5 | - kepler/fuzz 6 | paths-ignore: 7 | - '.github/workflows/**' 8 | workflow_dispatch: 9 | inputs: 10 | testLimit: 11 | description: 'Number of sequences of transactions to generate during testing' 12 | default: 50000 13 | required: true 14 | seqLen: 15 | description: 'Number of transactions to generate during testing' 16 | default: 100 17 | required: true 18 | 19 | jobs: 20 | echidna: 21 | name: Echidna 22 | runs-on: ubuntu-latest 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | testName: 27 | - PermissionedExchangeEchidnaTest 28 | - AirdropperEchidnaTest 29 | 30 | steps: 31 | - uses: actions/checkout@v2 32 | 33 | - name: Set up node 34 | uses: actions/setup-node@v2 35 | with: 36 | node-version: 18 37 | 38 | - run: corepack enable 39 | - run: yarn 40 | 41 | - name: Set up Python 3.8 42 | uses: actions/setup-python@v3 43 | with: 44 | python-version: 3.8 45 | 46 | - name: Install crytic-compile 47 | run: pip3 install crytic-compile 48 | 49 | - name: Install solc-select 50 | run: pip3 install solc-select 51 | 52 | - name: Solc Select 0.8.15 53 | run: | 54 | solc-select install 0.8.15 55 | solc-select use 0.8.15 56 | - name: Crytic Compile ${{ matrix.testName }} 57 | run: | 58 | crytic-compile test-fuzz/${{ matrix.testName }}.sol --solc-args "--optimize --optimize-runs 200" --export-format solc 59 | jq --sort-keys . crytic-export/combined_solc.json > sorted_crytic_solc.json 60 | 61 | - name: Cache ${{ matrix.testName }} Corpus 62 | uses: actions/cache@v2 63 | with: 64 | path: corpus 65 | key: abi-${{ matrix.testName }}-${{ hashFiles('**/sorted_crytic_solc.json') }}-v3 66 | 67 | - name: Fuzz ${{ matrix.testName }} 68 | uses: crytic/echidna-action@v2 69 | with: 70 | files: test-fuzz/${{ matrix.testName }}.sol 71 | contract: ${{ matrix.testName }} 72 | config: echidna.config.ci.yml 73 | corpus-dir: corpus 74 | test-mode: assertion 75 | test-limit: ${{ github.event.inputs.testLimit }} 76 | seq-len: ${{ github.event.inputs.seqLen }} 77 | solc-args: --optimize --optimize-runs 200 78 | solc-version: 0.8.15 79 | echidna-version: v2.0.0 80 | 81 | - name: Upload ${{ matrix.testName }} Coverage 82 | uses: actions/upload-artifact@v2 83 | with: 84 | name: coverage-${{ matrix.testName }} 85 | path: corpus/covered.* 86 | -------------------------------------------------------------------------------- /contracts/SQTRedeem.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | import '@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol'; 7 | import '@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol'; 8 | import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol'; 9 | import '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol'; 10 | import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; 11 | 12 | import './interfaces/ISQTGift.sol'; 13 | import './utils/SQParameter.sol'; 14 | 15 | contract SQTRedeem is Initializable, OwnableUpgradeable, SQParameter { 16 | address public sqtoken; 17 | 18 | /// @notice redeemable status of the contract 19 | bool public redeemable; 20 | 21 | /// @notice nft address => seriesId => redeemable amount for each NFT in the series 22 | mapping(address => mapping(uint256 => uint256)) public redeemableAmount; 23 | 24 | event SQTRedeemed( 25 | address indexed to, 26 | uint256 indexed tokenId, 27 | uint256 seriesId, 28 | address nft, 29 | uint256 sqtValue 30 | ); 31 | 32 | function initialize(address _sqtoken) external initializer { 33 | __Ownable_init(); 34 | 35 | sqtoken = _sqtoken; 36 | emit Parameter('redeemable', abi.encodePacked(false)); 37 | } 38 | 39 | function deposit(uint256 amount) public onlyOwner { 40 | require(IERC20(sqtoken).transferFrom(msg.sender, address(this), amount), 'SQR001'); 41 | } 42 | 43 | function withdraw(uint256 amount) public onlyOwner { 44 | require(IERC20(sqtoken).transfer(msg.sender, amount), 'SQR001'); 45 | } 46 | 47 | function setRedeemable(bool _redeemable) external onlyOwner { 48 | redeemable = _redeemable; 49 | emit Parameter('redeemable', abi.encodePacked(redeemable)); 50 | } 51 | 52 | function setRedeemableAmount(address nft, uint256 seriesId, uint256 amount) public onlyOwner { 53 | // this is to set the redeemable amount for per NFT in the series 54 | redeemableAmount[nft][seriesId] = amount; 55 | } 56 | 57 | function redeem(address nft, uint256 tokenId) public { 58 | require(redeemable, 'SQR002'); 59 | 60 | IERC165Upgradeable nftContract = IERC165Upgradeable(nft); 61 | require(nftContract.supportsInterface(type(ISQTGift).interfaceId), 'SQR003'); 62 | 63 | ISQTGift sqtGift = ISQTGift(nft); 64 | require(sqtGift.ownerOf(tokenId) == msg.sender, 'SQR005'); 65 | 66 | uint256 seriesId = sqtGift.getSeries(tokenId); 67 | uint256 sqtValue = redeemableAmount[nft][seriesId]; 68 | require(sqtValue > 0, 'SQR004'); 69 | 70 | ERC721BurnableUpgradeable(nft).burn(tokenId); 71 | require(IERC20(sqtoken).transfer(msg.sender, sqtValue), 'SQR001'); 72 | 73 | emit SQTRedeemed(msg.sender, tokenId, seriesId, nft, sqtValue); 74 | } 75 | 76 | function batchRedeem(address[] calldata _nfts, uint256[] calldata _tokenIds) public { 77 | require(_nfts.length == _tokenIds.length, 'G020'); 78 | for (uint256 i = 0; i < _nfts.length; i++) { 79 | redeem(_nfts[i], _tokenIds[i]); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /contracts/l2/UniswapPriceOracle.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | import '@openzeppelin/contracts/access/Ownable.sol'; 7 | import '../external/IQuoter.sol'; 8 | import '../interfaces/IPriceOracle.sol'; 9 | 10 | interface IUniswapV3Pool { 11 | function slot0() 12 | external 13 | view 14 | returns ( 15 | uint160 sqrtPriceX96, 16 | int24 tick, 17 | uint16 observationIndex, 18 | uint16 observationCardinality, 19 | uint16 observationCardinalityNext, 20 | uint8 feeProtocol, 21 | bool unlocked 22 | ); 23 | 24 | function token0() external view returns (address); 25 | function token1() external view returns (address); 26 | } 27 | 28 | contract UniswapPriceOracle is Ownable, IPriceOracle { 29 | IQuoter public quoter; // 0x222ca98f00ed15b1fae10b61c277703a194cf5d2, https://github.com/Uniswap/view-quoter-v3/tree/master 30 | mapping(bytes32 => uint24) public poolFees; 31 | 32 | constructor(address _quoterAddress) Ownable() { 33 | quoter = IQuoter(_quoterAddress); 34 | } 35 | 36 | function setQuoter(address _quoterAddress) external onlyOwner { 37 | quoter = IQuoter(_quoterAddress); 38 | } 39 | 40 | function setPoolFee(address fromToken, address toToken, uint24 _poolFee) external onlyOwner { 41 | bytes32 poolkey = _concatAddresses(fromToken, toToken); 42 | poolFees[poolkey] = _poolFee; 43 | } 44 | 45 | function getPoolFee(address fromToken, address toToken) public view returns (uint24) { 46 | bytes32 poolkey = _concatAddresses(fromToken, toToken); 47 | return poolFees[poolkey] == 0 ? 3000 : poolFees[poolkey]; 48 | } 49 | 50 | function getAssetPrice( 51 | address fromToken, 52 | address toToken 53 | ) external view override returns (uint256) { 54 | return _convertPrice(fromToken, toToken, 1); 55 | } 56 | 57 | function convertPrice( 58 | address fromToken, 59 | address toToken, 60 | uint256 amount 61 | ) external view override returns (uint256) { 62 | require(amount > 0, 'Amount must be greater than 0'); 63 | return _convertPrice(fromToken, toToken, amount); 64 | } 65 | 66 | // usdc: 0x833589fcd6edb6e08f4c7c32d4f71b54bda02913 67 | // sqt: 0x858c50c3af1913b0e849afdb74617388a1a5340d 68 | 69 | function _convertPrice( 70 | address fromToken, 71 | address toToken, 72 | uint256 amount 73 | ) internal view returns (uint256) { 74 | // Simulate the swap using the QuoterV2 contract 75 | IQuoter.QuoteExactInputSingleParams memory params = IQuoter.QuoteExactInputSingleParams({ 76 | tokenIn: fromToken, 77 | tokenOut: toToken, 78 | amountIn: amount, 79 | fee: getPoolFee(fromToken, toToken), 80 | sqrtPriceLimitX96: 0 // No price limit 81 | }); 82 | 83 | (uint256 amountOut, , , ) = quoter.quoteExactInputSingle(params); 84 | 85 | return amountOut; 86 | } 87 | 88 | function _concatAddresses(address addr1, address addr2) internal pure returns (bytes32) { 89 | if (addr1 > addr2) { 90 | (addr1, addr2) = (addr2, addr1); 91 | } 92 | return (bytes32(bytes20(addr1)) << 96) | bytes32(bytes20(addr2)); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /scripts/config/startup.mainnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "planTemplates": [ 3 | { 4 | "period": 129600, 5 | "dailyReqCap": 100000, 6 | "rateLimit": 6000, 7 | "token": "0xed3bb617dbC128095f8b0A00B9498C2Ef5c3D04a" 8 | }, 9 | { 10 | "period": 86400, 11 | "dailyReqCap": 50000, 12 | "rateLimit": 3000, 13 | "token": "0xed3bb617dbC128095f8b0A00B9498C2Ef5c3D04a" 14 | }, 15 | { 16 | "period": 43200, 17 | "dailyReqCap": 10000, 18 | "rateLimit": 1200, 19 | "token": "0xed3bb617dbC128095f8b0A00B9498C2Ef5c3D04a" 20 | }, 21 | { 22 | "period": 21600, 23 | "dailyReqCap": 2000, 24 | "rateLimit": 600, 25 | "token": "0xed3bb617dbC128095f8b0A00B9498C2Ef5c3D04a" 26 | }, 27 | { "period": 7200, "dailyReqCap": 100, "rateLimit": 300, "token": "0xed3bb617dbC128095f8b0A00B9498C2Ef5c3D04a" } 28 | ], 29 | "QRCreator": ["0x293a6d85DD0d7d290A719Fdeef43FaD10240bA77", "0x301ce005Ea3f7d8051462E060f53d84Ee898dFDe"], 30 | "AirdropController": ["0x293a6d85DD0d7d290A719Fdeef43FaD10240bA77", "0x301ce005Ea3f7d8051462E060f53d84Ee898dFDe"], 31 | "multiSign": "0x293a6d85DD0d7d290A719Fdeef43FaD10240bA77", 32 | "projects": [ 33 | { 34 | "name": "Polkadot Dictionary", 35 | "deploymentId": "QmSjjRjfjXXEfSUTheNwvWcBaH54pWoToTHPDsJRby955X", 36 | "deploymentMetadata": "QmNr5DAJ7dQ51hB8J2jeQeSvvGwUQ8dFQYojvu9syMqMoQ", 37 | "projectMetadata": "QmQMadZByP9RHeULtpJDH3qNBVGPv5CsawGaZV2vUSFvYE" 38 | }, 39 | { 40 | "name": "Avalanche Dictionary", 41 | "deploymentId": "QmaMetzawjqpAcNTv6nzF88yFHVCpLE4h5goxvayLn9Yxj", 42 | "deploymentMetadata": "QmNr5DAJ7dQ51hB8J2jeQeSvvGwUQ8dFQYojvu9syMqMoQ", 43 | "projectMetadata": "QmZTh1vfdFrRfVBDYyttDen7GZFhRC5NWGzCHA4YWyqXCr" 44 | }, 45 | { 46 | "name": "Moonbeam Dictionary", 47 | "deploymentId": "QmPHi8Y6bnLdidDjsd2sjgBdyEbwBu5s5QxN4n78zYpxJq", 48 | "deploymentMetadata": "QmNr5DAJ7dQ51hB8J2jeQeSvvGwUQ8dFQYojvu9syMqMoQ", 49 | "projectMetadata": "QmQ4cBHRabyy3ExkvfVDFHZSneF5j8ySf843zMifzfY7Cj" 50 | }, 51 | { 52 | "name": "Moonriver Dictionary", 53 | "deploymentId": "QmQnhLMgV3SrXbunjgHjnfdw32BAHQS3nNhLWKpNjtFTSZ", 54 | "deploymentMetadata": "QmNr5DAJ7dQ51hB8J2jeQeSvvGwUQ8dFQYojvu9syMqMoQ", 55 | "projectMetadata": "Qmdvn23qiTYNEBaJ5BwzaDBESXvzG3mrPtWuK9zajkXvLn" 56 | }, 57 | { 58 | "name": "Juno Dictionary", 59 | "deploymentId": "QmPP9XLoquWhUc8fyrTBfaaPzz4VR65nsVvzYBcPX1DoUz", 60 | "deploymentMetadata": "QmNr5DAJ7dQ51hB8J2jeQeSvvGwUQ8dFQYojvu9syMqMoQ", 61 | "projectMetadata": "QmZ9RitNZKhcguQnaswzkWvPZqSDgxE5munMqAfjbKgZbd" 62 | }, 63 | { 64 | "name": "Acala Dictionary", 65 | "deploymentId": "QmYH4KFV9yeLnbWwswoqLBLH6Z69ae2Snbyrs218cpPs1K", 66 | "deploymentMetadata": "QmNr5DAJ7dQ51hB8J2jeQeSvvGwUQ8dFQYojvu9syMqMoQ", 67 | "projectMetadata": "QmZPBEJu4q89T2UwxrDw1TkQ8UASAS7JyE4xuy1HMvw1NR" 68 | }, 69 | { 70 | "name": "Kusama Dictionary", 71 | "deploymentId": "QmPemHcmAJ6BRyV13FN91miLCHNtqXLLacsqYjSaTmbFmr", 72 | "deploymentMetadata": "QmNr5DAJ7dQ51hB8J2jeQeSvvGwUQ8dFQYojvu9syMqMoQ", 73 | "projectMetadata": "Qmcf1Yp3NeLkfM78KjcK4JKdtY7N2eMEodCFKd1ZyQUdAX" 74 | } 75 | ] 76 | } 77 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | ## Contracts API 2 | The contracts are upgradable, following the Open Zeppelin Proxy Upgrade Pattern. Each contract will be explained in brief detail below. 3 | 4 | [**_ConsumerHost_**](./contracts/ConsumerHost.md) 5 | > Host services for consumers, allowing consumers to use the PAYG faster and better. 6 | 7 | [**_EraManager_**](./contracts/EraManager.md) 8 | > Produce epochs based on a period to coordinate contracts. 9 | 10 | [**_IndexerRegistry_**](./contracts/IndexerRegistry.md) 11 | > The IndexerRegistry contract store and track all registered Indexers and related status for these Indexers. It also provide the entry for Indexers to register, unregister, and config their metedata. 12 | 13 | [**_InflationController_**](./contracts/InflationController.md) 14 | > The InflationController contract mint the inflation SQT token to a set address at a set inflation rate. It also provide the manual way to mint SQT Token to admin. 15 | 16 | [**_PermissionedExchange_**](./contracts/PermissionedExchange.md) 17 | > For now, PermissionedExchange contract allows traders trade their SQTs on admin sent orders, later on we may allow others to send their orders. Controllers may set the trade quota for trader, and trader cannot trade over the their quota. It provides a way for indexers to swap their rewards(SQT) to stable token with a fixed exchange rate. 18 | 19 | [**_PlanManager_**](./contracts/PlanManager.md) 20 | > The Plan Manager Contract tracks and maintains all the Plans and PlanTemplates. It is the place Indexer create and publish a Plan for a specific deployment. And also the place Consumer can search and take these Plan. 21 | 22 | [**_PurchaseOfferMarket_**](./contracts/PurchaseOfferMarket.md) 23 | > The Purchase Offer Market Contract tracks all purchase offers for Indexers and Consumers. It allows Consumers to create/cancel purchase offers, and Indexers to accept the purchase offer to make the service agreements. It is the place Consumer publish a purchase offer for a specific deployment. And also the place indexers can search and take these purchase offers. 24 | 25 | [**_ProjectRegistry_**](./contracts/ProjectRegistry.md) 26 | > This contract tracks all query projects and their deployments. At the beginning of the network, we will start with the restrict mode which only allow permissioned account to create and update query project. Indexers are able to start and stop indexing with a specific deployment from this conttact. Also Indexers can update and report their indexing status from this contarct. 27 | 28 | [**_RewardsDistributer_**](./contracts/RewardsDistributer.md) 29 | > The Rewards distributer contract tracks and distriubtes the rewards Era by Era. In each distribution, Indexers can take the commission part of rewards, the remaining rewards are distributed according to the staking amount of indexers and delegators. 30 | 31 | [**_RewardsHelper_**](./contracts/RewardsHelper.md) 32 | > 33 | 34 | [**_RewardsPool_**](./contracts/RewardsPool.md) 35 | > The Rewards Pool using the Cobb-Douglas production function for PAYG and Open Agreement. 36 | 37 | [**_RewardsStaking_**](./contracts/RewardsStaking.md) 38 | > Keep tracing the pending staking and commission rate and last settled era. 39 | 40 | [**_ServiceAgreementRegistry_**](./contracts/ServiceAgreementRegistry.md) 41 | > This contract tracks all service Agreements for Indexers and Consumers. 42 | 43 | [**_SQToken_**](./contracts/SQToken.md) 44 | > 45 | 46 | [**_Staking_**](./contracts/Staking.md) 47 | > The Staking contract hold and track the changes of all staked SQT Token, It provides entry for the indexers and delegators to stake/unstake, delegate/undelegate to available Indexers and withdraw their SQT Token. 48 | 49 | [**_StateChannel_**](./contracts/StateChannel.md) 50 | > The contact for Pay-as-you-go service for Indexer and Consumer. 51 | 52 | [**_Vesting_**](./contracts/Vesting.md) 53 | > 54 | 55 | [**_VSQToken_**](./contracts/VSQToken.md) 56 | > 57 | -------------------------------------------------------------------------------- /contracts/PriceOracle.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol'; 7 | import '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol'; 8 | 9 | import './interfaces/IPriceOracle.sol'; 10 | import './utils/SQParameter.sol'; 11 | 12 | contract PriceOracle is IPriceOracle, Initializable, OwnableUpgradeable, SQParameter { 13 | ///@notice the price of assetTo in assetFrom 14 | mapping(address => mapping(address => uint256)) public prices; 15 | 16 | ///@notice the size limit when controller change price 17 | uint256 public sizeLimit; 18 | 19 | ///@notice the block number limit when controller change price 20 | uint256 public blockLimit; 21 | 22 | ///@notice the block number of latest set price 23 | uint256 public latestPriceBlock; 24 | 25 | uint256 public enlargementFactor; 26 | 27 | ///@notice the controller account which can change price 28 | address public controller; 29 | 30 | function initialize(uint256 _sizeLimit, uint256 _blockLimit) external initializer { 31 | __Ownable_init(); 32 | 33 | sizeLimit = _sizeLimit; 34 | blockLimit = _blockLimit; 35 | enlargementFactor = 1e6; 36 | emit Parameter('sizeLimit', abi.encodePacked(sizeLimit)); 37 | emit Parameter('blockLimit', abi.encodePacked(blockLimit)); 38 | } 39 | 40 | event PricePosted(address assetFrom, address assetTo, uint256 previousPrice, uint256 newPrice); 41 | 42 | ///@notice update change price limit 43 | function setLimit(uint256 _sizeLimit, uint256 _blockLimit) public onlyOwner { 44 | sizeLimit = _sizeLimit; 45 | blockLimit = _blockLimit; 46 | emit Parameter('sizeLimit', abi.encodePacked(sizeLimit)); 47 | emit Parameter('blockLimit', abi.encodePacked(blockLimit)); 48 | } 49 | 50 | ///@notice update the controller account 51 | function setController(address _controller) public onlyOwner { 52 | controller = _controller; 53 | } 54 | 55 | ///@notice get the price of assetTo in assetFrom 56 | function getAssetPrice(address assetFrom, address assetTo) public view returns (uint256) { 57 | uint256 price = prices[assetFrom][assetTo]; 58 | require(price > 0, 'OR001'); 59 | return price; 60 | } 61 | 62 | ///set the price of assetTo in assetFrom 63 | ///use enlargementFactor 64 | ///Thus, if we wanted set 1 USDC (decimal=6) = 13 SQT(decimal=18) The price be 13e(18-6+6) 65 | ///@param assetFrom priceToken 66 | ///@param assetTo sqtToken 67 | function setAssetPrice( 68 | address assetFrom, 69 | address assetTo, 70 | uint256 assetFromAmount, 71 | uint256 assetToAmount 72 | ) public { 73 | uint256 prePrice = prices[assetFrom][assetTo]; 74 | uint256 price = (assetToAmount * enlargementFactor) / assetFromAmount; 75 | if (msg.sender == controller) { 76 | require(latestPriceBlock + blockLimit < block.number, 'OR002'); 77 | 78 | uint256 priceChanged = prePrice > price ? prePrice - price : price - prePrice; 79 | uint256 sizeChanged = (priceChanged * 100) / prePrice; 80 | 81 | require(sizeChanged < sizeLimit, 'OR003'); 82 | } else { 83 | require(msg.sender == owner(), 'OR004'); 84 | } 85 | 86 | latestPriceBlock = block.number; 87 | prices[assetFrom][assetTo] = price; 88 | emit PricePosted(assetFrom, assetTo, prePrice, price); 89 | } 90 | 91 | function convertPrice( 92 | address fromToken, 93 | address toToken, 94 | uint256 amount 95 | ) public view returns (uint256) { 96 | if (fromToken == toToken) { 97 | return amount; 98 | } 99 | uint256 assetPrice = getAssetPrice(fromToken, toToken); 100 | return (amount * assetPrice) / enlargementFactor; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/networks.ts: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | import { NetworkPair, SubqueryNetwork } from './types'; 5 | 6 | export const CURRENT_NETWORK = 'testnet'; 7 | 8 | // eslint-disable-next-line no-unused-vars 9 | export const networks: { [key in SubqueryNetwork]: NetworkPair } = { 10 | mainnet: { 11 | root: { 12 | chainId: '0x1', 13 | chainName: 'Ethereum Mainnet', 14 | rpcUrls: ['https://ethereum.publicnode.com'], 15 | iconUrls: [], 16 | blockExplorerUrls: ['https://etherscan.io'], 17 | nativeCurrency: { 18 | name: 'ETH', 19 | symbol: 'ETH', 20 | decimals: 18, 21 | }, 22 | }, 23 | child: { 24 | chainId: '0x2105', 25 | chainName: 'Base', 26 | rpcUrls: ['https://mainnet.base.org'], 27 | iconUrls: ['https://images.mirror-media.xyz/publication-images/cgqxxPdUFBDjgKna_dDir.png'], 28 | blockExplorerUrls: ['https://basescan.org'], 29 | nativeCurrency: { 30 | name: 'ETH', 31 | symbol: 'ETH', 32 | decimals: 18, 33 | }, 34 | }, 35 | }, 36 | 'testnet-mumbai': { 37 | root: { 38 | chainId: '0x5', 39 | chainName: 'Goerli', 40 | rpcUrls: ['https://ethereum-goerli.publicnode.com', 'https://rpc.ankr.com/eth_goerli'], 41 | iconUrls: [], 42 | blockExplorerUrls: ['https://goerli.etherscan.io'], 43 | nativeCurrency: { 44 | name: 'ETH', 45 | symbol: 'ETH', 46 | decimals: 18, 47 | }, 48 | }, 49 | child: { 50 | chainId: '0x13881', 51 | chainName: 'Mumbai', 52 | rpcUrls: ['https://rpc-mumbai.maticvigil.com', 'https://rpc.ankr.com/polygon_mumbai'], 53 | iconUrls: ['https://icons.llamao.fi/icons/chains/rsz_polygon.jpg'], 54 | blockExplorerUrls: ['https://mumbai.polygonscan.com/'], 55 | nativeCurrency: { 56 | name: 'Matic Token', 57 | symbol: 'MATIC', 58 | decimals: 18, 59 | }, 60 | }, 61 | }, 62 | testnet: { 63 | root: { 64 | chainId: '0xaa36a7', 65 | chainName: 'Sepolia', 66 | rpcUrls: ['https://rpc.sepolia.org', 'https://eth-sepolia.public.blastapi.io'], 67 | iconUrls: [], 68 | blockExplorerUrls: ['https://sepolia.etherscan.io'], 69 | nativeCurrency: { 70 | name: 'ETH', 71 | symbol: 'ETH', 72 | decimals: 18, 73 | }, 74 | }, 75 | child: { 76 | chainId: '0x14a34', 77 | chainName: 'Base Sepolia', 78 | rpcUrls: ['https://sepolia.base.org'], 79 | iconUrls: [], 80 | blockExplorerUrls: ['https://sepolia.basescan.org'], 81 | nativeCurrency: { 82 | name: 'ETH', 83 | symbol: 'ETH', 84 | decimals: 18, 85 | }, 86 | }, 87 | }, 88 | local: { 89 | root: { 90 | chainId: '0x7A69', 91 | chainName: 'Hardhat', 92 | rpcUrls: ['http://127.0.0.1:8545'], 93 | iconUrls: [], 94 | blockExplorerUrls: [''], 95 | nativeCurrency: { 96 | name: '', 97 | symbol: '', 98 | decimals: 18, 99 | }, 100 | }, 101 | child: { 102 | chainId: '0x7A69', 103 | chainName: 'Hardhat', 104 | rpcUrls: ['http://127.0.0.1:8545'], 105 | iconUrls: [], 106 | blockExplorerUrls: [''], 107 | nativeCurrency: { 108 | name: '', 109 | symbol: '', 110 | decimals: 18, 111 | }, 112 | }, 113 | }, 114 | }; 115 | -------------------------------------------------------------------------------- /test/PriceOracle.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | import { expect } from 'chai'; 5 | import { ethers, waffle } from 'hardhat'; 6 | import { PriceOracle } from '../src'; 7 | import { blockTravel } from './helper'; 8 | import { deployContracts } from './setup'; 9 | 10 | describe('PriceOracle Contract', () => { 11 | let wallet_0, wallet_1, wallet_2; 12 | let priceOracle: PriceOracle; 13 | const assetFrom = '0x0000000000000000000000000000000000000000'; 14 | const assetTo = '0x0000000000000000000000000000000000000001'; 15 | const price = 100; // 1 assetA to 100 assetB 16 | 17 | const deployer = () => deployContracts(wallet_0, wallet_1); 18 | before(async () => { 19 | [wallet_0, wallet_1, wallet_2] = await ethers.getSigners(); 20 | }); 21 | 22 | beforeEach(async () => { 23 | const deployment = await waffle.loadFixture(deployer); 24 | priceOracle = deployment.priceOracle; 25 | await priceOracle.setAssetPrice(assetFrom, assetTo, 1, price); 26 | }); 27 | 28 | describe('update paramsters', () => { 29 | it('change price change limit', async () => { 30 | await priceOracle.setLimit(10, 100); 31 | expect(await priceOracle.sizeLimit()).to.equal(10); 32 | expect(await priceOracle.blockLimit()).to.equal(100); 33 | }); 34 | it('change controller', async () => { 35 | await priceOracle.setController(wallet_1.address); 36 | expect(await priceOracle.controller()).to.equal(wallet_1.address); 37 | }); 38 | }); 39 | describe('convert price', () => { 40 | it('can convert as price specified', async () => { 41 | const fromPrice = 1e12; 42 | const price = 100; 43 | const enlargement = await priceOracle.enlargementFactor(); 44 | expect(await priceOracle.getAssetPrice(assetFrom, assetTo)).to.eq(enlargement.mul(price)); 45 | const toPrice = await priceOracle.convertPrice(assetFrom, assetTo, fromPrice); 46 | await expect(toPrice.toNumber()).to.eq(fromPrice * price); 47 | }); 48 | it('return directly for same asset', async () => { 49 | const fromPrice = 1e12; 50 | const toPrice = await priceOracle.convertPrice(assetFrom, assetFrom, fromPrice); 51 | await expect(toPrice).to.eq(fromPrice); 52 | }); 53 | }); 54 | 55 | describe('set price', () => { 56 | it('controller set price should work', async () => { 57 | await priceOracle.setLimit(10, 100); 58 | await priceOracle.setController(wallet_1.address); 59 | 60 | await blockTravel(100); 61 | 62 | const price1 = (price / 100) * 91; // -9% 63 | await priceOracle.connect(wallet_1).setAssetPrice(assetFrom, assetTo, 1, price1); 64 | await priceOracle.setAssetPrice(assetFrom, assetTo, 1, price); 65 | 66 | // time limit 67 | await expect(priceOracle.connect(wallet_1).setAssetPrice(assetFrom, assetTo, 1, price1)).to.be.revertedWith( 68 | 'OR002' 69 | ); 70 | 71 | await blockTravel(100); 72 | 73 | const price2 = (price / 100) * 109; // +9% 74 | await priceOracle.connect(wallet_1).setAssetPrice(assetFrom, assetTo, 1, price2); 75 | await priceOracle.setAssetPrice(assetFrom, assetTo, 1, price); 76 | 77 | await blockTravel(100); 78 | 79 | const price3 = (price / 100) * 110; // +10% 80 | await expect(priceOracle.connect(wallet_1).setAssetPrice(assetFrom, assetTo, 1, price3)).to.be.revertedWith( 81 | 'OR003' 82 | ); 83 | }); 84 | it('owner set price should work', async () => { 85 | const price1 = (price / 100) * 110; // +10% 86 | await expect(priceOracle.connect(wallet_2).setAssetPrice(assetFrom, assetTo, 1, price1)).to.be.revertedWith( 87 | 'OR004' 88 | ); 89 | 90 | await expect(priceOracle.setAssetPrice(assetFrom, assetTo, 1, price1)); 91 | }); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /src/contracts.ts: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | import Airdropper from './artifacts/contracts/Airdropper.sol/Airdropper.json'; 5 | import ConsumerHost from './artifacts/contracts/ConsumerHost.sol/ConsumerHost.json'; 6 | import ConsumerRegistry from './artifacts/contracts/ConsumerRegistry.sol/ConsumerRegistry.json'; 7 | import DisputeManager from './artifacts/contracts/DisputeManager.sol/DisputeManager.json'; 8 | import EraManager from './artifacts/contracts/l2/EraManager.sol/EraManager.json'; 9 | import IndexerRegistry from './artifacts/contracts/IndexerRegistry.sol/IndexerRegistry.json'; 10 | import InflationController from './artifacts/contracts/root/InflationController.sol/InflationController.json'; 11 | import PermissionedExchange from './artifacts/contracts/PermissionedExchange.sol/PermissionedExchange.json'; 12 | import TokenExchange from './artifacts/contracts/TokenExchange.sol/TokenExchange.json'; 13 | import PlanManager from './artifacts/contracts/PlanManager.sol/PlanManager.json'; 14 | import PriceOracle from './artifacts/contracts/PriceOracle.sol/PriceOracle.json'; 15 | import ProjectRegistry from './artifacts/contracts/ProjectRegistry.sol/ProjectRegistry.json'; 16 | import ProxyAdmin from './artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json'; 17 | import PurchaseOfferMarket from './artifacts/contracts/PurchaseOfferMarket.sol/PurchaseOfferMarket.json'; 18 | import RewardsDistributor from './artifacts/contracts/RewardsDistributor.sol/RewardsDistributor.json'; 19 | import RewardsHelper from './artifacts/contracts/RewardsHelper.sol/RewardsHelper.json'; 20 | import RewardsPool from './artifacts/contracts/RewardsPool.sol/RewardsPool.json'; 21 | import RewardsStaking from './artifacts/contracts/RewardsStaking.sol/RewardsStaking.json'; 22 | import RewardsBooster from './artifacts/contracts/RewardsBooster.sol/RewardsBooster.json'; 23 | import SQToken from './artifacts/contracts/root/SQToken.sol/SQToken.json'; 24 | import ServiceAgreementRegistry from './artifacts/contracts/ServiceAgreementRegistry.sol/ServiceAgreementRegistry.json'; 25 | import Settings from './artifacts/contracts/Settings.sol/Settings.json'; 26 | import Staking from './artifacts/contracts/Staking.sol/Staking.json'; 27 | import StakingManager from './artifacts/contracts/StakingManager.sol/StakingManager.json'; 28 | import StakingAllocation from './artifacts/contracts/StakingAllocation.sol/StakingAllocation.json'; 29 | import StateChannel from './artifacts/contracts/StateChannel.sol/StateChannel.json'; 30 | import VSQToken from './artifacts/contracts/VSQToken.sol/VSQToken.json'; 31 | import Vesting from './artifacts/contracts/root/Vesting.sol/Vesting.json'; 32 | import VTSQToken from './artifacts/contracts/root/VTSQToken.sol/VTSQToken.json'; 33 | import OpDestination from './artifacts/contracts/root/OpDestination.sol/OpDestination.json'; 34 | import SQTGift from './artifacts/contracts/SQTGift.sol/SQTGift.json'; 35 | import SQTRedeem from './artifacts/contracts/SQTRedeem.sol/SQTRedeem.json'; 36 | import L2SQToken from './artifacts/contracts/l2/L2SQToken.sol/L2SQToken.json'; 37 | import AirdropperLite from './artifacts/contracts/root/AirdropperLite.sol/AirdropperLite.json'; 38 | import L2Vesting from './artifacts/contracts/l2/L2Vesting.sol/L2Vesting.json'; 39 | import UniswapPriceOracle from './artifacts/contracts/l2/UniswapPriceOracle.sol/UniswapPriceOracle.json'; 40 | 41 | export default { 42 | Settings, 43 | SQToken, 44 | VSQToken, 45 | Staking, 46 | StakingManager, 47 | StakingAllocation, 48 | IndexerRegistry, 49 | ProjectRegistry, 50 | InflationController, 51 | ServiceAgreementRegistry, 52 | PlanManager, 53 | PurchaseOfferMarket, 54 | EraManager, 55 | RewardsDistributor, 56 | RewardsPool, 57 | RewardsStaking, 58 | RewardsHelper, 59 | RewardsBooster, 60 | ProxyAdmin, 61 | StateChannel, 62 | Airdropper, 63 | PermissionedExchange, 64 | TokenExchange, 65 | Vesting, 66 | VTSQToken, 67 | ConsumerHost, 68 | DisputeManager, 69 | PriceOracle, 70 | ConsumerRegistry, 71 | OpDestination, 72 | SQTGift, 73 | SQTRedeem, 74 | L2SQToken, 75 | AirdropperLite, 76 | L2Vesting, 77 | UniswapPriceOracle, 78 | }; 79 | -------------------------------------------------------------------------------- /publish/ABI/Settings.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": false, 7 | "internalType": "uint8", 8 | "name": "version", 9 | "type": "uint8" 10 | } 11 | ], 12 | "name": "Initialized", 13 | "type": "event" 14 | }, 15 | { 16 | "anonymous": false, 17 | "inputs": [ 18 | { 19 | "indexed": true, 20 | "internalType": "address", 21 | "name": "previousOwner", 22 | "type": "address" 23 | }, 24 | { 25 | "indexed": true, 26 | "internalType": "address", 27 | "name": "newOwner", 28 | "type": "address" 29 | } 30 | ], 31 | "name": "OwnershipTransferred", 32 | "type": "event" 33 | }, 34 | { 35 | "inputs": [ 36 | { 37 | "internalType": "enum SQContracts", 38 | "name": "", 39 | "type": "uint8" 40 | } 41 | ], 42 | "name": "contractAddresses", 43 | "outputs": [ 44 | { 45 | "internalType": "address", 46 | "name": "", 47 | "type": "address" 48 | } 49 | ], 50 | "stateMutability": "view", 51 | "type": "function" 52 | }, 53 | { 54 | "inputs": [ 55 | { 56 | "internalType": "enum SQContracts", 57 | "name": "sq", 58 | "type": "uint8" 59 | } 60 | ], 61 | "name": "getContractAddress", 62 | "outputs": [ 63 | { 64 | "internalType": "address", 65 | "name": "", 66 | "type": "address" 67 | } 68 | ], 69 | "stateMutability": "view", 70 | "type": "function" 71 | }, 72 | { 73 | "inputs": [], 74 | "name": "initialize", 75 | "outputs": [], 76 | "stateMutability": "nonpayable", 77 | "type": "function" 78 | }, 79 | { 80 | "inputs": [], 81 | "name": "owner", 82 | "outputs": [ 83 | { 84 | "internalType": "address", 85 | "name": "", 86 | "type": "address" 87 | } 88 | ], 89 | "stateMutability": "view", 90 | "type": "function" 91 | }, 92 | { 93 | "inputs": [], 94 | "name": "renounceOwnership", 95 | "outputs": [], 96 | "stateMutability": "nonpayable", 97 | "type": "function" 98 | }, 99 | { 100 | "inputs": [ 101 | { 102 | "internalType": "enum SQContracts[]", 103 | "name": "_sq", 104 | "type": "uint8[]" 105 | }, 106 | { 107 | "internalType": "address[]", 108 | "name": "_address", 109 | "type": "address[]" 110 | } 111 | ], 112 | "name": "setBatchAddress", 113 | "outputs": [], 114 | "stateMutability": "nonpayable", 115 | "type": "function" 116 | }, 117 | { 118 | "inputs": [ 119 | { 120 | "internalType": "enum SQContracts", 121 | "name": "sq", 122 | "type": "uint8" 123 | }, 124 | { 125 | "internalType": "address", 126 | "name": "_address", 127 | "type": "address" 128 | } 129 | ], 130 | "name": "setContractAddress", 131 | "outputs": [], 132 | "stateMutability": "nonpayable", 133 | "type": "function" 134 | }, 135 | { 136 | "inputs": [ 137 | { 138 | "internalType": "address", 139 | "name": "newOwner", 140 | "type": "address" 141 | } 142 | ], 143 | "name": "transferOwnership", 144 | "outputs": [], 145 | "stateMutability": "nonpayable", 146 | "type": "function" 147 | } 148 | ] -------------------------------------------------------------------------------- /test/fixtures/rpc_autonity_piccadilly.yaml: -------------------------------------------------------------------------------- 1 | # - kind: Project 2 | # projectType: 1 3 | # metadata: 4 | # name: Autonity Piccadilly RPC - Full Node 5 | # description: | 6 | # **Public RPC endpoint:** https://autonity-piccadilly.rpc.subquery.network/public 7 | 8 | # An example query for your terminal is 9 | 10 | # ``` 11 | # curl -H 'content-type:application/json' -d '{"id": 1, "jsonrpc": "2.0", "method": "eth_blockNumber"}' 'https://autonity-piccadilly.rpc.subquery.network/public' 12 | # ``` 13 | 14 | # Piccadilly is a testnet for the Autonity project and is being developed by a community that is passionate about the social benefits of monetary and market structure innovation. 15 | 16 | # Connect in seconds to SubQuery's decentralised network of RPC node operators running around the world so your dApps benefit from more reliable, scalable, and affordable RPC services. By accessing this public RPC endpoint, you agree to our Free Public RPC terms of service. 17 | 18 | # Autonity is a public, EVM based, proof-of-stake blockchain for decentralized clearing of smart derivatives contracts. Autonity aims to make every risk that the market wants to speculate on or hedge against into a smart derivative product that is tradable between anyone in a fully decentralized ecosystem of price discovery and trade execution. 19 | # websiteUrl: https://autonity.org/testnets 20 | # deployments: 21 | # - deployment: | 22 | # kind: ChainRpc 23 | # specVersion: "1.0.0" 24 | # name: autonity piccadilly - full 25 | # chain: 26 | # chainId: 65100004 27 | # rpcFamily: 28 | # - evm 29 | # nodeType: full 30 | # rpcDenyList: 31 | # - admin_ 32 | # - debug_ 33 | # - txpool_ 34 | # - personal_ 35 | # - miner_ 36 | # - les_ 37 | # version: 38 | # version: 1.0.0 39 | - kind: Project 40 | projectType: 1 41 | id: '0x4d' 42 | metadata: 43 | name: Autonity Piccadilly RPC - Archive Node 44 | description: | 45 | **Public RPC endpoint:** https://autonity-piccadilly.rpc.subquery.network/public 46 | 47 | An example query for your terminal is 48 | 49 | ``` 50 | curl -H 'content-type:application/json' -d '{"id": 1, "jsonrpc": "2.0", "method": "eth_blockNumber"}' 'https://autonity-piccadilly.rpc.subquery.network/public' 51 | ``` 52 | 53 | Piccadilly is a testnet for the Autonity project and is being developed by a community that is passionate about the social benefits of monetary and market structure innovation. 54 | 55 | Connect in seconds to SubQuery's decentralised network of RPC node operators running around the world so your dApps benefit from more reliable, scalable, and affordable RPC services. By accessing this public RPC endpoint, you agree to our Free Public RPC terms of service. 56 | 57 | Autonity is a public, EVM based, proof-of-stake blockchain for decentralized clearing of smart derivatives contracts. Autonity aims to make every risk that the market wants to speculate on or hedge against into a smart derivative product that is tradable between anyone in a fully decentralized ecosystem of price discovery and trade execution. 58 | websiteUrl: https://autonity.org/testnets 59 | deployments: 60 | - deployment: | 61 | kind: ChainRpc 62 | specVersion: "1.0.0" 63 | name: autonity piccadilly - archive 64 | chain: 65 | chainId: 65100003 66 | rpcFamily: 67 | - evm 68 | nodeType: archive 69 | rpcDenyList: 70 | - admin_ 71 | - debug_ 72 | - txpool_ 73 | - personal_ 74 | - miner_ 75 | - les_ 76 | version: 77 | version: 1.0.0 78 | deploymentId: 'QmW5RcoyWKRJdrgV1aGair8UtGZhpfK4VrYJvs4sRzGVL2' 79 | - deployment: | 80 | kind: ChainRpc 81 | specVersion: "1.0.0" 82 | name: autonity piccadilly - archive 83 | chain: 84 | chainId: 65100004 85 | rpcFamily: 86 | - evm 87 | nodeType: archive 88 | rpcDenyList: 89 | - admin_ 90 | - debug_ 91 | - txpool_ 92 | - personal_ 93 | - miner_ 94 | - les_ 95 | version: 96 | version: 2.0.0 97 | deploymentId: 'QmSXV8pkxKufQNGdaJ2N15t4cCta4iYWL7QXdLvtjEaS48' 98 | -------------------------------------------------------------------------------- /contracts/ConsumerRegistry.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol'; 7 | import '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol'; 8 | import '@openzeppelin/contracts-upgradeable/utils/introspection/ERC165CheckerUpgradeable.sol'; 9 | 10 | import './interfaces/ISettings.sol'; 11 | import './interfaces/IConsumer.sol'; 12 | 13 | /** 14 | * @title Consumer Registry Contract 15 | * @notice ### Overview 16 | * This contract include consumer controllers 17 | */ 18 | contract ConsumerRegistry is Initializable, OwnableUpgradeable { 19 | using ERC165CheckerUpgradeable for address; 20 | 21 | /// @dev ### STATES 22 | /// @notice ISettings contract which stores SubQuery network contracts address 23 | ISettings public settings; 24 | 25 | /// @notice users authorised by consumer that can request access token from runner 26 | /// consumer address => controller address => bool 27 | mapping(address => mapping(address => bool)) public controllers; 28 | 29 | /// @notice account address -> is whitelisted 30 | mapping(address => bool) public whitelist; 31 | 32 | // -- Events -- 33 | 34 | /** 35 | * @dev Emitted when consumer add new controller 36 | */ 37 | event ControllerAdded(address indexed consumer, address controller); 38 | /** 39 | * @dev Emitted when consumer remove user 40 | */ 41 | event ControllerRemoved(address indexed consumer, address controller); 42 | /** 43 | * @dev Emitted when whitelist account changed 44 | */ 45 | event WhitelistUpdated(address indexed account, bool isWhitelisted); 46 | 47 | /** 48 | * @dev Initialize this contract. Load establisherWhitelist. 49 | */ 50 | function initialize(ISettings _settings) external initializer { 51 | __Ownable_init(); 52 | 53 | settings = _settings; 54 | } 55 | 56 | function setSettings(ISettings _settings) external onlyOwner { 57 | settings = _settings; 58 | } 59 | 60 | function addWhitelist(address account) external onlyOwner { 61 | whitelist[account] = true; 62 | emit WhitelistUpdated(account, true); 63 | } 64 | 65 | function removeWhitelist(address account) external onlyOwner { 66 | delete whitelist[account]; 67 | emit WhitelistUpdated(account, false); 68 | } 69 | 70 | /** 71 | * @dev Consumer add controller can request access token from indexer. 72 | */ 73 | function addController(address consumer, address controller) external { 74 | if (msg.sender != consumer) { 75 | if (_isContract(consumer)) { 76 | require(consumer.supportsInterface(type(IConsumer).interfaceId), 'CR002'); 77 | IConsumer cConsumer = IConsumer(consumer); 78 | require(cConsumer.isSigner(msg.sender), 'CR003'); 79 | } else { 80 | revert('CR001'); 81 | } 82 | } 83 | 84 | controllers[consumer][controller] = true; 85 | emit ControllerAdded(consumer, controller); 86 | } 87 | 88 | /** 89 | * @dev Consumer remove users can request access token from indexer. 90 | */ 91 | function removeController(address consumer, address controller) external { 92 | if (_isContract(consumer)) { 93 | require(consumer.supportsInterface(type(IConsumer).interfaceId), 'CR002'); 94 | IConsumer cConsumer = IConsumer(consumer); 95 | require(cConsumer.isSigner(msg.sender), 'CR003'); 96 | } else { 97 | require(msg.sender == consumer, 'CR001'); 98 | } 99 | 100 | delete controllers[consumer][controller]; 101 | emit ControllerRemoved(consumer, controller); 102 | } 103 | 104 | function isController(address consumer, address controller) external view returns (bool) { 105 | return controllers[consumer][controller]; 106 | } 107 | 108 | /// @notice Determine the input address is contract or not 109 | function _isContract(address _addr) private view returns (bool) { 110 | uint32 size; 111 | assembly { 112 | size := extcodesize(_addr) 113 | } 114 | return (size > 0); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/sdk.ts: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | import type { Provider as AbstractProvider } from '@ethersproject/abstract-provider'; 5 | import { Signer } from 'ethers'; 6 | import { DEPLOYMENT_DETAILS } from './deployments'; 7 | import { 8 | Airdropper, 9 | ConsumerHost, 10 | ConsumerRegistry, 11 | DisputeManager, 12 | EraManager, 13 | IndexerRegistry, 14 | PlanManager, 15 | PriceOracle, 16 | ProjectRegistry, 17 | ProxyAdmin, 18 | PurchaseOfferMarket, 19 | RewardsDistributor, 20 | RewardsHelper, 21 | RewardsPool, 22 | RewardsStaking, 23 | ServiceAgreementRegistry, 24 | Settings, 25 | Staking, 26 | StakingManager, 27 | StateChannel, 28 | VSQToken, 29 | SQTGift, 30 | SQTRedeem, 31 | ERC20, 32 | RewardsBooster, 33 | StakingAllocation, 34 | L2Vesting, 35 | } from './typechain'; 36 | import { CONTRACT_FACTORY, ContractDeploymentInner, ContractName, FactoryContstructor, SdkOptions } from './types'; 37 | import assert from 'assert'; 38 | 39 | // HOTFIX: Contract names are not consistent between deployments and privous var names 40 | const contractNameConversion: Record = { 41 | l2SQToken: 'sqToken', 42 | sQTGift: 'sqtGift', 43 | sQTRedeem: 'sqtRedeem', 44 | }; 45 | 46 | export class ContractSDK { 47 | private _contractDeployments: ContractDeploymentInner; 48 | 49 | readonly settings!: Settings; 50 | readonly sqToken!: ERC20; 51 | readonly staking!: Staking; 52 | readonly stakingManager!: StakingManager; 53 | readonly indexerRegistry!: IndexerRegistry; 54 | readonly projectRegistry!: ProjectRegistry; 55 | readonly serviceAgreementRegistry!: ServiceAgreementRegistry; 56 | readonly eraManager!: EraManager; 57 | readonly planManager!: PlanManager; 58 | readonly rewardsBooster!: RewardsBooster; 59 | readonly rewardsDistributor!: RewardsDistributor; 60 | readonly rewardsPool!: RewardsPool; 61 | readonly rewardsStaking!: RewardsStaking; 62 | readonly rewardsHelper!: RewardsHelper; 63 | readonly purchaseOfferMarket!: PurchaseOfferMarket; 64 | readonly stateChannel!: StateChannel; 65 | readonly airdropper!: Airdropper; 66 | readonly consumerHost!: ConsumerHost; 67 | readonly disputeManager!: DisputeManager; 68 | readonly proxyAdmin!: ProxyAdmin; 69 | readonly consumerRegistry!: ConsumerRegistry; 70 | readonly priceOracle!: PriceOracle; 71 | readonly vSQToken!: VSQToken; 72 | readonly sqtGift!: SQTGift; 73 | readonly sqtRedeem!: SQTRedeem; 74 | readonly stakingAllocation!: StakingAllocation; 75 | readonly l2Vesting!: L2Vesting; 76 | 77 | constructor( 78 | // eslint-disable-next-line no-unused-vars 79 | private readonly signerOrProvider: AbstractProvider | Signer, 80 | public readonly options: SdkOptions 81 | ) { 82 | assert( 83 | this.options.deploymentDetails || DEPLOYMENT_DETAILS[options.network], 84 | ' missing contract deployment info' 85 | ); 86 | this._contractDeployments = this.options.deploymentDetails ?? DEPLOYMENT_DETAILS[options.network]!.child; 87 | this._init(); 88 | } 89 | 90 | static create(signerOrProvider: AbstractProvider | Signer, options: SdkOptions) { 91 | return new ContractSDK(signerOrProvider, options); 92 | } 93 | 94 | private async _init() { 95 | const contracts = Object.entries(this._contractDeployments).map(([name, contract]) => ({ 96 | address: contract.address, 97 | factory: CONTRACT_FACTORY[name as ContractName] as FactoryContstructor, 98 | name: name as ContractName, 99 | })); 100 | 101 | for (const { name, factory, address } of contracts) { 102 | const contractInstance = factory.connect(address, this.signerOrProvider); 103 | if (contractInstance) { 104 | const key = name.charAt(0).toLowerCase() + name.slice(1); 105 | const contractName = contractNameConversion[key] ?? key; 106 | Object.defineProperty(this, contractName, { 107 | get: () => contractInstance, 108 | }); 109 | } else { 110 | throw new Error(`${name} contract not found`); 111 | } 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /scripts/setup.ts: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv'; 2 | import { providers, utils, Wallet } from 'ethers'; 3 | 4 | import moduleAlias from 'module-alias'; 5 | moduleAlias.addAlias('./publish', '../publish'); 6 | moduleAlias.addAlias('./artifacts', '../artifacts'); 7 | import { hideBin } from 'yargs/helpers'; 8 | import yargs from 'yargs/yargs'; 9 | 10 | import { networks, SubqueryNetwork, NetworkPair } from '../src'; 11 | import contractsConfig from './config/contracts.config'; 12 | import { ContractConfig } from './contracts'; 13 | 14 | dotenv.config(); 15 | 16 | const seed = process.env.SEED; 17 | const privateKey = process.env.PK; 18 | 19 | export const { argv } = yargs(hideBin(process.argv)).options({ 20 | network: { 21 | demandOption: true, 22 | describe: 'network', 23 | type: 'string', 24 | choices: ['testnet', 'testnet-mumbai', 'mainnet'], 25 | }, 26 | history: { 27 | type: 'boolean', 28 | describe: 'compare history deployment', 29 | default: true, 30 | }, 31 | 'check-only': { 32 | type: 'boolean', 33 | default: true, 34 | }, 35 | 'implementation-only': { 36 | type: 'boolean', 37 | describe: 'only deploy implementation contract', 38 | default: false, 39 | }, 40 | target: { 41 | type: 'string', 42 | choices: ['root', 'child'], 43 | demandOption: true, 44 | }, 45 | }); 46 | 47 | export async function setupCommon(pair: NetworkPair) { 48 | let wallet: Wallet; 49 | 50 | const rootProvider = new providers.StaticJsonRpcProvider( 51 | pair.root.rpcUrls[0], 52 | pair.root.chainId 53 | ? { 54 | chainId: parseInt(pair.root.chainId, 16), 55 | name: pair.root.chainName, 56 | } 57 | : undefined 58 | ); 59 | const childProvider = new providers.StaticJsonRpcProvider( 60 | pair.child.rpcUrls[0], 61 | pair.child.chainId 62 | ? { 63 | chainId: parseInt(pair.child.chainId, 16), 64 | name: pair.child.chainName, 65 | } 66 | : undefined 67 | ); 68 | if (seed) { 69 | const hdNode = utils.HDNode.fromMnemonic(seed).derivePath("m/44'/60'/0'/0/0"); 70 | wallet = new Wallet(hdNode); 71 | } else if (privateKey) { 72 | wallet = new Wallet(privateKey); 73 | } else { 74 | throw new Error('Not found SEED or PK in env'); 75 | } 76 | console.log(`signer is ${wallet.address}`); 77 | return { 78 | wallet, 79 | rootProvider, 80 | childProvider, 81 | overrides: {}, 82 | }; 83 | } 84 | 85 | const setup = async (network?: string) => { 86 | const config: { network: NetworkPair; contracts: ContractConfig } = { contracts: null, network: null }; 87 | let history = false; 88 | let checkOnly = false; 89 | let implementationOnly = false; 90 | network = network ?? argv.network; 91 | if (!contractsConfig[network] || !networks[network]) { 92 | throw new Error('no network specified'); 93 | } 94 | config.contracts = contractsConfig[network]; 95 | config.network = networks[network]; 96 | 97 | const name = (argv.network ?? 'local') as SubqueryNetwork; 98 | 99 | history = argv.history; 100 | checkOnly = argv['check-only']; 101 | implementationOnly = argv['implementation-only']; 102 | 103 | if (process.env.ROOT_ENDPOINT) { 104 | console.log(`use overridden endpoint ${process.env.ROOT_ENDPOINT}`); 105 | config.network.root.rpcUrls = [process.env.ROOT_ENDPOINT]; 106 | } 107 | if (process.env.CHILD_ENDPOINT) { 108 | console.log(`use overridden endpoint ${process.env.CHILD_ENDPOINT}`); 109 | config.network.child.rpcUrls = [process.env.CHILD_ENDPOINT]; 110 | } 111 | 112 | let confirms = 1; 113 | if (['Polygon'].includes(config.network.child.chainName)) { 114 | confirms = 20; 115 | } 116 | const { wallet, rootProvider, childProvider, overrides } = await setupCommon(config.network); 117 | return { 118 | name, 119 | config, 120 | wallet, 121 | rootProvider, 122 | childProvider, 123 | overrides, 124 | confirms, 125 | history, 126 | checkOnly, 127 | implementationOnly, 128 | target: argv.target, 129 | }; 130 | }; 131 | 132 | export default setup; 133 | -------------------------------------------------------------------------------- /test/fixtures/rpc_projects.yaml: -------------------------------------------------------------------------------- 1 | #- kind: Account 2 | # name: consumer1 3 | # seed: trick scale shop mountain any van develop blame sport grid equal garbage 4 | # derive: /1 5 | - kind: Project 6 | projectType: 1 # rpc 7 | metadata: 8 | name: Polkadot Rpc 9 | description: Polkadot endpoint 10 | websiteUrl: https://subquery.network 11 | codeUrl: https://github.com/subquery 12 | deployments: 13 | - deployment: | 14 | kind: ChainRpc 15 | specVersion: "1.0.0" 16 | name: Polkadot Rpc 17 | chain: 18 | genesisHash: 0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3 19 | rpcFamily: 20 | - substrate 21 | nodeType: archive 22 | version: 23 | version: 1.0.0 24 | description: 25 | - kind: Project 26 | projectType: 1 # rpc 27 | metadata: 28 | name: Eth Private Rpc 29 | description: eth endpoint 30 | websiteUrl: https://subquery.network 31 | codeUrl: https://github.com/subquery 32 | deployments: 33 | - deployment: | 34 | kind: ChainRpc 35 | specVersion: "1.0.0" 36 | name: Eth Private Rpc (new) 37 | chain: 38 | chainId: 12345 39 | rpcFamily: 40 | - evm 41 | nodeType: archive 42 | version: 43 | version: 1.0.0 44 | description: 45 | - kind: Project 46 | projectType: 1 # rpc 47 | metadata: 48 | name: Eth Mainnet Rpc - Full Node 49 | description: '' 50 | websiteUrl: https://ethereum.org/ 51 | deployments: 52 | - deployment: | 53 | kind: ChainRpc 54 | specVersion: 1.0.0 55 | name: eth-mainnet - full 56 | chain: 57 | chainId: 1 58 | rpcFamily: 59 | - evm 60 | nodeType: full 61 | rpcDenyList: 62 | - admin 63 | - debug 64 | - txpool 65 | - personal 66 | - miner 67 | - les 68 | version: 69 | version: 1.0.0 70 | description: '' 71 | - kind: Project 72 | projectType: 1 # rpc 73 | metadata: 74 | name: Eth Mainnet Rpc - Archive Node 75 | description: '' 76 | websiteUrl: https://ethereum.org/ 77 | deployments: 78 | - deployment: | 79 | kind: ChainRpc 80 | specVersion: 1.0.0 81 | name: eth-mainnet - archive 82 | chain: 83 | chainId: 1 84 | rpcFamily: 85 | - evm 86 | nodeType: archive 87 | rpcDenyList: 88 | - admin 89 | - debug 90 | - txpool 91 | - personal 92 | - miner 93 | - les 94 | version: 95 | version: 1.0.0 96 | description: '' 97 | - kind: Project 98 | projectType: 1 # rpc 99 | metadata: 100 | name: Base Rpc - Full Node 101 | description: '' 102 | websiteUrl: https://base.org/ 103 | deployments: 104 | - deployment: | 105 | kind: ChainRpc 106 | specVersion: 1.0.0 107 | name: base - full 108 | chain: 109 | chainId: 8453 110 | rpcFamily: 111 | - evm 112 | nodeType: full 113 | rpcDenyList: 114 | - admin 115 | - debug 116 | - txpool 117 | - personal 118 | - miner 119 | - les 120 | version: 121 | version: 1.0.0 122 | description: '' 123 | - kind: Project 124 | projectType: 1 # rpc 125 | metadata: 126 | name: Base Rpc - Archive Node 127 | description: '' 128 | websiteUrl: https://base.org/ 129 | deployments: 130 | - deployment: | 131 | kind: ChainRpc 132 | specVersion: 1.0.0 133 | name: base - archive 134 | chain: 135 | chainId: 8453 136 | rpcFamily: 137 | - evm 138 | nodeType: archive 139 | rpcDenyList: 140 | - admin 141 | - debug 142 | - txpool 143 | - personal 144 | - miner 145 | - les 146 | version: 147 | version: 1.0.0 148 | description: '' 149 | -------------------------------------------------------------------------------- /publish/ABI/ProxyAdmin.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": true, 7 | "internalType": "address", 8 | "name": "previousOwner", 9 | "type": "address" 10 | }, 11 | { 12 | "indexed": true, 13 | "internalType": "address", 14 | "name": "newOwner", 15 | "type": "address" 16 | } 17 | ], 18 | "name": "OwnershipTransferred", 19 | "type": "event" 20 | }, 21 | { 22 | "inputs": [ 23 | { 24 | "internalType": "contract ITransparentUpgradeableProxy", 25 | "name": "proxy", 26 | "type": "address" 27 | }, 28 | { 29 | "internalType": "address", 30 | "name": "newAdmin", 31 | "type": "address" 32 | } 33 | ], 34 | "name": "changeProxyAdmin", 35 | "outputs": [], 36 | "stateMutability": "nonpayable", 37 | "type": "function" 38 | }, 39 | { 40 | "inputs": [ 41 | { 42 | "internalType": "contract ITransparentUpgradeableProxy", 43 | "name": "proxy", 44 | "type": "address" 45 | } 46 | ], 47 | "name": "getProxyAdmin", 48 | "outputs": [ 49 | { 50 | "internalType": "address", 51 | "name": "", 52 | "type": "address" 53 | } 54 | ], 55 | "stateMutability": "view", 56 | "type": "function" 57 | }, 58 | { 59 | "inputs": [ 60 | { 61 | "internalType": "contract ITransparentUpgradeableProxy", 62 | "name": "proxy", 63 | "type": "address" 64 | } 65 | ], 66 | "name": "getProxyImplementation", 67 | "outputs": [ 68 | { 69 | "internalType": "address", 70 | "name": "", 71 | "type": "address" 72 | } 73 | ], 74 | "stateMutability": "view", 75 | "type": "function" 76 | }, 77 | { 78 | "inputs": [], 79 | "name": "owner", 80 | "outputs": [ 81 | { 82 | "internalType": "address", 83 | "name": "", 84 | "type": "address" 85 | } 86 | ], 87 | "stateMutability": "view", 88 | "type": "function" 89 | }, 90 | { 91 | "inputs": [], 92 | "name": "renounceOwnership", 93 | "outputs": [], 94 | "stateMutability": "nonpayable", 95 | "type": "function" 96 | }, 97 | { 98 | "inputs": [ 99 | { 100 | "internalType": "address", 101 | "name": "newOwner", 102 | "type": "address" 103 | } 104 | ], 105 | "name": "transferOwnership", 106 | "outputs": [], 107 | "stateMutability": "nonpayable", 108 | "type": "function" 109 | }, 110 | { 111 | "inputs": [ 112 | { 113 | "internalType": "contract ITransparentUpgradeableProxy", 114 | "name": "proxy", 115 | "type": "address" 116 | }, 117 | { 118 | "internalType": "address", 119 | "name": "implementation", 120 | "type": "address" 121 | } 122 | ], 123 | "name": "upgrade", 124 | "outputs": [], 125 | "stateMutability": "nonpayable", 126 | "type": "function" 127 | }, 128 | { 129 | "inputs": [ 130 | { 131 | "internalType": "contract ITransparentUpgradeableProxy", 132 | "name": "proxy", 133 | "type": "address" 134 | }, 135 | { 136 | "internalType": "address", 137 | "name": "implementation", 138 | "type": "address" 139 | }, 140 | { 141 | "internalType": "bytes", 142 | "name": "data", 143 | "type": "bytes" 144 | } 145 | ], 146 | "name": "upgradeAndCall", 147 | "outputs": [], 148 | "stateMutability": "payable", 149 | "type": "function" 150 | } 151 | ] -------------------------------------------------------------------------------- /publish/ABI/VSQToken.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": false, 7 | "internalType": "uint8", 8 | "name": "version", 9 | "type": "uint8" 10 | } 11 | ], 12 | "name": "Initialized", 13 | "type": "event" 14 | }, 15 | { 16 | "anonymous": false, 17 | "inputs": [ 18 | { 19 | "indexed": true, 20 | "internalType": "address", 21 | "name": "previousOwner", 22 | "type": "address" 23 | }, 24 | { 25 | "indexed": true, 26 | "internalType": "address", 27 | "name": "newOwner", 28 | "type": "address" 29 | } 30 | ], 31 | "name": "OwnershipTransferred", 32 | "type": "event" 33 | }, 34 | { 35 | "inputs": [ 36 | { 37 | "internalType": "address", 38 | "name": "account", 39 | "type": "address" 40 | } 41 | ], 42 | "name": "balanceOf", 43 | "outputs": [ 44 | { 45 | "internalType": "uint256", 46 | "name": "", 47 | "type": "uint256" 48 | } 49 | ], 50 | "stateMutability": "view", 51 | "type": "function" 52 | }, 53 | { 54 | "inputs": [], 55 | "name": "decimals", 56 | "outputs": [ 57 | { 58 | "internalType": "uint8", 59 | "name": "", 60 | "type": "uint8" 61 | } 62 | ], 63 | "stateMutability": "pure", 64 | "type": "function" 65 | }, 66 | { 67 | "inputs": [ 68 | { 69 | "internalType": "contract ISettings", 70 | "name": "_settings", 71 | "type": "address" 72 | } 73 | ], 74 | "name": "initialize", 75 | "outputs": [], 76 | "stateMutability": "nonpayable", 77 | "type": "function" 78 | }, 79 | { 80 | "inputs": [], 81 | "name": "name", 82 | "outputs": [ 83 | { 84 | "internalType": "string", 85 | "name": "", 86 | "type": "string" 87 | } 88 | ], 89 | "stateMutability": "view", 90 | "type": "function" 91 | }, 92 | { 93 | "inputs": [], 94 | "name": "owner", 95 | "outputs": [ 96 | { 97 | "internalType": "address", 98 | "name": "", 99 | "type": "address" 100 | } 101 | ], 102 | "stateMutability": "view", 103 | "type": "function" 104 | }, 105 | { 106 | "inputs": [], 107 | "name": "renounceOwnership", 108 | "outputs": [], 109 | "stateMutability": "nonpayable", 110 | "type": "function" 111 | }, 112 | { 113 | "inputs": [ 114 | { 115 | "internalType": "contract ISettings", 116 | "name": "_settings", 117 | "type": "address" 118 | } 119 | ], 120 | "name": "setSettings", 121 | "outputs": [], 122 | "stateMutability": "nonpayable", 123 | "type": "function" 124 | }, 125 | { 126 | "inputs": [], 127 | "name": "settings", 128 | "outputs": [ 129 | { 130 | "internalType": "contract ISettings", 131 | "name": "", 132 | "type": "address" 133 | } 134 | ], 135 | "stateMutability": "view", 136 | "type": "function" 137 | }, 138 | { 139 | "inputs": [], 140 | "name": "symbol", 141 | "outputs": [ 142 | { 143 | "internalType": "string", 144 | "name": "", 145 | "type": "string" 146 | } 147 | ], 148 | "stateMutability": "view", 149 | "type": "function" 150 | }, 151 | { 152 | "inputs": [ 153 | { 154 | "internalType": "address", 155 | "name": "newOwner", 156 | "type": "address" 157 | } 158 | ], 159 | "name": "transferOwnership", 160 | "outputs": [], 161 | "stateMutability": "nonpayable", 162 | "type": "function" 163 | } 164 | ] -------------------------------------------------------------------------------- /contracts/l2/EraManager.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity 0.8.15; 5 | 6 | import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol'; 7 | import '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol'; 8 | 9 | import '../interfaces/IEraManager.sol'; 10 | import '../interfaces/ISettings.sol'; 11 | import '../utils/SQParameter.sol'; 12 | 13 | /** 14 | * @title EraManager contract 15 | * @notice Produce epochs based on a period to coordinate contracts. Staking and reward distributing are running based on Eras 16 | */ 17 | contract EraManager is Initializable, OwnableUpgradeable, IEraManager, SQParameter { 18 | /// @dev ### STATES 19 | /// @notice ISettings contract which stores SubQuery network contracts address 20 | ISettings public settings; 21 | 22 | /// @notice Era period in second 23 | uint256 public eraPeriod; 24 | 25 | /// @notice Current Era number 26 | uint256 public eraNumber; 27 | 28 | /// @notice Current era start time in unix timestamp 29 | uint256 public eraStartTime; 30 | 31 | /// @notice Maintenance mode, when maintenance mode, only owner can update 32 | bool public maintenance; 33 | 34 | /// @dev ### EVENTS 35 | /// @notice Emitted when admin update the eraPeriod 36 | event EraPeriodUpdate(uint256 indexed era, uint256 eraPeriod); 37 | 38 | /// @notice Emitted when new Era started 39 | event NewEraStart(uint256 indexed era, address caller); 40 | 41 | /** 42 | * @dev ### FUNCTIONS 43 | * @notice Initialize the contract to start from Era 1 44 | * @param _settings ISettings contract 45 | * @param _eraPeriod eraPeriod in seconds 46 | */ 47 | function initialize(ISettings _settings, uint256 _eraPeriod) external initializer { 48 | __Ownable_init(); 49 | require(_eraPeriod > 0, 'E001'); 50 | 51 | settings = _settings; 52 | eraPeriod = _eraPeriod; 53 | eraNumber = 1; 54 | emit NewEraStart(eraNumber, msg.sender); 55 | emit Parameter('eraPeriod', abi.encodePacked(eraPeriod)); 56 | emit Parameter('maintenance', abi.encodePacked(false)); 57 | } 58 | 59 | /** 60 | * @notice Update setting state. 61 | * @param _settings ISettings contract 62 | */ 63 | function setSettings(ISettings _settings) external onlyOwner { 64 | settings = _settings; 65 | } 66 | 67 | function enableMaintenance() external onlyOwner { 68 | maintenance = true; 69 | emit Parameter('maintenance', abi.encodePacked(true)); 70 | } 71 | 72 | function disableMaintenance() external onlyOwner { 73 | maintenance = false; 74 | emit Parameter('maintenance', abi.encodePacked(false)); 75 | } 76 | 77 | /** 78 | * @notice Start a new era if time already passed 79 | */ 80 | function startNewEra() public { 81 | require(!maintenance, 'G019'); 82 | require(eraStartTime + eraPeriod < block.timestamp, 'E002'); 83 | 84 | eraNumber++; 85 | eraStartTime = block.timestamp; 86 | 87 | // IInflationController inflationController = IInflationController(settings.getContractAddress(SQContracts.InflationController)); 88 | // if (inflationController.inflationRate() > 0) { 89 | // inflationController.mintInflatedTokens(); 90 | // } 91 | 92 | emit NewEraStart(eraNumber, msg.sender); 93 | } 94 | 95 | /** 96 | * @notice Start a new era if time already passed and return the new Era number 97 | * @return eraNumber New Era number 98 | */ 99 | function safeUpdateAndGetEra() external returns (uint256) { 100 | require(!maintenance, 'G019'); 101 | if (eraStartTime + eraPeriod < block.timestamp) { 102 | startNewEra(); 103 | } 104 | return eraNumber; 105 | } 106 | 107 | /** 108 | * @notice Utility function to calculate the EraNumber from a given timestamp 109 | * @param timestamp A given timestamp 110 | * @return eraNumber The calculated Era number 111 | */ 112 | function timestampToEraNumber(uint256 timestamp) external view returns (uint256) { 113 | require(timestamp >= eraStartTime, 'E003'); 114 | return eraNumber + ((timestamp - eraStartTime) / eraPeriod); 115 | } 116 | 117 | /** 118 | * @notice Update era period - admin only 119 | * @param newEraPeriod New Era Period to update 120 | */ 121 | function updateEraPeriod(uint256 newEraPeriod) external onlyOwner { 122 | require(newEraPeriod > 0, 'E001'); 123 | 124 | eraPeriod = newEraPeriod; 125 | 126 | emit EraPeriodUpdate(eraNumber, eraPeriod); 127 | emit Parameter('eraPeriod', abi.encodePacked(eraPeriod)); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /test-fuzz/PermissionedExchangeEchidnaTest.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity ^0.8.15; 5 | 6 | import '../contracts/PermissionedExchange.sol'; 7 | import '../contracts/Settings.sol'; 8 | import '../contracts/interfaces/ISettings.sol'; 9 | import "../contracts/root/SQToken.sol"; 10 | 11 | contract PermissionedExchangeEchidnaTest { 12 | PermissionedExchange internal pExchange; 13 | Settings internal settings; 14 | SQToken internal SQT; 15 | IERC20 internal USDC; 16 | 17 | constructor() { 18 | SQT = new SQToken(address(this)); 19 | settings = new Settings(); 20 | settings.setSQToken(address(SQT)); 21 | pExchange = new PermissionedExchange(); 22 | address[] memory t = new address[](1); 23 | t[0] = address(this); 24 | pExchange.initialize(ISettings(address(settings)), t); 25 | } 26 | 27 | // --- Math --- 28 | function add(uint256 x, uint256 y) internal pure returns (uint256 z) { 29 | z = x + y; 30 | assert(z >= x); // check if there is an addition overflow 31 | } 32 | 33 | function sub(uint256 x, uint256 y) internal pure returns (uint256 z) { 34 | z = x - y; 35 | assert(z <= x); // check if there is a subtraction overflow 36 | } 37 | 38 | /** test create a pairOrders, addQuota, trade, cancelOrder 39 | * @param _agive amountGive 40 | * @param _aget amountGet 41 | * @param _ed expireDate 42 | * @param _tgb tokenGiveBalance 43 | * @param _qm tradeAmount 44 | * createPairOrders(address(SQT), address(USDC), _agive, _aget, _ed, _tgb) 45 | * first order: 46 | * tokenGive: SQT Address 47 | * tokenGet: USDC Address 48 | * amountGive: _agive 49 | * amountGet: _aget 50 | * sender: this address 51 | * expireDate: _ed 52 | * pairOrderId: next order id 53 | * tokenGiveBalance: _tgb 54 | * 55 | * next order 56 | * tokenGive: USDC Address 57 | * tokenGet: SQT Address 58 | * amountGive: _aget 59 | * amountGet: _agive 60 | * sender: this address 61 | * expireDate: _ed 62 | * pairOrderId: first order id 63 | * tokenGiveBalance: 0 64 | */ 65 | function test_workflow(uint256 _agive, uint256 _aget, uint256 _ed, uint256 _tgb, uint256 _qm) public { 66 | uint256 firstOrderId = pExchange.nextOrderId(); 67 | pExchange.createPairOrders(address(SQT), address(USDC), _agive, _aget, _ed, _tgb); 68 | assert(pExchange.nextOrderId() == add(firstOrderId, 2)); 69 | (address ftokenGive,address ftokenGet,,,,,,) = pExchange.orders(firstOrderId); 70 | (address ntokenGive,address ntokenGet,,,,,,) = pExchange.orders(add(firstOrderId, 1)); 71 | assert(ftokenGive == address(SQT)); 72 | assert(ftokenGet == address(USDC)); 73 | assert(ntokenGive == address(USDC)); 74 | assert(ntokenGet == address(SQT)); 75 | 76 | test_addQuota(_qm); 77 | 78 | test_trade(add(firstOrderId, 1), _qm); 79 | 80 | test_cancelOrder(firstOrderId); 81 | } 82 | 83 | function test_addQuota(uint256 qm) public { 84 | uint256 quotaBefore = pExchange.tradeQuota(address(SQT), address(this)); 85 | pExchange.addQuota(address(SQT), address(this), qm); 86 | assert(pExchange.tradeQuota(address(SQT), address(this)) == add(quotaBefore,qm)); 87 | } 88 | 89 | function test_trade(uint256 id, uint256 amount) public { 90 | uint256 balanceBefore = SQT.balanceOf(address(pExchange)); 91 | (,,uint256 amountGive,uint256 amountGet,,,uint256 pairOrderId,uint256 tgbBefore) = pExchange.orders(id); 92 | (,,,,,,,uint256 ptgbBefore) = pExchange.orders(pairOrderId); 93 | uint256 newAmountGet = (amountGive * amount) / amountGet; 94 | pExchange.trade(id, amount); 95 | assert(SQT.balanceOf(address(pExchange)) == add(balanceBefore, amount)); 96 | (,,,,,,,uint256 tgb) = pExchange.orders(id); 97 | assert(tgb == sub(tgbBefore, newAmountGet)); 98 | (,,,,,,,uint256 tgbPair) = pExchange.orders(pairOrderId); 99 | assert(tgbPair == add(ptgbBefore, amount)); 100 | } 101 | 102 | function test_cancelOrder(uint256 id) public { 103 | uint256 balanceBefore = SQT.balanceOf(address(pExchange)); 104 | (,,,,,,uint256 pairOrderId, uint256 tgb) = pExchange.orders(id); 105 | pExchange.cancelOrder(id); 106 | assert(SQT.balanceOf(address(pExchange)) == sub(balanceBefore, tgb)); 107 | (address tg,,,,,,,) = pExchange.orders(id); 108 | assert(tg == address(0)); 109 | (,,,,,,uint256 poid,) = pExchange.orders(pairOrderId); 110 | assert(poid == 0); 111 | } 112 | } -------------------------------------------------------------------------------- /test/Vesting.test-data.ts: -------------------------------------------------------------------------------- 1 | export const PLANS = [ 2 | { 3 | name: 'seed', 4 | short: '3ye with 12mo lockup', 5 | planId: 0, 6 | lockPeriod: 31536000, 7 | vestingPeriod: 63072000, 8 | initialUnlockPercent: 0, 9 | }, 10 | { 11 | name: 'Series A', 12 | short: '2ye with 6mo lockup', 13 | planId: 1, 14 | lockPeriod: 15768000, 15 | vestingPeriod: 47304000, 16 | initialUnlockPercent: 0, 17 | }, 18 | { 19 | name: 'Series B', 20 | short: '1ye with 3mo lockup', 21 | planId: 2, 22 | lockPeriod: 7884000, 23 | vestingPeriod: 23652000, 24 | initialUnlockPercent: 0, 25 | }, 26 | { 27 | name: 'Strategic Round', 28 | short: '4ye with 24mo lockup', 29 | planId: 3, 30 | lockPeriod: 63072000, 31 | vestingPeriod: 63072000, 32 | initialUnlockPercent: 0, 33 | }, 34 | { 35 | name: 'Team & Advisers', 36 | short: '4ye with 24mo lockup', 37 | planId: 4, 38 | lockPeriod: 63072000, 39 | vestingPeriod: 63072000, 40 | initialUnlockPercent: 0, 41 | }, 42 | { 43 | name: 'Foundation & Community', 44 | short: '5ye vesting', 45 | planId: 5, 46 | lockPeriod: 0, 47 | vestingPeriod: 157680000, 48 | initialUnlockPercent: 0, 49 | }, 50 | ]; 51 | 52 | export const EXPECTATION: { total: number; monthly: number[] }[] = [ 53 | // seed 54 | { 55 | total: 1_165_000_000, 56 | monthly: [ 57 | // 1 month = 2628000 sec 58 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48_541_667, 97_083_333, 145_625_000, 194_166_667, 242_708_333, 59 | 291_250_000, 339_791_667, 388_333_333, 436_875_000, 485_416_667, 533_958_333, 582_500_000, 631_041_667, 60 | 679_583_333, 728_125_000, 776_666_667, 825_208_333, 873_750_000, 922_291_667, 970_833_333, 1_019_375_000, 61 | 1_067_916_667, 1_116_458_333, 1_165_000_000, 62 | ], 63 | }, 64 | // series A 65 | { 66 | total: 1_445_770_481, 67 | monthly: [ 68 | // 1 month = 2628000 sec 69 | 0, 0, 0, 0, 0, 0, 0, 80_320_582, 160_641_165, 240_961_747, 321_282_329, 401_602_911, 481_923_494, 70 | 562_244_076, 642_564_658, 722_885_241, 803_205_823, 883_526_405, 963_846_988, 1_044_167_570, 1_124_488_152, 71 | 1_204_808_734, 1_285_129_317, 1_365_449_899, 1_445_770_481, 72 | ], 73 | }, 74 | // series B 75 | { 76 | total: 320_210_262, 77 | monthly: [ 78 | // 1 month = 2628000 sec 79 | 0, 0, 0, 0, 35_578_918, 71_157_836, 106_736_754, 142_315_672, 177_894_590, 213_473_508, 249_052_426, 80 | 284_631_344, 320_210_262, 81 | ], 82 | }, 83 | // Strategic Round 84 | { 85 | total: 550_000_000, 86 | monthly: [ 87 | // 1 month = 2628000 sec 88 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22_916_667, 45_833_333, 89 | 68_750_000, 91_666_667, 114_583_333, 137_500_000, 160_416_667, 183_333_333, 206_250_000, 229_166_667, 90 | 252_083_333, 275_000_000, 297_916_667, 320_833_333, 343_750_000, 366_666_667, 389_583_333, 412_500_000, 91 | 435_416_667, 458_333_333, 481_250_000, 504_166_667, 527_083_333, 550_000_000, 92 | ], 93 | }, 94 | // Team & Advisers 95 | { 96 | total: 1_500_000_000, 97 | monthly: [ 98 | // 1 month = 2628000 sec 99 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62_500_000, 125_000_000, 100 | 187_500_000, 250_000_000, 312_500_000, 375_000_000, 437_500_000, 500_000_000, 562_500_000, 625_000_000, 101 | 687_500_000, 750_000_000, 812_500_000, 875_000_000, 937_500_000, 1_000_000_000, 1_062_500_000, 102 | 1_125_000_000, 1_187_500_000, 1_250_000_000, 1_312_500_000, 1_375_000_000, 1_437_500_000, 1_500_000_000, 103 | ], 104 | }, 105 | // Foundation & Community 106 | { 107 | total: 3_192_430_000, 108 | monthly: [ 109 | 0, 53_207_167, 106_414_333, 159_621_500, 212_828_667, 266_035_833, 319_243_000, 372_450_167, 425_657_333, 110 | 478_864_500, 532_071_667, 585_278_833, 638_486_000, 691_693_167, 744_900_333, 798_107_500, 851_314_667, 111 | 904_521_833, 957_729_000, 1_010_936_167, 1_064_143_333, 1_117_350_500, 1_170_557_667, 1_223_764_833, 112 | 1_276_972_000, 1_330_179_167, 1_383_386_333, 1_436_593_500, 1_489_800_667, 1_543_007_833, 1_596_215_000, 113 | 1_649_422_167, 1_702_629_333, 1_755_836_500, 1_809_043_667, 1_862_250_833, 1_915_458_000, 1_968_665_167, 114 | 2_021_872_333, 2_075_079_500, 2_128_286_667, 2_181_493_833, 2_234_701_000, 2_287_908_167, 2_341_115_333, 115 | 2_394_322_500, 2_447_529_667, 2_500_736_833, 2_553_944_000, 2_607_151_167, 2_660_358_333, 2_713_565_500, 116 | 2_766_772_667, 2_819_979_833, 2_873_187_000, 2_926_394_167, 2_979_601_333, 3_032_808_500, 3_086_015_667, 117 | 3_139_222_833, 3_192_430_000, 118 | ], 119 | }, 120 | ]; 121 | -------------------------------------------------------------------------------- /test/EraManager.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | import { expect } from 'chai'; 5 | import { ethers, waffle } from 'hardhat'; 6 | import { EraManager } from '../src'; 7 | import { lastestBlockTime, timeTravel } from './helper'; 8 | import { deployContracts } from './setup'; 9 | const { time } = require('@openzeppelin/test-helpers'); 10 | 11 | describe('Era Manager Contract', () => { 12 | let wallet_0, wallet_1; 13 | let eraManager: EraManager; 14 | 15 | const deployer = () => deployContracts(wallet_0, wallet_1); 16 | before(async () => { 17 | [wallet_0, wallet_1] = await ethers.getSigners(); 18 | }); 19 | 20 | beforeEach(async () => { 21 | const deployment = await waffle.loadFixture(deployer); 22 | eraManager = deployment.eraManager; 23 | }); 24 | 25 | describe('Start new era', () => { 26 | it('check default config', async () => { 27 | // check default era values 28 | expect(await eraManager.eraPeriod()).to.equal(time.duration.days(1).toNumber()); 29 | expect(await eraManager.eraNumber()).to.equal(1); 30 | expect(await eraManager.eraStartTime()).to.equal(0); 31 | }); 32 | 33 | it('start new era should work', async () => { 34 | // start new era 35 | expect(await eraManager.startNewEra()) 36 | .to.be.emit(eraManager, 'NewEraStart') 37 | .withArgs(2, wallet_0.address); 38 | 39 | // check updates 40 | expect(await eraManager.eraNumber()).to.equal(2); 41 | expect(Number(await eraManager.eraStartTime())).to.greaterThanOrEqual(0); 42 | }); 43 | 44 | it('start new era with active era should failed', async () => { 45 | // start new era 46 | await eraManager.startNewEra(); 47 | // try to start new era again 48 | await expect(eraManager.startNewEra()).to.be.revertedWith('E002'); 49 | }); 50 | }); 51 | 52 | describe('Update era', () => { 53 | it('update era period should work', async () => { 54 | // update era period 55 | expect(await eraManager.updateEraPeriod(10)) 56 | .to.be.emit(eraManager, 'EraPeriodUpdate') 57 | .withArgs(1, 10); 58 | // check updates 59 | expect(await eraManager.eraPeriod()).to.equal(10); 60 | }); 61 | 62 | it('safe update era should work', async () => { 63 | // safe update era 64 | await eraManager.safeUpdateAndGetEra(); 65 | expect(await eraManager.eraNumber()).to.equal(2); 66 | await eraManager.safeUpdateAndGetEra(); 67 | expect(await eraManager.eraNumber()).to.equal(2); 68 | 69 | // Safe update era after era preriod changed should work 70 | await eraManager.updateEraPeriod(time.duration.days(1).toNumber()); 71 | await timeTravel(time.duration.days(2).toNumber()); 72 | await eraManager.safeUpdateAndGetEra(); 73 | expect(await eraManager.eraNumber()).to.equal(3); 74 | }); 75 | }); 76 | 77 | describe('timestampToEraNumber', () => { 78 | beforeEach(async () => { 79 | //setup era period be 3 days 80 | await eraManager.connect(wallet_0).updateEraPeriod(time.duration.days(3).toString()); 81 | }); 82 | it('timestampToEraNumber should work', async () => { 83 | await timeTravel(time.duration.days(3).toNumber()); 84 | await eraManager.safeUpdateAndGetEra(); 85 | expect(await eraManager.eraNumber()).to.equal(2); 86 | let timestamp = await lastestBlockTime(); 87 | expect(await eraManager.timestampToEraNumber(timestamp)).to.equal(2); 88 | timestamp += time.duration.days(13).toNumber(); 89 | expect(await eraManager.timestampToEraNumber(timestamp)).to.equal(6); 90 | await timeTravel(time.duration.days(3).toNumber()); 91 | await eraManager.safeUpdateAndGetEra(); 92 | await timeTravel(time.duration.days(3).toNumber()); 93 | await eraManager.safeUpdateAndGetEra(); 94 | await timeTravel(time.duration.days(3).toNumber()); 95 | await eraManager.safeUpdateAndGetEra(); 96 | await timeTravel(time.duration.days(4).toNumber()); 97 | await eraManager.safeUpdateAndGetEra(); 98 | expect(await eraManager.eraNumber()).to.equal(6); 99 | await eraManager.safeUpdateAndGetEra(); 100 | expect(await eraManager.eraNumber()).to.equal(6); 101 | }); 102 | it('passed timestamp 2 EraNumber should fail', async () => { 103 | await timeTravel(time.duration.days(3).toNumber()); 104 | await eraManager.safeUpdateAndGetEra(); 105 | let timestamp = (await eraManager.eraStartTime()).toNumber(); 106 | expect(await eraManager.timestampToEraNumber(timestamp)).to.equal(2); 107 | timestamp -= 100; 108 | await expect(eraManager.timestampToEraNumber(timestamp)).to.be.revertedWith('E003'); 109 | }); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /test/SQTGift.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | import { expect } from 'chai'; 5 | import { ethers, waffle } from 'hardhat'; 6 | import { eventFrom, eventsFrom } from './helper'; 7 | import { deployContracts } from './setup'; 8 | import { SQTGift } from '../src'; 9 | import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; 10 | 11 | describe('SQT Gift Nft', () => { 12 | let wallet_0: SignerWithAddress, wallet_1: SignerWithAddress, wallet_2: SignerWithAddress; 13 | let nft: SQTGift; 14 | 15 | const deployer = async () => { 16 | const deployment = await deployContracts(wallet_0, wallet_1); 17 | return deployment.sqtGift; 18 | }; 19 | before(async () => { 20 | [wallet_0, wallet_1, wallet_2] = await ethers.getSigners(); 21 | }); 22 | 23 | beforeEach(async () => { 24 | nft = await waffle.loadFixture(deployer); 25 | }); 26 | 27 | describe('Serie Config', () => { 28 | it('add series', async () => { 29 | const tx = await nft.createSeries(100, 'abc'); 30 | const event = await eventFrom(tx, nft, 'SeriesCreated(uint256,uint256,string)'); 31 | expect(event.seriesId).to.eq(0); 32 | expect(event.maxSupply).to.eq(100); 33 | expect(event.tokenURI).to.eq('abc'); 34 | }); 35 | it('add allowList', async () => { 36 | await nft.createSeries(100, 'abc'); 37 | const tx = await nft.addToAllowlist(0, wallet_1.address, 1); 38 | const event = await eventFrom(tx, nft, 'AllowListAdded(address,uint256,uint8)'); 39 | expect(event.account).to.eq(wallet_1.address); 40 | expect(event.seriesId).to.eq(0); 41 | }); 42 | it('add allowList for non exist series', async () => { 43 | await expect(nft.addToAllowlist(0, wallet_1.address, 1)).to.revertedWith('SQG001'); 44 | await nft.createSeries(100, 'abc'); 45 | await expect(nft.addToAllowlist(0, wallet_1.address, 1)).not.reverted; 46 | }); 47 | it('batch add allowList', async () => { 48 | await nft.createSeries(100, 'token0'); 49 | await nft.createSeries(100, 'token1'); 50 | const tx = await nft.batchAddToAllowlist( 51 | [0, 0, 1, 0], 52 | [wallet_1.address, wallet_2.address, wallet_1.address, wallet_1.address], 53 | [1, 2, 1, 5] 54 | ); 55 | const events = await eventsFrom(tx, nft, 'AllowListAdded(address,uint256,uint8)'); 56 | expect(events.length).to.eq(4); 57 | expect(await nft.allowlist(wallet_1.address, 0)).to.eq(6); 58 | expect(await nft.allowlist(wallet_1.address, 1)).to.eq(1); 59 | expect(await nft.allowlist(wallet_2.address, 0)).to.eq(2); 60 | expect(await nft.allowlist(wallet_2.address, 1)).to.eq(0); 61 | }); 62 | }); 63 | 64 | describe('Mint Tokens', () => { 65 | beforeEach(async () => { 66 | await nft.createSeries(100, 'series0'); 67 | await nft.createSeries(50, 'series1'); 68 | await nft.addToAllowlist(0, wallet_1.address, 1); 69 | await nft.addToAllowlist(1, wallet_1.address, 1); 70 | }); 71 | it('mint with allowed wallet', async () => { 72 | let tx = await nft.connect(wallet_1).mint(0); 73 | const event = await eventFrom(tx, nft, 'Transfer(address,address,uint256)'); 74 | expect(await nft.ownerOf(event.tokenId)).to.eq(wallet_1.address); 75 | tx = await nft.connect(wallet_1).mint(1); 76 | const event2 = await eventFrom(tx, nft, 'Transfer(address,address,uint256)'); 77 | expect(event.tokenId).not.eq(event2.tokenId); 78 | }); 79 | it('mint with allowed wallet 2', async () => { 80 | await nft.connect(wallet_1).mint(0); 81 | await expect(nft.connect(wallet_1).mint(0)).to.revertedWith('SQG002'); 82 | await nft.addToAllowlist(0, wallet_1.address, 1); 83 | await nft.connect(wallet_1).mint(0); 84 | const token2Series = await nft.getSeries(1); 85 | expect(token2Series).to.eq(0); 86 | }); 87 | it('can not mint when exceed max supply', async () => { 88 | // series 2 89 | await nft.createSeries(1, 'series1'); 90 | await nft.addToAllowlist(2, wallet_1.address, 2); 91 | await nft.connect(wallet_1).mint(2); 92 | await expect(nft.connect(wallet_1).mint(2)).to.revertedWith('SQG005'); 93 | await nft.setMaxSupply(2, 2); 94 | await expect(nft.connect(wallet_1).mint(2)).not.reverted; 95 | }); 96 | it('can not mint when deactived', async () => { 97 | await nft.setSeriesActive(0, false); 98 | await expect(nft.connect(wallet_1).mint(0)).to.revertedWith('SQG004'); 99 | await nft.setSeriesActive(0, true); 100 | await expect(nft.connect(wallet_1).mint(0)).not.reverted; 101 | }); 102 | it('can batch mint token', async () => { 103 | await nft.addToAllowlist(0, wallet_2.address, 10); 104 | await nft.connect(wallet_2).batchMint(0); 105 | expect(await nft.balanceOf(wallet_2.address)).to.eq(10); 106 | }); 107 | }); 108 | }); 109 | -------------------------------------------------------------------------------- /contracts/TokenExchange.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | pragma solidity ^0.8.15; 5 | 6 | import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol'; 7 | import '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol'; 8 | import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; 9 | import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; 10 | 11 | import './interfaces/ISQToken.sol'; 12 | 13 | /** 14 | * @title Token Exchange Contract 15 | * @notice This smart contract enables single-direction token trading between two predefined tokens ('tokenGet' and 'tokenGive'). 16 | * It features owner-exclusive rights for trade cancellations, adding a layer of control and security. After each trade, 17 | * the 'tokenGet' is automatically burned, reducing its total supply in circulation. This contract is ideal for controlled 18 | * trading environments with a focus on token supply management. 19 | */ 20 | contract TokenExchange is Initializable, OwnableUpgradeable { 21 | using SafeERC20 for IERC20; 22 | 23 | struct ExchangeOrder { 24 | address tokenGive; 25 | address tokenGet; 26 | uint256 amountGive; 27 | uint256 amountGet; 28 | address sender; 29 | uint256 tokenGiveBalance; 30 | } 31 | 32 | /// @dev ### STATES 33 | /// @notice The next order Id 34 | uint256 public nextOrderId; 35 | /// @notice Orders: orderId => ExchangeOrder 36 | mapping(uint256 => ExchangeOrder) public orders; 37 | 38 | /// @dev ### EVENTS 39 | /// @notice Emitted when exchange order sent. 40 | event ExchangeOrderSent( 41 | uint256 indexed orderId, 42 | address sender, 43 | address tokenGive, 44 | address tokenGet, 45 | uint256 amountGive, 46 | uint256 amountGet, 47 | uint256 tokenGiveBalance 48 | ); 49 | /// @notice Emitted when expired exchange order settled. 50 | event OrderSettled( 51 | uint256 indexed orderId, 52 | address tokenGive, 53 | address tokenGet, 54 | uint256 amountGive 55 | ); 56 | /// @notice Emitted when trader trade on exist orders. 57 | event Trade( 58 | uint256 indexed orderId, 59 | address tokenGive, 60 | address tokenGet, 61 | uint256 amountGive, 62 | uint256 amountGet 63 | ); 64 | 65 | function initialize() external initializer { 66 | __Ownable_init(); 67 | 68 | nextOrderId = 1; 69 | } 70 | 71 | /** 72 | * @notice only onwer have the permission to send the order for now, and traders can do exchanges on these exist orders. 73 | * @param _tokenGive The token address order want give. 74 | * @param _tokenGet The token address order want get 75 | . * @param _tokenGiveBalance The balance of order give token amount. 76 | */ 77 | function sendOrder( 78 | address _tokenGive, 79 | address _tokenGet, 80 | uint256 _amountGive, 81 | uint256 _amountGet, 82 | uint256 _tokenGiveBalance 83 | ) public onlyOwner { 84 | require(_tokenGiveBalance > 0, 'TE001'); 85 | 86 | IERC20(_tokenGive).safeTransferFrom(msg.sender, address(this), _tokenGiveBalance); 87 | orders[nextOrderId] = ExchangeOrder( 88 | _tokenGive, 89 | _tokenGet, 90 | _amountGive, 91 | _amountGet, 92 | msg.sender, 93 | _tokenGiveBalance 94 | ); 95 | 96 | emit ExchangeOrderSent( 97 | nextOrderId, 98 | msg.sender, 99 | _tokenGive, 100 | _tokenGet, 101 | _amountGive, 102 | _amountGet, 103 | _tokenGiveBalance 104 | ); 105 | 106 | nextOrderId += 1; 107 | } 108 | 109 | /** 110 | * @notice Owner can cancel the sent order anytime, and this will return leftgiven token back to owner. 111 | * @param orderId The order id to cancel. 112 | */ 113 | function cancelOrder(uint256 orderId) public onlyOwner { 114 | ExchangeOrder memory order = orders[orderId]; 115 | require(order.sender != address(0), 'TE002'); 116 | 117 | if (order.tokenGiveBalance != 0) { 118 | IERC20(order.tokenGive).safeTransfer(order.sender, order.tokenGiveBalance); 119 | } 120 | 121 | emit OrderSettled(orderId, order.tokenGive, order.tokenGet, order.tokenGiveBalance); 122 | 123 | delete orders[orderId]; 124 | } 125 | 126 | /** 127 | * @notice Traders do exchange on exchange orders. The trading rate is 1:1. Token get will be burn after trading 128 | * @param orderId The order id to trade. 129 | * @param amount The amount to trade. 130 | */ 131 | function trade(uint256 orderId, uint256 amount) public { 132 | ExchangeOrder storage order = orders[orderId]; 133 | require(order.sender != address(0), 'TE002'); 134 | 135 | ISQToken(order.tokenGet).burnFrom(msg.sender, amount); 136 | 137 | uint256 amountGive = (amount * order.amountGive) / order.amountGet; 138 | require(amountGive <= order.tokenGiveBalance, 'TE003'); 139 | 140 | IERC20(order.tokenGive).safeTransfer(msg.sender, amountGive); 141 | order.tokenGiveBalance = order.tokenGiveBalance - amountGive; 142 | 143 | emit Trade(orderId, order.tokenGive, order.tokenGet, amountGive, amount); 144 | } 145 | } 146 | --------------------------------------------------------------------------------