diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..f7d582e6b --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +packages/legacy/verified/** -text diff --git a/.prettierignore b/.prettierignore index 40e18c5ea..1b32b6c76 100644 --- a/.prettierignore +++ b/.prettierignore @@ -36,3 +36,6 @@ deployments/hardhat/ **/chain-*/ **/*-localhost/ **/*-hardhat/ + +# Verified mainnet source, must stay byte-identical +packages/legacy/verified/ diff --git a/.solhintignore b/.solhintignore new file mode 100644 index 000000000..6a9effc81 --- /dev/null +++ b/.solhintignore @@ -0,0 +1 @@ +packages/legacy/verified/ diff --git a/package.json b/package.json index def4914ca..e5d8ea1db 100644 --- a/package.json +++ b/package.json @@ -78,8 +78,8 @@ "scripts/lint-staged-run.sh 'prettier -w --cache --log-level warn'" ], "*.sol": [ - "solhint --fix --noPrompt --noPoster", - "prettier -w --cache --log-level warn" + "scripts/lint-staged-run.sh 'solhint --fix --noPrompt --noPoster'", + "scripts/lint-staged-run.sh 'prettier -w --cache --log-level warn'" ], "*.md": [ "markdownlint --fix", diff --git a/packages/legacy/README.md b/packages/legacy/README.md new file mode 100644 index 000000000..060847999 --- /dev/null +++ b/packages/legacy/README.md @@ -0,0 +1,39 @@ +# Legacy mainnet implementations + +A frozen archive of the source of seven implementations running on Ethereum mainnet, as verified on [Sourcify](https://sourcify.dev). Each contract has its own bundle under `verified//`, holding the verified sources and the compiler version and settings. + +This package is private and is not published. It holds no Solidity imports from the workspace and no tests of contract behaviour. + +| Contract | Implementation | Proxy | solc | +| ------------------- | -------------------------------------------- | -------------------------------------------- | ----- | +| GraphToken | `0xc944E90C64B2c07662A292be6244BDf05Cda44a7` | none | 0.7.4 | +| L1GraphTokenGateway | `0xD41ca6A1d034D178c196DFa916f22f7D1a1B8222` | `0x01cDC91B0A9bA741903aA3699BF4CE31d6C5cC06` | 0.7.6 | +| BridgeEscrow | `0xBcD54513aa593646d72aEA31406c633C235Ad6EA` | `0x36aFF7001294daE4C2ED4fDEfC478a00De77F090` | 0.7.6 | +| L1GNS | `0x83A5B6C542aB20436087Fc79724bdF06ea80f836` | `0xaDcA0dd4729c8BA3aCf3E99F3A9f471EF37b6825` | 0.7.6 | +| Curation | `0xDeb46851907fd85DD475780CcE2eE0D67c969825` | `0x8FE00a685Bcb3B2cc296ff6FfEaB10acA4CE1538` | 0.7.6 | +| L1Staking | `0x20a14D78848BC8B3F3d4B77239F2adC3C0259A10` | `0xF55041E37E12cD407ad00CE2910B8269B01263b9` | 0.7.6 | +| StakingExtension | `0xA479c00cDa8C07bce458D7a826C7b091672EB92C` | none | 0.7.6 | + +When this archive was created, the workspace source reproduced only Curation. + +GraphToken and L1Staking are Sourcify partial matches (`sourcifyMatch: "match"`), so their sources may differ from the deployed ones outside the code. + +## Checks + +`pnpm test:self` needs no network access. It compares every source file with `sourceHashes` in `compiler.json`, compiles every bundle with its exact solc-js version, and compares the result with the hash of the on-chain code in `mainnet.json`. The comparison ignores metadata and immutable values. + +## Sources must stay unformatted + +Never edit or format anything under `verified/`. Formatting doesn't change bytecode, so only the source hashes catch it. `.prettierignore`, `.solhintignore`, `scripts/lint-staged-run.sh` and `.gitattributes` keep tools and git from rewriting these files, and `GraphToken.sol` keeps its CRLF line endings. Don't add a `.solhint.json` to this package. + +## Re-running the scripts + +```bash +# Rewrite verified/ from Sourcify, using the addresses in targets.json +node scripts/fetch-verified.mjs + +# Check the addresses on-chain at a finalized block and rewrite mainnet.json +MAINNET_RPC_URL= node scripts/record-mainnet.mjs +``` + +`record-mainnet` exits 1 if a proxy, or L1Staking's `extensionImpl`, no longer points at the listed implementation. Pass the RPC URL only as an inline env var, and never commit it. diff --git a/packages/legacy/mainnet.json b/packages/legacy/mainnet.json new file mode 100644 index 000000000..f7571d850 --- /dev/null +++ b/packages/legacy/mainnet.json @@ -0,0 +1,40 @@ +{ + "block": 26306549, + "contracts": { + "GraphToken": { + "implementation": "0xc944E90C64B2c07662A292be6244BDf05Cda44a7", + "proxy": null, + "runtimeHash": "43e4307034b17a35b4411b390f76d0b7278c95408743f4e7104d7f11327012ee" + }, + "L1GraphTokenGateway": { + "implementation": "0xD41ca6A1d034D178c196DFa916f22f7D1a1B8222", + "proxy": "0x01cDC91B0A9bA741903aA3699BF4CE31d6C5cC06", + "runtimeHash": "40b384d3427296452c81f952e3434c2394c24661ea4d42f7e8e3a2a589001eaf" + }, + "BridgeEscrow": { + "implementation": "0xBcD54513aa593646d72aEA31406c633C235Ad6EA", + "proxy": "0x36aFF7001294daE4C2ED4fDEfC478a00De77F090", + "runtimeHash": "26e16ae208b7353a0f44e768f4f40e6467758b7004394a8a640a8a4ea29bce9e" + }, + "L1GNS": { + "implementation": "0x83A5B6C542aB20436087Fc79724bdF06ea80f836", + "proxy": "0xaDcA0dd4729c8BA3aCf3E99F3A9f471EF37b6825", + "runtimeHash": "f88f9f204bd4f121019d8eb9164a1952f0ae0e79ed602088206e2059d3a51515" + }, + "Curation": { + "implementation": "0xDeb46851907fd85DD475780CcE2eE0D67c969825", + "proxy": "0x8FE00a685Bcb3B2cc296ff6FfEaB10acA4CE1538", + "runtimeHash": "c8b0bdcab52a661ff3a8615f5bf29336615832f4f0b4b3cf4e2506c6b22a87b6" + }, + "L1Staking": { + "implementation": "0x20a14D78848BC8B3F3d4B77239F2adC3C0259A10", + "proxy": "0xF55041E37E12cD407ad00CE2910B8269B01263b9", + "runtimeHash": "692f896bc98b60d256e121d90e2111108dfcfd14ccc1aa935f8be645ffb7f8d6" + }, + "StakingExtension": { + "implementation": "0xA479c00cDa8C07bce458D7a826C7b091672EB92C", + "proxy": null, + "runtimeHash": "3f13acd82e366c4a32dd279d795ad0fd3a60b0b5b3e672ca82ca20e3371edce2" + } + } +} diff --git a/packages/legacy/package.json b/packages/legacy/package.json new file mode 100644 index 000000000..187ad68df --- /dev/null +++ b/packages/legacy/package.json @@ -0,0 +1,14 @@ +{ + "name": "@graphprotocol/legacy", + "version": "0.0.0", + "private": true, + "description": "Archive of the Sourcify-verified source of The Graph's mainnet implementations", + "license": "GPL-2.0-or-later", + "scripts": { + "test:self": "node --test 'scripts/*.test.mjs' && node scripts/verify.mjs" + }, + "devDependencies": { + "solc-0.7.4": "npm:solc@0.7.4", + "solc-0.7.6": "npm:solc@0.7.6" + } +} diff --git a/packages/legacy/scripts/fetch-verified.mjs b/packages/legacy/scripts/fetch-verified.mjs new file mode 100644 index 000000000..0f397d190 --- /dev/null +++ b/packages/legacy/scripts/fetch-verified.mjs @@ -0,0 +1,51 @@ +#!/usr/bin/env node +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +import { sha256 } from './lib.mjs' + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') +const SOURCIFY = 'https://sourcify.dev/server/v2/contract/1' + +async function fetchBundle(implementation) { + const response = await fetch(`${SOURCIFY}/${implementation}?fields=stdJsonInput,compilation`) + if (!response.ok) throw new Error(`sourcify ${implementation}: HTTP ${response.status}`) + return response.json() +} + +function writeBundle(name, { match, stdJsonInput, compilation }) { + if (!match) throw new Error(`${name}: not verified on Sourcify`) + const dir = path.join(root, 'verified', name) + const sourcesDir = path.join(dir, 'sources') + fs.rmSync(dir, { recursive: true, force: true }) + + const sourceHashes = {} + for (const [file, { content }] of Object.entries(stdJsonInput.sources)) { + const dest = path.resolve(sourcesDir, file) + if (!dest.startsWith(sourcesDir + path.sep)) throw new Error(`${name}: unsafe source path ${file}`) + fs.mkdirSync(path.dirname(dest), { recursive: true }) + fs.writeFileSync(dest, content) + sourceHashes[file] = sha256(fs.readFileSync(dest)) + } + + const { outputSelection: _, ...settings } = stdJsonInput.settings + const split = compilation.fullyQualifiedName.lastIndexOf(':') + const compiler = { + version: compilation.compilerVersion, + settings, + target: { + source: compilation.fullyQualifiedName.slice(0, split), + name: compilation.fullyQualifiedName.slice(split + 1), + }, + sourcifyMatch: match, + sourceHashes, + } + fs.writeFileSync(path.join(dir, 'compiler.json'), JSON.stringify(compiler, null, 2) + '\n') + console.log(`${name}: ${Object.keys(sourceHashes).length} files, ${match}, ${compiler.version}`) +} + +const targets = JSON.parse(fs.readFileSync(path.join(root, 'targets.json'), 'utf8')) +for (const [name, { implementation }] of Object.entries(targets)) { + writeBundle(name, await fetchBundle(implementation)) +} diff --git a/packages/legacy/scripts/lib.mjs b/packages/legacy/scripts/lib.mjs new file mode 100644 index 000000000..77b982cc1 --- /dev/null +++ b/packages/legacy/scripts/lib.mjs @@ -0,0 +1,97 @@ +import { createHash } from 'node:crypto' +import fs from 'node:fs' +import { createRequire } from 'node:module' +import path from 'node:path' + +const require = createRequire(import.meta.url) + +export const sha256 = (data) => createHash('sha256').update(data).digest('hex') + +export function normalizeRuntime(hex, ranges) { + const code = hex.toLowerCase().replace(/^0x/, '') + const metadataLength = parseInt(code.slice(-4), 16) + const stripped = code.slice(0, code.length - 2 * (metadataLength + 2)) + return ranges.reduce((acc, { start, length }) => { + const [from, to] = [2 * start, 2 * (start + length)] + return acc.slice(0, from) + '0'.repeat(to - from) + acc.slice(to) + }, stripped) +} + +export function runtimeRanges(deployedBytecode) { + const { immutableReferences = {}, linkReferences = {} } = deployedBytecode + return [ + ...Object.values(immutableReferences).flat(), + ...Object.values(linkReferences) + .flatMap((libs) => Object.values(libs)) + .flat(), + ].map(({ start, length }) => ({ start, length })) +} + +export const runtimeHash = (normalized) => sha256(normalized) + +const readCompiler = (dir) => JSON.parse(fs.readFileSync(path.join(dir, 'compiler.json'), 'utf8')) + +function listSources(dir) { + const root = path.join(dir, 'sources') + return fs + .readdirSync(root, { recursive: true, withFileTypes: true }) + .filter((entry) => entry.isFile()) + .map((entry) => path.relative(root, path.join(entry.parentPath, entry.name)).split(path.sep).join('/')) + .sort() +} + +export function compileBundle(dir) { + const { version, settings, target } = readCompiler(dir) + const solc = require(`solc-${version.split('+')[0]}`) + if (!solc.version().startsWith(version)) throw new Error(`expected solc ${version}, loaded ${solc.version()}`) + + const sources = Object.fromEntries( + listSources(dir).map((file) => [file, { content: fs.readFileSync(path.join(dir, 'sources', file), 'utf8') }]), + ) + const outputSelection = { + '*': { + '*': [ + 'evm.deployedBytecode.object', + 'evm.deployedBytecode.immutableReferences', + 'evm.deployedBytecode.linkReferences', + 'storageLayout', + ], + }, + } + const input = { language: 'Solidity', sources, settings: { ...settings, outputSelection } } + const output = JSON.parse(solc.compile(JSON.stringify(input))) + + const errors = (output.errors ?? []).filter((e) => e.severity === 'error') + if (errors.length) throw new Error(`${dir}:\n${errors.map((e) => e.formattedMessage).join('\n')}`) + + const contract = output.contracts?.[target.source]?.[target.name] + if (!contract) throw new Error(`${dir}: no output for ${target.source}:${target.name}`) + return { + object: contract.evm.deployedBytecode.object, + ranges: runtimeRanges(contract.evm.deployedBytecode), + storageLayout: contract.storageLayout, + } +} + +export function compareAddresses(targets, contracts) { + return Object.keys(targets) + .filter((name) => name in contracts) + .flatMap((name) => + ['implementation', 'proxy'] + .filter((key) => String(targets[name][key]).toLowerCase() !== String(contracts[name][key]).toLowerCase()) + .map( + (key) => `${name}: ${key} is ${targets[name][key]} in targets.json, ${contracts[name][key]} in mainnet.json`, + ), + ) +} + +export function checkSources(dir) { + const { sourceHashes } = readCompiler(dir) + const found = new Set(listSources(dir)) + const changed = [...found].filter( + (file) => + !(file in sourceHashes) || sha256(fs.readFileSync(path.join(dir, 'sources', file))) !== sourceHashes[file], + ) + const missing = Object.keys(sourceHashes).filter((file) => !found.has(file)) + return [...changed, ...missing].sort() +} diff --git a/packages/legacy/scripts/lib.test.mjs b/packages/legacy/scripts/lib.test.mjs new file mode 100644 index 000000000..e0e247ce2 --- /dev/null +++ b/packages/legacy/scripts/lib.test.mjs @@ -0,0 +1,105 @@ +import assert from 'node:assert/strict' +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import { test } from 'node:test' +import { fileURLToPath } from 'node:url' + +import { checkSources, compareAddresses, compileBundle, normalizeRuntime, runtimeHash, runtimeRanges } from './lib.mjs' + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') +const BRIDGE_ESCROW_DIR = path.join(root, 'verified/BridgeEscrow') + +const readMainnet = () => JSON.parse(fs.readFileSync(path.join(root, 'mainnet.json'), 'utf8')).contracts +const targetSource = (dir) => JSON.parse(fs.readFileSync(path.join(dir, 'compiler.json'), 'utf8')).target.source + +function copyBundle(t) { + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'legacy-test-')) + t.after(() => fs.rmSync(tmp, { recursive: true, force: true })) + const dir = path.join(tmp, 'BridgeEscrow') + fs.cpSync(BRIDGE_ESCROW_DIR, dir, { recursive: true }) + return dir +} + +function editTarget(dir, edit) { + const file = path.join(dir, 'sources', targetSource(dir)) + const before = fs.readFileSync(file, 'utf8') + const after = edit(before) + assert.notEqual(after, before) + fs.writeFileSync(file, after) +} + +test('normalizeRuntime zeroes ranges and removes metadata', () => { + // 4 code bytes, then a 2-byte CBOR blob and its 2-byte length + assert.equal(normalizeRuntime('0xAABBCCDD' + 'eeff' + '0002', [{ start: 1, length: 2 }]), 'aa0000dd') +}) + +test('runtimeRanges reads immutable and link references', () => { + const ranges = runtimeRanges({ + immutableReferences: { 1: [{ start: 3, length: 32 }] }, + linkReferences: { 'a.sol': { Lib: [{ start: 40, length: 20 }] } }, + }) + assert.deepEqual(ranges, [ + { start: 3, length: 32 }, + { start: 40, length: 20 }, + ]) +}) + +test('verified BridgeEscrow matches mainnet.json', () => { + const { object, ranges } = compileBundle(BRIDGE_ESCROW_DIR) + assert.equal(runtimeHash(normalizeRuntime(object, ranges)), readMainnet().BridgeEscrow.runtimeHash) +}) + +test('a code change no longer matches mainnet.json', (t) => { + const dir = copyBundle(t) + editTarget(dir, (src) => + src.replace( + /contract BridgeEscrow\b[^{]*\{/, + (m) => `${m} function inserted() external pure returns (uint256) { return 1; }`, + ), + ) + const { object, ranges } = compileBundle(dir) + assert.notEqual(runtimeHash(normalizeRuntime(object, ranges)), readMainnet().BridgeEscrow.runtimeHash) +}) + +test('a comment-only change fails the source check', (t) => { + const dir = copyBundle(t) + assert.deepEqual(checkSources(dir), []) + editTarget(dir, (src) => `${src}// comment\n`) + assert.deepEqual(checkSources(dir), [targetSource(dir)]) +}) + +test('checkSources reports missing and extra files', (t) => { + const dir = copyBundle(t) + fs.rmSync(path.join(dir, 'sources', targetSource(dir))) + fs.writeFileSync(path.join(dir, 'sources/extra.sol'), '') + assert.deepEqual(checkSources(dir).sort(), ['extra.sol', targetSource(dir)].sort()) +}) + +test('compareAddresses reports mismatched addresses', () => { + const targets = { + A: { implementation: '0xAbC', proxy: '0xDeF' }, + B: { implementation: '0x123', proxy: null }, + } + assert.deepEqual( + compareAddresses(targets, { + A: { implementation: '0xabc', proxy: '0xdef', runtimeHash: 'x' }, + B: { implementation: '0x123', proxy: null, runtimeHash: 'y' }, + }), + [], + ) + assert.deepEqual( + compareAddresses(targets, { + A: { implementation: '0xbad', proxy: '0xDeF' }, + B: { implementation: '0x123', proxy: '0xbad' }, + }), + [ + 'A: implementation is 0xAbC in targets.json, 0xbad in mainnet.json', + 'B: proxy is null in targets.json, 0xbad in mainnet.json', + ], + ) +}) + +test('compareAddresses skips contracts missing from either file', () => { + assert.deepEqual(compareAddresses({ A: { implementation: '0x1', proxy: null } }, {}), []) +}) diff --git a/packages/legacy/scripts/record-mainnet.mjs b/packages/legacy/scripts/record-mainnet.mjs new file mode 100644 index 000000000..00c8d7ccf --- /dev/null +++ b/packages/legacy/scripts/record-mainnet.mjs @@ -0,0 +1,76 @@ +#!/usr/bin/env node +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +import { compileBundle, normalizeRuntime, runtimeHash } from './lib.mjs' + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') +const EIP1967_IMPLEMENTATION_SLOT = '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + +function fail(message) { + console.error(message) + process.exit(1) +} + +const url = process.env.MAINNET_RPC_URL +if (!url) fail('MAINNET_RPC_URL is required') + +// Errors from fetch can carry the URL, so they are replaced rather than rethrown +async function rpc(method, params) { + let response + try { + response = await fetch(url, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }), + }) + } catch { + throw new Error(`${method}: request failed`) + } + if (!response.ok) throw new Error(`${method}: HTTP ${response.status}`) + const { result, error } = await response.json() + if (error) throw new Error(`${method}: RPC error ${error.code}`) + return result +} + +function extensionImplSlot(storageLayout) { + const entries = storageLayout.storage.filter((entry) => entry.label === 'extensionImpl') + if (entries.length !== 1) fail(`expected one extensionImpl in L1Staking layout, found ${entries.length}`) + // The check below reads the low 20 bytes, which is only this variable at offset 0 + if (entries[0].offset !== 0) fail(`extensionImpl is at offset ${entries[0].offset}, expected 0`) + return `0x${BigInt(entries[0].slot).toString(16)}` +} + +const targets = JSON.parse(fs.readFileSync(path.join(root, 'targets.json'), 'utf8')) +const bundles = Object.fromEntries( + Object.keys(targets).map((name) => [name, compileBundle(path.join(root, 'verified', name))]), +) + +const { number } = await rpc('eth_getBlockByNumber', ['finalized', false]) +const block = Number(number) + +for (const [name, { implementation, proxy }] of Object.entries(targets)) { + if (!proxy) continue + const word = await rpc('eth_getStorageAt', [proxy, EIP1967_IMPLEMENTATION_SLOT, number]) + if (BigInt(word) !== BigInt(implementation)) fail(`${name}: proxy points at ${word}, not ${implementation}`) + console.log(`${name}: proxy implementation slot matches ${implementation}`) +} + +const slot = extensionImplSlot(bundles.L1Staking.storageLayout) +const word = await rpc('eth_getStorageAt', [targets.L1Staking.proxy, slot, number]) +const extensionImpl = `0x${word.slice(-40)}` +if (extensionImpl.toLowerCase() !== targets.StakingExtension.implementation.toLowerCase()) { + fail(`StakingExtension: L1Staking extensionImpl is ${extensionImpl}, not ${targets.StakingExtension.implementation}`) +} +console.log(`StakingExtension: L1Staking extensionImpl (slot ${BigInt(slot)}) matches ${extensionImpl}`) + +const contracts = {} +for (const [name, { implementation, proxy }] of Object.entries(targets)) { + const code = await rpc('eth_getCode', [implementation, number]) + if (code === '0x') fail(`${name}: no code at ${implementation}`) + contracts[name] = { implementation, proxy, runtimeHash: runtimeHash(normalizeRuntime(code, bundles[name].ranges)) } +} + +fs.writeFileSync(path.join(root, 'mainnet.json'), JSON.stringify({ block, contracts }, null, 2) + '\n') +console.log(`recorded ${Object.keys(contracts).length} contracts at block ${block}`) diff --git a/packages/legacy/scripts/verify.mjs b/packages/legacy/scripts/verify.mjs new file mode 100644 index 000000000..e0943a175 --- /dev/null +++ b/packages/legacy/scripts/verify.mjs @@ -0,0 +1,41 @@ +#!/usr/bin/env node +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +import { checkSources, compareAddresses, compileBundle, normalizeRuntime, runtimeHash } from './lib.mjs' + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') +const targets = JSON.parse(fs.readFileSync(path.join(root, 'targets.json'), 'utf8')) +const { contracts } = JSON.parse(fs.readFileSync(path.join(root, 'mainnet.json'), 'utf8')) +const folders = fs + .readdirSync(path.join(root, 'verified'), { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => entry.name) + +const mismatches = [] + +const listed = { 'targets.json': Object.keys(targets), 'verified/': folders, 'mainnet.json': Object.keys(contracts) } +const all = new Set(Object.values(listed).flat()) +for (const [where, names] of Object.entries(listed)) { + for (const name of all) { + if (!names.includes(name)) mismatches.push(`${name}: not in ${where}`) + } +} + +mismatches.push(...compareAddresses(targets, contracts)) + +for (const name of folders) { + const dir = path.join(root, 'verified', name) + for (const file of checkSources(dir)) mismatches.push(`${name}: source differs: ${file}`) + const { object, ranges } = compileBundle(dir) + if (runtimeHash(normalizeRuntime(object, ranges)) !== contracts[name]?.runtimeHash) { + mismatches.push(`${name}: runtime code differs from mainnet.json`) + } +} + +if (mismatches.length) { + for (const mismatch of mismatches) console.error(mismatch) + process.exit(1) +} +console.log(`OK: ${folders.length} mainnet implementations reproduce`) diff --git a/packages/legacy/targets.json b/packages/legacy/targets.json new file mode 100644 index 000000000..d0b38d5dc --- /dev/null +++ b/packages/legacy/targets.json @@ -0,0 +1,30 @@ +{ + "GraphToken": { + "implementation": "0xc944E90C64B2c07662A292be6244BDf05Cda44a7", + "proxy": null + }, + "L1GraphTokenGateway": { + "implementation": "0xD41ca6A1d034D178c196DFa916f22f7D1a1B8222", + "proxy": "0x01cDC91B0A9bA741903aA3699BF4CE31d6C5cC06" + }, + "BridgeEscrow": { + "implementation": "0xBcD54513aa593646d72aEA31406c633C235Ad6EA", + "proxy": "0x36aFF7001294daE4C2ED4fDEfC478a00De77F090" + }, + "L1GNS": { + "implementation": "0x83A5B6C542aB20436087Fc79724bdF06ea80f836", + "proxy": "0xaDcA0dd4729c8BA3aCf3E99F3A9f471EF37b6825" + }, + "Curation": { + "implementation": "0xDeb46851907fd85DD475780CcE2eE0D67c969825", + "proxy": "0x8FE00a685Bcb3B2cc296ff6FfEaB10acA4CE1538" + }, + "L1Staking": { + "implementation": "0x20a14D78848BC8B3F3d4B77239F2adC3C0259A10", + "proxy": "0xF55041E37E12cD407ad00CE2910B8269B01263b9" + }, + "StakingExtension": { + "implementation": "0xA479c00cDa8C07bce458D7a826C7b091672EB92C", + "proxy": null + } +} diff --git a/packages/legacy/verified/BridgeEscrow/compiler.json b/packages/legacy/verified/BridgeEscrow/compiler.json new file mode 100644 index 000000000..fa7cfd787 --- /dev/null +++ b/packages/legacy/verified/BridgeEscrow/compiler.json @@ -0,0 +1,41 @@ +{ + "version": "0.7.6+commit.7338295f", + "settings": { + "metadata": { + "bytecodeHash": "ipfs", + "useLiteralContent": true + }, + "libraries": {}, + "optimizer": { + "runs": 200, + "enabled": true + }, + "evmVersion": "istanbul", + "remappings": [] + }, + "target": { + "source": "contracts/gateway/BridgeEscrow.sol", + "name": "BridgeEscrow" + }, + "sourcifyMatch": "exact_match", + "sourceHashes": { + "contracts/staking/IStaking.sol": "17f3a421e91e745b50ca53227a4197b3992d1133946c91a307ed073993c267c9", + "contracts/token/IGraphToken.sol": "244ca084fd409c9ad3969e7ac5548346495466442ff7f802288b112d687705d2", + "contracts/curation/ICuration.sol": "5535bfac7790f46af6b64b4c0db2348d9158992a2fd7b9d7af056a6e63a8b4ba", + "contracts/governance/Managed.sol": "a0e4ec30705ab1d9f7fd896a957fb096168885c92e62c44069932176fd3cb8f3", + "contracts/governance/IManaged.sol": "bd1f47d7a16eb1a2ddbf547511166fee8e99c73d7cb871908837d5db65037bb8", + "contracts/epochs/IEpochManager.sol": "cb45047682865d6ed215af7d05d505b8fa7a1e66b1e54b45c1a47798281f47ab", + "contracts/gateway/BridgeEscrow.sol": "041373fcc69168cb816a6657c56e30781bcd8e607a56b5aac6c310d508d736ef", + "contracts/staking/IStakingData.sol": "b44cad426f81eb34c59b2c276080ba8d674f98ebe685529fb24acc61c217b1bc", + "contracts/upgrades/IGraphProxy.sol": "d7b89c0be1a05c93facc640a08d0b4f1e0b4557cb268d9143e4c42ce24bbd1a9", + "contracts/arbitrum/ITokenGateway.sol": "55bd317bf1ca12a2308cc51ad4ec6c370dcb6d1cdd1631ec2308e57528ad7600", + "contracts/governance/IController.sol": "beb591bb70a3b94f025a5f64bbb3fc6788043fb8d5f1cdffb21acb8cbbce8f8d", + "contracts/rewards/IRewardsManager.sol": "30dac160a644d4cd2750efabccbe76bad626a112a9f67ac90e91f5a7df8513ad", + "contracts/upgrades/GraphUpgradeable.sol": "fdb340dbeb5de52ea8243fbfab9d2ef8fdfb916e909dc4e1ae6b314cde0ea083", + "contracts/curation/IGraphCurationToken.sol": "4f905a43b8e7146f0cfa000657de76aa27a84a042522b2a98af7552d2eecb767", + "@openzeppelin/contracts/token/ERC20/IERC20.sol": "0573c2961569aa4906845d0cd428b5b7394956170054ceeaa8f8af96cd44875c", + "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol": "2c3a3edc2b1a4ac2c4a8645475b51f2668b1ad5ea22df074d0c0ebd3122ce2e7", + "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol": "877bc9cb396d0f50330bb9c0057c029407e159739b6fab0b110f19451c8681e4", + "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol": "57cf669fc8977ccd1738280e0a39c0156ecbde0af6142d6dd845ab1c055ca61d" + } +} diff --git a/packages/legacy/verified/BridgeEscrow/sources/@openzeppelin/contracts-upgradeable/proxy/Initializable.sol b/packages/legacy/verified/BridgeEscrow/sources/@openzeppelin/contracts-upgradeable/proxy/Initializable.sol new file mode 100644 index 000000000..9fd6dc9fc --- /dev/null +++ b/packages/legacy/verified/BridgeEscrow/sources/@openzeppelin/contracts-upgradeable/proxy/Initializable.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT + +// solhint-disable-next-line compiler-version +pragma solidity >=0.4.24 <0.8.0; + +import "../utils/AddressUpgradeable.sol"; + +/** + * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed + * behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an + * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer + * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. + * + * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as + * possible by providing the encoded function call as the `_data` argument to {UpgradeableProxy-constructor}. + * + * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure + * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. + */ +abstract contract Initializable { + + /** + * @dev Indicates that the contract has been initialized. + */ + bool private _initialized; + + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool private _initializing; + + /** + * @dev Modifier to protect an initializer function from being invoked twice. + */ + modifier initializer() { + require(_initializing || _isConstructor() || !_initialized, "Initializable: contract is already initialized"); + + bool isTopLevelCall = !_initializing; + if (isTopLevelCall) { + _initializing = true; + _initialized = true; + } + + _; + + if (isTopLevelCall) { + _initializing = false; + } + } + + /// @dev Returns true if and only if the function is running in the constructor + function _isConstructor() private view returns (bool) { + return !AddressUpgradeable.isContract(address(this)); + } +} diff --git a/packages/legacy/verified/BridgeEscrow/sources/@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol b/packages/legacy/verified/BridgeEscrow/sources/@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol new file mode 100644 index 000000000..e4c8c06ec --- /dev/null +++ b/packages/legacy/verified/BridgeEscrow/sources/@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20Upgradeable { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} diff --git a/packages/legacy/verified/BridgeEscrow/sources/@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol b/packages/legacy/verified/BridgeEscrow/sources/@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol new file mode 100644 index 000000000..08a59041b --- /dev/null +++ b/packages/legacy/verified/BridgeEscrow/sources/@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.2 <0.8.0; + +/** + * @dev Collection of functions related to the address type + */ +library AddressUpgradeable { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + uint256 size; + // solhint-disable-next-line no-inline-assembly + assembly { size := extcodesize(account) } + return size > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + // solhint-disable-next-line avoid-low-level-calls, avoid-call-value + (bool success, ) = recipient.call{ value: amount }(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain`call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.call{ value: value }(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.staticcall(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) { + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + + // solhint-disable-next-line no-inline-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} diff --git a/packages/legacy/verified/BridgeEscrow/sources/@openzeppelin/contracts/token/ERC20/IERC20.sol b/packages/legacy/verified/BridgeEscrow/sources/@openzeppelin/contracts/token/ERC20/IERC20.sol new file mode 100644 index 000000000..9b877d119 --- /dev/null +++ b/packages/legacy/verified/BridgeEscrow/sources/@openzeppelin/contracts/token/ERC20/IERC20.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} diff --git a/packages/legacy/verified/BridgeEscrow/sources/contracts/arbitrum/ITokenGateway.sol b/packages/legacy/verified/BridgeEscrow/sources/contracts/arbitrum/ITokenGateway.sol new file mode 100644 index 000000000..977fe07f2 --- /dev/null +++ b/packages/legacy/verified/BridgeEscrow/sources/contracts/arbitrum/ITokenGateway.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2020, Offchain Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Originally copied from: + * https://github.com/OffchainLabs/arbitrum/tree/e3a6307ad8a2dc2cad35728a2a9908cfd8dd8ef9/packages/arb-bridge-peripherals + * + * MODIFIED from Offchain Labs' implementation: + * - Changed solidity version to 0.7.6 (pablo@edgeandnode.com) + * + */ + +pragma solidity ^0.7.6; + +interface ITokenGateway { + /// @notice event deprecated in favor of DepositInitiated and WithdrawalInitiated + // event OutboundTransferInitiated( + // address token, + // address indexed _from, + // address indexed _to, + // uint256 indexed _transferId, + // uint256 _amount, + // bytes _data + // ); + + /// @notice event deprecated in favor of DepositFinalized and WithdrawalFinalized + // event InboundTransferFinalized( + // address token, + // address indexed _from, + // address indexed _to, + // uint256 indexed _transferId, + // uint256 _amount, + // bytes _data + // ); + + function outboundTransfer( + address _token, + address _to, + uint256 _amount, + uint256 _maxGas, + uint256 _gasPriceBid, + bytes calldata _data + ) external payable returns (bytes memory); + + function finalizeInboundTransfer( + address _token, + address _from, + address _to, + uint256 _amount, + bytes calldata _data + ) external payable; + + /** + * @notice Calculate the address used when bridging an ERC20 token + * @dev the L1 and L2 address oracles may not always be in sync. + * For example, a custom token may have been registered but not deployed or the contract self destructed. + * @param l1ERC20 address of L1 token + * @return L2 address of a bridged ERC20 token + */ + function calculateL2TokenAddress(address l1ERC20) external view returns (address); +} diff --git a/packages/legacy/verified/BridgeEscrow/sources/contracts/curation/ICuration.sol b/packages/legacy/verified/BridgeEscrow/sources/contracts/curation/ICuration.sol new file mode 100644 index 000000000..9e1701aaf --- /dev/null +++ b/packages/legacy/verified/BridgeEscrow/sources/contracts/curation/ICuration.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import "./IGraphCurationToken.sol"; + +interface ICuration { + // -- Configuration -- + + function setDefaultReserveRatio(uint32 _defaultReserveRatio) external; + + function setMinimumCurationDeposit(uint256 _minimumCurationDeposit) external; + + function setCurationTaxPercentage(uint32 _percentage) external; + + function setCurationTokenMaster(address _curationTokenMaster) external; + + // -- Curation -- + + function mint( + bytes32 _subgraphDeploymentID, + uint256 _tokensIn, + uint256 _signalOutMin + ) external returns (uint256, uint256); + + function burn( + bytes32 _subgraphDeploymentID, + uint256 _signalIn, + uint256 _tokensOutMin + ) external returns (uint256); + + function collect(bytes32 _subgraphDeploymentID, uint256 _tokens) external; + + // -- Getters -- + + function isCurated(bytes32 _subgraphDeploymentID) external view returns (bool); + + function getCuratorSignal(address _curator, bytes32 _subgraphDeploymentID) + external + view + returns (uint256); + + function getCurationPoolSignal(bytes32 _subgraphDeploymentID) external view returns (uint256); + + function getCurationPoolTokens(bytes32 _subgraphDeploymentID) external view returns (uint256); + + function tokensToSignal(bytes32 _subgraphDeploymentID, uint256 _tokensIn) + external + view + returns (uint256, uint256); + + function signalToTokens(bytes32 _subgraphDeploymentID, uint256 _signalIn) + external + view + returns (uint256); + + function curationTaxPercentage() external view returns (uint32); +} diff --git a/packages/legacy/verified/BridgeEscrow/sources/contracts/curation/IGraphCurationToken.sol b/packages/legacy/verified/BridgeEscrow/sources/contracts/curation/IGraphCurationToken.sol new file mode 100644 index 000000000..43679aba6 --- /dev/null +++ b/packages/legacy/verified/BridgeEscrow/sources/contracts/curation/IGraphCurationToken.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; + +interface IGraphCurationToken is IERC20Upgradeable { + function initialize(address _owner) external; + + function burnFrom(address _account, uint256 _amount) external; + + function mint(address _to, uint256 _amount) external; +} diff --git a/packages/legacy/verified/BridgeEscrow/sources/contracts/epochs/IEpochManager.sol b/packages/legacy/verified/BridgeEscrow/sources/contracts/epochs/IEpochManager.sol new file mode 100644 index 000000000..36b1f47a3 --- /dev/null +++ b/packages/legacy/verified/BridgeEscrow/sources/contracts/epochs/IEpochManager.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +interface IEpochManager { + // -- Configuration -- + + function setEpochLength(uint256 _epochLength) external; + + // -- Epochs + + function runEpoch() external; + + // -- Getters -- + + function isCurrentEpochRun() external view returns (bool); + + function blockNum() external view returns (uint256); + + function blockHash(uint256 _block) external view returns (bytes32); + + function currentEpoch() external view returns (uint256); + + function currentEpochBlock() external view returns (uint256); + + function currentEpochBlockSinceStart() external view returns (uint256); + + function epochsSince(uint256 _epoch) external view returns (uint256); + + function epochsSinceUpdate() external view returns (uint256); +} diff --git a/packages/legacy/verified/BridgeEscrow/sources/contracts/gateway/BridgeEscrow.sol b/packages/legacy/verified/BridgeEscrow/sources/contracts/gateway/BridgeEscrow.sol new file mode 100644 index 000000000..3c0fa5c1a --- /dev/null +++ b/packages/legacy/verified/BridgeEscrow/sources/contracts/gateway/BridgeEscrow.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol"; + +import { GraphUpgradeable } from "../upgrades/GraphUpgradeable.sol"; +import { Managed } from "../governance/Managed.sol"; +import { IGraphToken } from "../token/IGraphToken.sol"; + +/** + * @title Bridge Escrow + * @dev This contracts acts as a gateway for an L2 bridge (or several). It simply holds GRT and has + * a set of spenders that can transfer the tokens; the L1 side of each L2 bridge has to be + * approved as a spender. + */ +contract BridgeEscrow is Initializable, GraphUpgradeable, Managed { + /** + * @notice Initialize the BridgeEscrow contract. + * @param _controller Address of the Controller that manages this contract + */ + function initialize(address _controller) external onlyImpl initializer { + Managed._initialize(_controller); + } + + /** + * @notice Approve a spender (i.e. a bridge that manages the GRT funds held by the escrow) + * @param _spender Address of the spender that will be approved + */ + function approveAll(address _spender) external onlyGovernor { + graphToken().approve(_spender, type(uint256).max); + } + + /** + * @notice Revoke a spender (i.e. a bridge that will no longer manage the GRT funds held by the escrow) + * @param _spender Address of the spender that will be revoked + */ + function revokeAll(address _spender) external onlyGovernor { + graphToken().approve(_spender, 0); + } +} diff --git a/packages/legacy/verified/BridgeEscrow/sources/contracts/governance/IController.sol b/packages/legacy/verified/BridgeEscrow/sources/contracts/governance/IController.sol new file mode 100644 index 000000000..7df3d94ee --- /dev/null +++ b/packages/legacy/verified/BridgeEscrow/sources/contracts/governance/IController.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; + +interface IController { + function getGovernor() external view returns (address); + + // -- Registry -- + + function setContractProxy(bytes32 _id, address _contractAddress) external; + + function unsetContractProxy(bytes32 _id) external; + + function updateController(bytes32 _id, address _controller) external; + + function getContractProxy(bytes32 _id) external view returns (address); + + // -- Pausing -- + + function setPartialPaused(bool _partialPaused) external; + + function setPaused(bool _paused) external; + + function setPauseGuardian(address _newPauseGuardian) external; + + function paused() external view returns (bool); + + function partialPaused() external view returns (bool); +} diff --git a/packages/legacy/verified/BridgeEscrow/sources/contracts/governance/IManaged.sol b/packages/legacy/verified/BridgeEscrow/sources/contracts/governance/IManaged.sol new file mode 100644 index 000000000..1a458a460 --- /dev/null +++ b/packages/legacy/verified/BridgeEscrow/sources/contracts/governance/IManaged.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +interface IManaged { + function setController(address _controller) external; +} diff --git a/packages/legacy/verified/BridgeEscrow/sources/contracts/governance/Managed.sol b/packages/legacy/verified/BridgeEscrow/sources/contracts/governance/Managed.sol new file mode 100644 index 000000000..6b8fe624e --- /dev/null +++ b/packages/legacy/verified/BridgeEscrow/sources/contracts/governance/Managed.sol @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import { IController } from "./IController.sol"; + +import { ICuration } from "../curation/ICuration.sol"; +import { IEpochManager } from "../epochs/IEpochManager.sol"; +import { IRewardsManager } from "../rewards/IRewardsManager.sol"; +import { IStaking } from "../staking/IStaking.sol"; +import { IGraphToken } from "../token/IGraphToken.sol"; +import { ITokenGateway } from "../arbitrum/ITokenGateway.sol"; + +import { IManaged } from "./IManaged.sol"; + +/** + * @title Graph Managed contract + * @dev The Managed contract provides an interface to interact with the Controller. + * It also provides local caching for contract addresses. This mechanism relies on calling the + * public `syncAllContracts()` function whenever a contract changes in the controller. + * + * Inspired by Livepeer: + * https://github.com/livepeer/protocol/blob/streamflow/contracts/Controller.sol + */ +abstract contract Managed is IManaged { + // -- State -- + + /// Controller that contract is registered with + IController public controller; + /// @dev Cache for the addresses of the contracts retrieved from the controller + mapping(bytes32 => address) private _addressCache; + /// @dev Gap for future storage variables + uint256[10] private __gap; + + // Immutables + bytes32 private immutable CURATION = keccak256("Curation"); + bytes32 private immutable EPOCH_MANAGER = keccak256("EpochManager"); + bytes32 private immutable REWARDS_MANAGER = keccak256("RewardsManager"); + bytes32 private immutable STAKING = keccak256("Staking"); + bytes32 private immutable GRAPH_TOKEN = keccak256("GraphToken"); + bytes32 private immutable GRAPH_TOKEN_GATEWAY = keccak256("GraphTokenGateway"); + + // -- Events -- + + /// Emitted when a contract parameter has been updated + event ParameterUpdated(string param); + /// Emitted when the controller address has been set + event SetController(address controller); + + /// Emitted when contract with `nameHash` is synced to `contractAddress`. + event ContractSynced(bytes32 indexed nameHash, address contractAddress); + + // -- Modifiers -- + + /** + * @dev Revert if the controller is paused or partially paused + */ + function _notPartialPaused() internal view { + require(!controller.paused(), "Paused"); + require(!controller.partialPaused(), "Partial-paused"); + } + + /** + * @dev Revert if the controller is paused + */ + function _notPaused() internal view virtual { + require(!controller.paused(), "Paused"); + } + + /** + * @dev Revert if the caller is not the governor + */ + function _onlyGovernor() internal view { + require(msg.sender == controller.getGovernor(), "Only Controller governor"); + } + + /** + * @dev Revert if the caller is not the Controller + */ + function _onlyController() internal view { + require(msg.sender == address(controller), "Caller must be Controller"); + } + + /** + * @dev Revert if the controller is paused or partially paused + */ + modifier notPartialPaused() { + _notPartialPaused(); + _; + } + + /** + * @dev Revert if the controller is paused + */ + modifier notPaused() { + _notPaused(); + _; + } + + /** + * @dev Revert if the caller is not the Controller + */ + modifier onlyController() { + _onlyController(); + _; + } + + /** + * @dev Revert if the caller is not the governor + */ + modifier onlyGovernor() { + _onlyGovernor(); + _; + } + + // -- Functions -- + + /** + * @dev Initialize a Managed contract + * @param _controller Address for the Controller that manages this contract + */ + function _initialize(address _controller) internal { + _setController(_controller); + } + + /** + * @notice Set Controller. Only callable by current controller. + * @param _controller Controller contract address + */ + function setController(address _controller) external override onlyController { + _setController(_controller); + } + + /** + * @dev Set controller. + * @param _controller Controller contract address + */ + function _setController(address _controller) internal { + require(_controller != address(0), "Controller must be set"); + controller = IController(_controller); + emit SetController(_controller); + } + + /** + * @dev Return Curation interface + * @return Curation contract registered with Controller + */ + function curation() internal view returns (ICuration) { + return ICuration(_resolveContract(CURATION)); + } + + /** + * @dev Return EpochManager interface + * @return Epoch manager contract registered with Controller + */ + function epochManager() internal view returns (IEpochManager) { + return IEpochManager(_resolveContract(EPOCH_MANAGER)); + } + + /** + * @dev Return RewardsManager interface + * @return Rewards manager contract registered with Controller + */ + function rewardsManager() internal view returns (IRewardsManager) { + return IRewardsManager(_resolveContract(REWARDS_MANAGER)); + } + + /** + * @dev Return Staking interface + * @return Staking contract registered with Controller + */ + function staking() internal view returns (IStaking) { + return IStaking(_resolveContract(STAKING)); + } + + /** + * @dev Return GraphToken interface + * @return Graph token contract registered with Controller + */ + function graphToken() internal view returns (IGraphToken) { + return IGraphToken(_resolveContract(GRAPH_TOKEN)); + } + + /** + * @dev Return GraphTokenGateway (L1 or L2) interface + * @return Graph token gateway contract registered with Controller + */ + function graphTokenGateway() internal view returns (ITokenGateway) { + return ITokenGateway(_resolveContract(GRAPH_TOKEN_GATEWAY)); + } + + /** + * @dev Resolve a contract address from the cache or the Controller if not found + * @param _nameHash keccak256 hash of the contract name + * @return Address of the contract + */ + function _resolveContract(bytes32 _nameHash) internal view returns (address) { + address contractAddress = _addressCache[_nameHash]; + if (contractAddress == address(0)) { + contractAddress = controller.getContractProxy(_nameHash); + } + return contractAddress; + } + + /** + * @dev Cache a contract address from the Controller registry. + * @param _name Name of the contract to sync into the cache + */ + function _syncContract(string memory _name) internal { + bytes32 nameHash = keccak256(abi.encodePacked(_name)); + address contractAddress = controller.getContractProxy(nameHash); + if (_addressCache[nameHash] != contractAddress) { + _addressCache[nameHash] = contractAddress; + emit ContractSynced(nameHash, contractAddress); + } + } + + /** + * @notice Sync protocol contract addresses from the Controller registry + * @dev This function will cache all the contracts using the latest addresses + * Anyone can call the function whenever a Proxy contract change in the + * controller to ensure the protocol is using the latest version + */ + function syncAllContracts() external { + _syncContract("Curation"); + _syncContract("EpochManager"); + _syncContract("RewardsManager"); + _syncContract("Staking"); + _syncContract("GraphToken"); + _syncContract("GraphTokenGateway"); + } +} diff --git a/packages/legacy/verified/BridgeEscrow/sources/contracts/rewards/IRewardsManager.sol b/packages/legacy/verified/BridgeEscrow/sources/contracts/rewards/IRewardsManager.sol new file mode 100644 index 000000000..dc17c8ba8 --- /dev/null +++ b/packages/legacy/verified/BridgeEscrow/sources/contracts/rewards/IRewardsManager.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +interface IRewardsManager { + /** + * @dev Stores accumulated rewards and snapshots related to a particular SubgraphDeployment. + */ + struct Subgraph { + uint256 accRewardsForSubgraph; + uint256 accRewardsForSubgraphSnapshot; + uint256 accRewardsPerSignalSnapshot; + uint256 accRewardsPerAllocatedToken; + } + + // -- Config -- + + function setIssuanceRate(uint256 _issuanceRate) external; + + function setMinimumSubgraphSignal(uint256 _minimumSubgraphSignal) external; + + // -- Denylist -- + + function setSubgraphAvailabilityOracle(address _subgraphAvailabilityOracle) external; + + function setDenied(bytes32 _subgraphDeploymentID, bool _deny) external; + + function setDeniedMany(bytes32[] calldata _subgraphDeploymentID, bool[] calldata _deny) + external; + + function isDenied(bytes32 _subgraphDeploymentID) external view returns (bool); + + // -- Getters -- + + function getNewRewardsPerSignal() external view returns (uint256); + + function getAccRewardsPerSignal() external view returns (uint256); + + function getAccRewardsForSubgraph(bytes32 _subgraphDeploymentID) + external + view + returns (uint256); + + function getAccRewardsPerAllocatedToken(bytes32 _subgraphDeploymentID) + external + view + returns (uint256, uint256); + + function getRewards(address _allocationID) external view returns (uint256); + + // -- Updates -- + + function updateAccRewardsPerSignal() external returns (uint256); + + function takeRewards(address _allocationID) external returns (uint256); + + // -- Hooks -- + + function onSubgraphSignalUpdate(bytes32 _subgraphDeploymentID) external returns (uint256); + + function onSubgraphAllocationUpdate(bytes32 _subgraphDeploymentID) external returns (uint256); +} diff --git a/packages/legacy/verified/BridgeEscrow/sources/contracts/staking/IStaking.sol b/packages/legacy/verified/BridgeEscrow/sources/contracts/staking/IStaking.sol new file mode 100644 index 000000000..86942e4d1 --- /dev/null +++ b/packages/legacy/verified/BridgeEscrow/sources/contracts/staking/IStaking.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; +pragma abicoder v2; + +import "./IStakingData.sol"; + +interface IStaking is IStakingData { + // -- Allocation Data -- + + /** + * @dev Possible states an allocation can be + * States: + * - Null = indexer == address(0) + * - Active = not Null && tokens > 0 + * - Closed = Active && closedAtEpoch != 0 + * - Finalized = Closed && closedAtEpoch + channelDisputeEpochs > now() + * - Claimed = not Null && tokens == 0 + */ + enum AllocationState { + Null, + Active, + Closed, + Finalized, + Claimed + } + + // -- Configuration -- + + function setMinimumIndexerStake(uint256 _minimumIndexerStake) external; + + function setThawingPeriod(uint32 _thawingPeriod) external; + + function setCurationPercentage(uint32 _percentage) external; + + function setProtocolPercentage(uint32 _percentage) external; + + function setChannelDisputeEpochs(uint32 _channelDisputeEpochs) external; + + function setMaxAllocationEpochs(uint32 _maxAllocationEpochs) external; + + function setRebateRatio(uint32 _alphaNumerator, uint32 _alphaDenominator) external; + + function setDelegationRatio(uint32 _delegationRatio) external; + + function setDelegationParameters( + uint32 _indexingRewardCut, + uint32 _queryFeeCut, + uint32 _cooldownBlocks + ) external; + + function setDelegationParametersCooldown(uint32 _blocks) external; + + function setDelegationUnbondingPeriod(uint32 _delegationUnbondingPeriod) external; + + function setDelegationTaxPercentage(uint32 _percentage) external; + + function setSlasher(address _slasher, bool _allowed) external; + + function setAssetHolder(address _assetHolder, bool _allowed) external; + + // -- Operation -- + + function setOperator(address _operator, bool _allowed) external; + + function isOperator(address _operator, address _indexer) external view returns (bool); + + // -- Staking -- + + function stake(uint256 _tokens) external; + + function stakeTo(address _indexer, uint256 _tokens) external; + + function unstake(uint256 _tokens) external; + + function slash( + address _indexer, + uint256 _tokens, + uint256 _reward, + address _beneficiary + ) external; + + function withdraw() external; + + function setRewardsDestination(address _destination) external; + + // -- Delegation -- + + function delegate(address _indexer, uint256 _tokens) external returns (uint256); + + function undelegate(address _indexer, uint256 _shares) external returns (uint256); + + function withdrawDelegated(address _indexer, address _newIndexer) external returns (uint256); + + // -- Channel management and allocations -- + + function allocate( + bytes32 _subgraphDeploymentID, + uint256 _tokens, + address _allocationID, + bytes32 _metadata, + bytes calldata _proof + ) external; + + function allocateFrom( + address _indexer, + bytes32 _subgraphDeploymentID, + uint256 _tokens, + address _allocationID, + bytes32 _metadata, + bytes calldata _proof + ) external; + + function closeAllocation(address _allocationID, bytes32 _poi) external; + + function closeAllocationMany(CloseAllocationRequest[] calldata _requests) external; + + function closeAndAllocate( + address _oldAllocationID, + bytes32 _poi, + address _indexer, + bytes32 _subgraphDeploymentID, + uint256 _tokens, + address _allocationID, + bytes32 _metadata, + bytes calldata _proof + ) external; + + function collect(uint256 _tokens, address _allocationID) external; + + function claim(address _allocationID, bool _restake) external; + + function claimMany(address[] calldata _allocationID, bool _restake) external; + + // -- Getters and calculations -- + + function hasStake(address _indexer) external view returns (bool); + + function getIndexerStakedTokens(address _indexer) external view returns (uint256); + + function getIndexerCapacity(address _indexer) external view returns (uint256); + + function getAllocation(address _allocationID) external view returns (Allocation memory); + + function getAllocationState(address _allocationID) external view returns (AllocationState); + + function isAllocation(address _allocationID) external view returns (bool); + + function getSubgraphAllocatedTokens(bytes32 _subgraphDeploymentID) + external + view + returns (uint256); + + function getDelegation(address _indexer, address _delegator) + external + view + returns (Delegation memory); + + function isDelegator(address _indexer, address _delegator) external view returns (bool); +} diff --git a/packages/legacy/verified/BridgeEscrow/sources/contracts/staking/IStakingData.sol b/packages/legacy/verified/BridgeEscrow/sources/contracts/staking/IStakingData.sol new file mode 100644 index 000000000..348a5a7f9 --- /dev/null +++ b/packages/legacy/verified/BridgeEscrow/sources/contracts/staking/IStakingData.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; + +interface IStakingData { + /** + * @dev Allocate GRT tokens for the purpose of serving queries of a subgraph deployment + * An allocation is created in the allocate() function and consumed in claim() + */ + struct Allocation { + address indexer; + bytes32 subgraphDeploymentID; + uint256 tokens; // Tokens allocated to a SubgraphDeployment + uint256 createdAtEpoch; // Epoch when it was created + uint256 closedAtEpoch; // Epoch when it was closed + uint256 collectedFees; // Collected fees for the allocation + uint256 effectiveAllocation; // Effective allocation when closed + uint256 accRewardsPerAllocatedToken; // Snapshot used for reward calc + } + + /** + * @dev Represents a request to close an allocation with a specific proof of indexing. + * This is passed when calling closeAllocationMany to define the closing parameters for + * each allocation. + */ + struct CloseAllocationRequest { + address allocationID; + bytes32 poi; + } + + // -- Delegation Data -- + + /** + * @dev Delegation pool information. One per indexer. + */ + struct DelegationPool { + uint32 cooldownBlocks; // Blocks to wait before updating parameters + uint32 indexingRewardCut; // in PPM + uint32 queryFeeCut; // in PPM + uint256 updatedAtBlock; // Block when the pool was last updated + uint256 tokens; // Total tokens as pool reserves + uint256 shares; // Total shares minted in the pool + mapping(address => Delegation) delegators; // Mapping of delegator => Delegation + } + + /** + * @dev Individual delegation data of a delegator in a pool. + */ + struct Delegation { + uint256 shares; // Shares owned by a delegator in the pool + uint256 tokensLocked; // Tokens locked for undelegation + uint256 tokensLockedUntil; // Block when locked tokens can be withdrawn + } +} diff --git a/packages/legacy/verified/BridgeEscrow/sources/contracts/token/IGraphToken.sol b/packages/legacy/verified/BridgeEscrow/sources/contracts/token/IGraphToken.sol new file mode 100644 index 000000000..8255e18d5 --- /dev/null +++ b/packages/legacy/verified/BridgeEscrow/sources/contracts/token/IGraphToken.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface IGraphToken is IERC20 { + // -- Mint and Burn -- + + function burn(uint256 amount) external; + + function burnFrom(address _from, uint256 amount) external; + + function mint(address _to, uint256 _amount) external; + + // -- Mint Admin -- + + function addMinter(address _account) external; + + function removeMinter(address _account) external; + + function renounceMinter() external; + + function isMinter(address _account) external view returns (bool); + + // -- Permit -- + + function permit( + address _owner, + address _spender, + uint256 _value, + uint256 _deadline, + uint8 _v, + bytes32 _r, + bytes32 _s + ) external; + + // -- Allowance -- + + function increaseAllowance(address spender, uint256 addedValue) external returns (bool); + + function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool); +} diff --git a/packages/legacy/verified/BridgeEscrow/sources/contracts/upgrades/GraphUpgradeable.sol b/packages/legacy/verified/BridgeEscrow/sources/contracts/upgrades/GraphUpgradeable.sol new file mode 100644 index 000000000..92ce80ad7 --- /dev/null +++ b/packages/legacy/verified/BridgeEscrow/sources/contracts/upgrades/GraphUpgradeable.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import { IGraphProxy } from "./IGraphProxy.sol"; + +/** + * @title Graph Upgradeable + * @dev This contract is intended to be inherited from upgradeable contracts. + */ +abstract contract GraphUpgradeable { + /** + * @dev Storage slot with the address of the current implementation. + * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is + * validated in the constructor. + */ + bytes32 internal constant IMPLEMENTATION_SLOT = + 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + /** + * @dev Check if the caller is the proxy admin. + */ + modifier onlyProxyAdmin(IGraphProxy _proxy) { + require(msg.sender == _proxy.admin(), "Caller must be the proxy admin"); + _; + } + + /** + * @dev Check if the caller is the implementation. + */ + modifier onlyImpl() { + require(msg.sender == _implementation(), "Only implementation"); + _; + } + + /** + * @dev Returns the current implementation. + * @return impl Address of the current implementation + */ + function _implementation() internal view returns (address impl) { + bytes32 slot = IMPLEMENTATION_SLOT; + // solhint-disable-next-line no-inline-assembly + assembly { + impl := sload(slot) + } + } + + /** + * @notice Accept to be an implementation of proxy. + * @param _proxy Proxy to accept + */ + function acceptProxy(IGraphProxy _proxy) external onlyProxyAdmin(_proxy) { + _proxy.acceptUpgrade(); + } + + /** + * @notice Accept to be an implementation of proxy and then call a function from the new + * implementation as specified by `_data`, which should be an encoded function call. This is + * useful to initialize new storage variables in the proxied contract. + * @param _proxy Proxy to accept + * @param _data Calldata for the initialization function call (including selector) + */ + function acceptProxyAndCall(IGraphProxy _proxy, bytes calldata _data) + external + onlyProxyAdmin(_proxy) + { + _proxy.acceptUpgradeAndCall(_data); + } +} diff --git a/packages/legacy/verified/BridgeEscrow/sources/contracts/upgrades/IGraphProxy.sol b/packages/legacy/verified/BridgeEscrow/sources/contracts/upgrades/IGraphProxy.sol new file mode 100644 index 000000000..61946e948 --- /dev/null +++ b/packages/legacy/verified/BridgeEscrow/sources/contracts/upgrades/IGraphProxy.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +interface IGraphProxy { + function admin() external returns (address); + + function setAdmin(address _newAdmin) external; + + function implementation() external returns (address); + + function pendingImplementation() external returns (address); + + function upgradeTo(address _newImplementation) external; + + function acceptUpgrade() external; + + function acceptUpgradeAndCall(bytes calldata data) external; +} diff --git a/packages/legacy/verified/Curation/compiler.json b/packages/legacy/verified/Curation/compiler.json new file mode 100644 index 000000000..56cc658bf --- /dev/null +++ b/packages/legacy/verified/Curation/compiler.json @@ -0,0 +1,56 @@ +{ + "version": "0.7.6+commit.7338295f", + "settings": { + "metadata": { + "bytecodeHash": "ipfs", + "useLiteralContent": true + }, + "libraries": {}, + "optimizer": { + "runs": 200, + "enabled": true + }, + "evmVersion": "istanbul", + "remappings": [] + }, + "target": { + "source": "contracts/curation/Curation.sol", + "name": "Curation" + }, + "sourcifyMatch": "exact_match", + "sourceHashes": { + "contracts/discovery/IGNS.sol": "0a4c68898721a068313a7fa32b99ebe9da1a432067dcb968a4d7a11e5d72e619", + "contracts/base/IMulticall.sol": "bbc79b9531ebaf4245bab4b7fed363d2e479cc9ce24d579e56a4d8ab44094dea", + "contracts/staking/IStaking.sol": "e2839479d19e1da6160b5bbad8c39976811c99f33d5bea1f11f8ea20c5a3ce3b", + "contracts/utils/TokenUtils.sol": "f1069ba54bdf449002414f051d99538ca02d8253a1670ec1398727002ffff45b", + "contracts/curation/Curation.sol": "f673242dd9d7faceb676aafc58baa037b92be8d442baba71615d1da770deaf67", + "contracts/token/IGraphToken.sol": "244ca084fd409c9ad3969e7ac5548346495466442ff7f802288b112d687705d2", + "contracts/curation/ICuration.sol": "34624b8df89549050f23bb3c606c9c60440330ab23551f4d4f1f0b94aef6afc9", + "contracts/governance/Managed.sol": "ea920fd87b021bee5df867d98e770db5abda51823b31f019ced405a4e09fdf72", + "contracts/staking/libs/Cobbs.sol": "c03a095787e00a624074e850ed76e384ad9e915bebb04092436a2173c05e451d", + "contracts/governance/IManaged.sol": "2f4a25095e90d6b08f1789dde49cb87d46888090ccc4e6ab1459e316c746cd29", + "contracts/staking/libs/Stakes.sol": "d2954b67e9bf2681a3d958ab2d3eda8d8c7942e4fade92cd4670e33458f6d676", + "contracts/bancor/BancorFormula.sol": "7ee49a811307a446981ef9aae22c8985aa1a3df7c741fb9706d79156206b1b9b", + "contracts/epochs/IEpochManager.sol": "cb45047682865d6ed215af7d05d505b8fa7a1e66b1e54b45c1a47798281f47ab", + "contracts/staking/IStakingBase.sol": "e6e3c27f691c5517ba30f179f5bffc3e94428da1b7756352f4a4d1885d63da3e", + "contracts/staking/IStakingData.sol": "76dc93c787a58f0bf494194a880a219d8c788e525cb1288cd7063fe78ec59dc2", + "contracts/staking/libs/Rebates.sol": "db2181deffbf15c6a42edbf891b5b24ece3e5f366ced2989efe18f829614b161", + "contracts/upgrades/IGraphProxy.sol": "d7b89c0be1a05c93facc640a08d0b4f1e0b4557cb268d9143e4c42ce24bbd1a9", + "contracts/arbitrum/ITokenGateway.sol": "55bd317bf1ca12a2308cc51ad4ec6c370dcb6d1cdd1631ec2308e57528ad7600", + "contracts/governance/IController.sol": "beb591bb70a3b94f025a5f64bbb3fc6788043fb8d5f1cdffb21acb8cbbce8f8d", + "contracts/staking/libs/MathUtils.sol": "721f4b3dbde3170e895a45c09905972db388321a2d7f61408ad4e2fd8d5a34a8", + "contracts/rewards/IRewardsManager.sol": "5cf8c3593f475719620e6a01b478fa3b5644d3848f82e7768bb36d43069478dd", + "contracts/curation/CurationStorage.sol": "0c96b08c3f1fc4d18f76d5a8fee51d41d7945407ea1de34004aadac0afc5d322", + "contracts/staking/IStakingExtension.sol": "1f39a3d4a7f482889a345fa4b31c1353f899f7f078b42f558f13960a7665f2ca", + "contracts/staking/libs/LibFixedMath.sol": "8a2760584a3bacd920e16cad7f90ff37e73e1795558574c8c6153b747e2ba498", + "contracts/upgrades/GraphUpgradeable.sol": "fdb340dbeb5de52ea8243fbfab9d2ef8fdfb916e909dc4e1ae6b314cde0ea083", + "@openzeppelin/contracts/math/SafeMath.sol": "4a04d0a20a19e3ef1dcabae9cad9ba006430a4e7eec4d9b519db87999722c98a", + "contracts/curation/IGraphCurationToken.sol": "4f905a43b8e7146f0cfa000657de76aa27a84a042522b2a98af7552d2eecb767", + "@openzeppelin/contracts/token/ERC20/IERC20.sol": "0573c2961569aa4906845d0cd428b5b7394956170054ceeaa8f8af96cd44875c", + "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol": "2c3a3edc2b1a4ac2c4a8645475b51f2668b1ad5ea22df074d0c0ebd3122ce2e7", + "@openzeppelin/contracts-upgradeable/proxy/ClonesUpgradeable.sol": "e97be532a19dd1b1aa885216a3138ff4d96759981390a7a9ee6e23e768d96f6e", + "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol": "dabaab4d3d3f03e6bfb86eec1d54f31edf0429f4bfc4dff717d5776d5231c145", + "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol": "877bc9cb396d0f50330bb9c0057c029407e159739b6fab0b110f19451c8681e4", + "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol": "57cf669fc8977ccd1738280e0a39c0156ecbde0af6142d6dd845ab1c055ca61d" + } +} diff --git a/packages/legacy/verified/Curation/sources/@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol b/packages/legacy/verified/Curation/sources/@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol new file mode 100644 index 000000000..c7b5c001e --- /dev/null +++ b/packages/legacy/verified/Curation/sources/@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMathUpgradeable { + /** + * @dev Returns the addition of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + + /** + * @dev Returns the substraction of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b > a) return (false, 0); + return (true, a - b); + } + + /** + * @dev Returns the multiplication of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + + /** + * @dev Returns the division of two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b == 0) return (false, 0); + return (true, a / b); + } + + /** + * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b == 0) return (false, 0); + return (true, a % b); + } + + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + require(b <= a, "SafeMath: subtraction overflow"); + return a - b; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) return 0; + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + require(b > 0, "SafeMath: division by zero"); + return a / b; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + require(b > 0, "SafeMath: modulo by zero"); + return a % b; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {trySub}. + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + return a - b; + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting with custom message on + * division by zero. The result is rounded towards zero. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryDiv}. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + return a / b; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting with custom message when dividing by zero. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryMod}. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + return a % b; + } +} diff --git a/packages/legacy/verified/Curation/sources/@openzeppelin/contracts-upgradeable/proxy/ClonesUpgradeable.sol b/packages/legacy/verified/Curation/sources/@openzeppelin/contracts-upgradeable/proxy/ClonesUpgradeable.sol new file mode 100644 index 000000000..ef497fddf --- /dev/null +++ b/packages/legacy/verified/Curation/sources/@openzeppelin/contracts-upgradeable/proxy/ClonesUpgradeable.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for + * deploying minimal proxy contracts, also known as "clones". + * + * > To simply and cheaply clone contract functionality in an immutable way, this standard specifies + * > a minimal bytecode implementation that delegates all calls to a known, fixed address. + * + * The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2` + * (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the + * deterministic method. + * + * _Available since v3.4._ + */ +library ClonesUpgradeable { + /** + * @dev Deploys and returns the address of a clone that mimics the behaviour of `master`. + * + * This function uses the create opcode, which should never revert. + */ + function clone(address master) internal returns (address instance) { + // solhint-disable-next-line no-inline-assembly + assembly { + let ptr := mload(0x40) + mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000) + mstore(add(ptr, 0x14), shl(0x60, master)) + mstore(add(ptr, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000) + instance := create(0, ptr, 0x37) + } + require(instance != address(0), "ERC1167: create failed"); + } + + /** + * @dev Deploys and returns the address of a clone that mimics the behaviour of `master`. + * + * This function uses the create2 opcode and a `salt` to deterministically deploy + * the clone. Using the same `master` and `salt` multiple time will revert, since + * the clones cannot be deployed twice at the same address. + */ + function cloneDeterministic(address master, bytes32 salt) internal returns (address instance) { + // solhint-disable-next-line no-inline-assembly + assembly { + let ptr := mload(0x40) + mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000) + mstore(add(ptr, 0x14), shl(0x60, master)) + mstore(add(ptr, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000) + instance := create2(0, ptr, 0x37, salt) + } + require(instance != address(0), "ERC1167: create2 failed"); + } + + /** + * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}. + */ + function predictDeterministicAddress(address master, bytes32 salt, address deployer) internal pure returns (address predicted) { + // solhint-disable-next-line no-inline-assembly + assembly { + let ptr := mload(0x40) + mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000) + mstore(add(ptr, 0x14), shl(0x60, master)) + mstore(add(ptr, 0x28), 0x5af43d82803e903d91602b57fd5bf3ff00000000000000000000000000000000) + mstore(add(ptr, 0x38), shl(0x60, deployer)) + mstore(add(ptr, 0x4c), salt) + mstore(add(ptr, 0x6c), keccak256(ptr, 0x37)) + predicted := keccak256(add(ptr, 0x37), 0x55) + } + } + + /** + * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}. + */ + function predictDeterministicAddress(address master, bytes32 salt) internal view returns (address predicted) { + return predictDeterministicAddress(master, salt, address(this)); + } +} diff --git a/packages/legacy/verified/Curation/sources/@openzeppelin/contracts-upgradeable/proxy/Initializable.sol b/packages/legacy/verified/Curation/sources/@openzeppelin/contracts-upgradeable/proxy/Initializable.sol new file mode 100644 index 000000000..9fd6dc9fc --- /dev/null +++ b/packages/legacy/verified/Curation/sources/@openzeppelin/contracts-upgradeable/proxy/Initializable.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT + +// solhint-disable-next-line compiler-version +pragma solidity >=0.4.24 <0.8.0; + +import "../utils/AddressUpgradeable.sol"; + +/** + * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed + * behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an + * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer + * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. + * + * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as + * possible by providing the encoded function call as the `_data` argument to {UpgradeableProxy-constructor}. + * + * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure + * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. + */ +abstract contract Initializable { + + /** + * @dev Indicates that the contract has been initialized. + */ + bool private _initialized; + + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool private _initializing; + + /** + * @dev Modifier to protect an initializer function from being invoked twice. + */ + modifier initializer() { + require(_initializing || _isConstructor() || !_initialized, "Initializable: contract is already initialized"); + + bool isTopLevelCall = !_initializing; + if (isTopLevelCall) { + _initializing = true; + _initialized = true; + } + + _; + + if (isTopLevelCall) { + _initializing = false; + } + } + + /// @dev Returns true if and only if the function is running in the constructor + function _isConstructor() private view returns (bool) { + return !AddressUpgradeable.isContract(address(this)); + } +} diff --git a/packages/legacy/verified/Curation/sources/@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol b/packages/legacy/verified/Curation/sources/@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol new file mode 100644 index 000000000..e4c8c06ec --- /dev/null +++ b/packages/legacy/verified/Curation/sources/@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20Upgradeable { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} diff --git a/packages/legacy/verified/Curation/sources/@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol b/packages/legacy/verified/Curation/sources/@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol new file mode 100644 index 000000000..08a59041b --- /dev/null +++ b/packages/legacy/verified/Curation/sources/@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.2 <0.8.0; + +/** + * @dev Collection of functions related to the address type + */ +library AddressUpgradeable { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + uint256 size; + // solhint-disable-next-line no-inline-assembly + assembly { size := extcodesize(account) } + return size > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + // solhint-disable-next-line avoid-low-level-calls, avoid-call-value + (bool success, ) = recipient.call{ value: amount }(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain`call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.call{ value: value }(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.staticcall(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) { + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + + // solhint-disable-next-line no-inline-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} diff --git a/packages/legacy/verified/Curation/sources/@openzeppelin/contracts/math/SafeMath.sol b/packages/legacy/verified/Curation/sources/@openzeppelin/contracts/math/SafeMath.sol new file mode 100644 index 000000000..3e81f295b --- /dev/null +++ b/packages/legacy/verified/Curation/sources/@openzeppelin/contracts/math/SafeMath.sol @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + + /** + * @dev Returns the substraction of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b > a) return (false, 0); + return (true, a - b); + } + + /** + * @dev Returns the multiplication of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + + /** + * @dev Returns the division of two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b == 0) return (false, 0); + return (true, a / b); + } + + /** + * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b == 0) return (false, 0); + return (true, a % b); + } + + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + require(b <= a, "SafeMath: subtraction overflow"); + return a - b; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) return 0; + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + require(b > 0, "SafeMath: division by zero"); + return a / b; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + require(b > 0, "SafeMath: modulo by zero"); + return a % b; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {trySub}. + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + return a - b; + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting with custom message on + * division by zero. The result is rounded towards zero. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryDiv}. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + return a / b; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting with custom message when dividing by zero. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryMod}. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + return a % b; + } +} diff --git a/packages/legacy/verified/Curation/sources/@openzeppelin/contracts/token/ERC20/IERC20.sol b/packages/legacy/verified/Curation/sources/@openzeppelin/contracts/token/ERC20/IERC20.sol new file mode 100644 index 000000000..9b877d119 --- /dev/null +++ b/packages/legacy/verified/Curation/sources/@openzeppelin/contracts/token/ERC20/IERC20.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} diff --git a/packages/legacy/verified/Curation/sources/contracts/arbitrum/ITokenGateway.sol b/packages/legacy/verified/Curation/sources/contracts/arbitrum/ITokenGateway.sol new file mode 100644 index 000000000..977fe07f2 --- /dev/null +++ b/packages/legacy/verified/Curation/sources/contracts/arbitrum/ITokenGateway.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2020, Offchain Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Originally copied from: + * https://github.com/OffchainLabs/arbitrum/tree/e3a6307ad8a2dc2cad35728a2a9908cfd8dd8ef9/packages/arb-bridge-peripherals + * + * MODIFIED from Offchain Labs' implementation: + * - Changed solidity version to 0.7.6 (pablo@edgeandnode.com) + * + */ + +pragma solidity ^0.7.6; + +interface ITokenGateway { + /// @notice event deprecated in favor of DepositInitiated and WithdrawalInitiated + // event OutboundTransferInitiated( + // address token, + // address indexed _from, + // address indexed _to, + // uint256 indexed _transferId, + // uint256 _amount, + // bytes _data + // ); + + /// @notice event deprecated in favor of DepositFinalized and WithdrawalFinalized + // event InboundTransferFinalized( + // address token, + // address indexed _from, + // address indexed _to, + // uint256 indexed _transferId, + // uint256 _amount, + // bytes _data + // ); + + function outboundTransfer( + address _token, + address _to, + uint256 _amount, + uint256 _maxGas, + uint256 _gasPriceBid, + bytes calldata _data + ) external payable returns (bytes memory); + + function finalizeInboundTransfer( + address _token, + address _from, + address _to, + uint256 _amount, + bytes calldata _data + ) external payable; + + /** + * @notice Calculate the address used when bridging an ERC20 token + * @dev the L1 and L2 address oracles may not always be in sync. + * For example, a custom token may have been registered but not deployed or the contract self destructed. + * @param l1ERC20 address of L1 token + * @return L2 address of a bridged ERC20 token + */ + function calculateL2TokenAddress(address l1ERC20) external view returns (address); +} diff --git a/packages/legacy/verified/Curation/sources/contracts/bancor/BancorFormula.sol b/packages/legacy/verified/Curation/sources/contracts/bancor/BancorFormula.sol new file mode 100644 index 000000000..9cb41a502 --- /dev/null +++ b/packages/legacy/verified/Curation/sources/contracts/bancor/BancorFormula.sol @@ -0,0 +1,775 @@ +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.7.6; + +import "@openzeppelin/contracts/math/SafeMath.sol"; + +contract BancorFormula { + using SafeMath for uint256; + + uint16 public constant version = 6; + + uint256 private constant ONE = 1; + uint32 private constant MAX_RATIO = 1000000; + uint8 private constant MIN_PRECISION = 32; + uint8 private constant MAX_PRECISION = 127; + + /** + * @dev Auto-generated via 'PrintIntScalingFactors.py' + */ + uint256 private constant FIXED_1 = 0x080000000000000000000000000000000; + uint256 private constant FIXED_2 = 0x100000000000000000000000000000000; + uint256 private constant MAX_NUM = 0x200000000000000000000000000000000; + + /** + * @dev Auto-generated via 'PrintLn2ScalingFactors.py' + */ + uint256 private constant LN2_NUMERATOR = 0x3f80fe03f80fe03f80fe03f80fe03f8; + uint256 private constant LN2_DENOMINATOR = 0x5b9de1d10bf4103d647b0955897ba80; + + /** + * @dev Auto-generated via 'PrintFunctionOptimalLog.py' and 'PrintFunctionOptimalExp.py' + */ + uint256 private constant OPT_LOG_MAX_VAL = 0x15bf0a8b1457695355fb8ac404e7a79e3; + uint256 private constant OPT_EXP_MAX_VAL = 0x800000000000000000000000000000000; + + /** + * @dev Auto-generated via 'PrintFunctionConstructor.py' + */ + uint256[128] private maxExpArray; + + constructor() { + // maxExpArray[ 0] = 0x6bffffffffffffffffffffffffffffffff; + // maxExpArray[ 1] = 0x67ffffffffffffffffffffffffffffffff; + // maxExpArray[ 2] = 0x637fffffffffffffffffffffffffffffff; + // maxExpArray[ 3] = 0x5f6fffffffffffffffffffffffffffffff; + // maxExpArray[ 4] = 0x5b77ffffffffffffffffffffffffffffff; + // maxExpArray[ 5] = 0x57b3ffffffffffffffffffffffffffffff; + // maxExpArray[ 6] = 0x5419ffffffffffffffffffffffffffffff; + // maxExpArray[ 7] = 0x50a2ffffffffffffffffffffffffffffff; + // maxExpArray[ 8] = 0x4d517fffffffffffffffffffffffffffff; + // maxExpArray[ 9] = 0x4a233fffffffffffffffffffffffffffff; + // maxExpArray[ 10] = 0x47165fffffffffffffffffffffffffffff; + // maxExpArray[ 11] = 0x4429afffffffffffffffffffffffffffff; + // maxExpArray[ 12] = 0x415bc7ffffffffffffffffffffffffffff; + // maxExpArray[ 13] = 0x3eab73ffffffffffffffffffffffffffff; + // maxExpArray[ 14] = 0x3c1771ffffffffffffffffffffffffffff; + // maxExpArray[ 15] = 0x399e96ffffffffffffffffffffffffffff; + // maxExpArray[ 16] = 0x373fc47fffffffffffffffffffffffffff; + // maxExpArray[ 17] = 0x34f9e8ffffffffffffffffffffffffffff; + // maxExpArray[ 18] = 0x32cbfd5fffffffffffffffffffffffffff; + // maxExpArray[ 19] = 0x30b5057fffffffffffffffffffffffffff; + // maxExpArray[ 20] = 0x2eb40f9fffffffffffffffffffffffffff; + // maxExpArray[ 21] = 0x2cc8340fffffffffffffffffffffffffff; + // maxExpArray[ 22] = 0x2af09481ffffffffffffffffffffffffff; + // maxExpArray[ 23] = 0x292c5bddffffffffffffffffffffffffff; + // maxExpArray[ 24] = 0x277abdcdffffffffffffffffffffffffff; + // maxExpArray[ 25] = 0x25daf6657fffffffffffffffffffffffff; + // maxExpArray[ 26] = 0x244c49c65fffffffffffffffffffffffff; + // maxExpArray[ 27] = 0x22ce03cd5fffffffffffffffffffffffff; + // maxExpArray[ 28] = 0x215f77c047ffffffffffffffffffffffff; + // maxExpArray[ 29] = 0x1fffffffffffffffffffffffffffffffff; + // maxExpArray[ 30] = 0x1eaefdbdabffffffffffffffffffffffff; + // maxExpArray[ 31] = 0x1d6bd8b2ebffffffffffffffffffffffff; + maxExpArray[32] = 0x1c35fedd14ffffffffffffffffffffffff; + maxExpArray[33] = 0x1b0ce43b323fffffffffffffffffffffff; + maxExpArray[34] = 0x19f0028ec1ffffffffffffffffffffffff; + maxExpArray[35] = 0x18ded91f0e7fffffffffffffffffffffff; + maxExpArray[36] = 0x17d8ec7f0417ffffffffffffffffffffff; + maxExpArray[37] = 0x16ddc6556cdbffffffffffffffffffffff; + maxExpArray[38] = 0x15ecf52776a1ffffffffffffffffffffff; + maxExpArray[39] = 0x15060c256cb2ffffffffffffffffffffff; + maxExpArray[40] = 0x1428a2f98d72ffffffffffffffffffffff; + maxExpArray[41] = 0x13545598e5c23fffffffffffffffffffff; + maxExpArray[42] = 0x1288c4161ce1dfffffffffffffffffffff; + maxExpArray[43] = 0x11c592761c666fffffffffffffffffffff; + maxExpArray[44] = 0x110a688680a757ffffffffffffffffffff; + maxExpArray[45] = 0x1056f1b5bedf77ffffffffffffffffffff; + maxExpArray[46] = 0x0faadceceeff8bffffffffffffffffffff; + maxExpArray[47] = 0x0f05dc6b27edadffffffffffffffffffff; + maxExpArray[48] = 0x0e67a5a25da4107fffffffffffffffffff; + maxExpArray[49] = 0x0dcff115b14eedffffffffffffffffffff; + maxExpArray[50] = 0x0d3e7a392431239fffffffffffffffffff; + maxExpArray[51] = 0x0cb2ff529eb71e4fffffffffffffffffff; + maxExpArray[52] = 0x0c2d415c3db974afffffffffffffffffff; + maxExpArray[53] = 0x0bad03e7d883f69bffffffffffffffffff; + maxExpArray[54] = 0x0b320d03b2c343d5ffffffffffffffffff; + maxExpArray[55] = 0x0abc25204e02828dffffffffffffffffff; + maxExpArray[56] = 0x0a4b16f74ee4bb207fffffffffffffffff; + maxExpArray[57] = 0x09deaf736ac1f569ffffffffffffffffff; + maxExpArray[58] = 0x0976bd9952c7aa957fffffffffffffffff; + maxExpArray[59] = 0x09131271922eaa606fffffffffffffffff; + maxExpArray[60] = 0x08b380f3558668c46fffffffffffffffff; + maxExpArray[61] = 0x0857ddf0117efa215bffffffffffffffff; + maxExpArray[62] = 0x07ffffffffffffffffffffffffffffffff; + maxExpArray[63] = 0x07abbf6f6abb9d087fffffffffffffffff; + maxExpArray[64] = 0x075af62cbac95f7dfa7fffffffffffffff; + maxExpArray[65] = 0x070d7fb7452e187ac13fffffffffffffff; + maxExpArray[66] = 0x06c3390ecc8af379295fffffffffffffff; + maxExpArray[67] = 0x067c00a3b07ffc01fd6fffffffffffffff; + maxExpArray[68] = 0x0637b647c39cbb9d3d27ffffffffffffff; + maxExpArray[69] = 0x05f63b1fc104dbd39587ffffffffffffff; + maxExpArray[70] = 0x05b771955b36e12f7235ffffffffffffff; + maxExpArray[71] = 0x057b3d49dda84556d6f6ffffffffffffff; + maxExpArray[72] = 0x054183095b2c8ececf30ffffffffffffff; + maxExpArray[73] = 0x050a28be635ca2b888f77fffffffffffff; + maxExpArray[74] = 0x04d5156639708c9db33c3fffffffffffff; + maxExpArray[75] = 0x04a23105873875bd52dfdfffffffffffff; + maxExpArray[76] = 0x0471649d87199aa990756fffffffffffff; + maxExpArray[77] = 0x04429a21a029d4c1457cfbffffffffffff; + maxExpArray[78] = 0x0415bc6d6fb7dd71af2cb3ffffffffffff; + maxExpArray[79] = 0x03eab73b3bbfe282243ce1ffffffffffff; + maxExpArray[80] = 0x03c1771ac9fb6b4c18e229ffffffffffff; + maxExpArray[81] = 0x0399e96897690418f785257fffffffffff; + maxExpArray[82] = 0x0373fc456c53bb779bf0ea9fffffffffff; + maxExpArray[83] = 0x034f9e8e490c48e67e6ab8bfffffffffff; + maxExpArray[84] = 0x032cbfd4a7adc790560b3337ffffffffff; + maxExpArray[85] = 0x030b50570f6e5d2acca94613ffffffffff; + maxExpArray[86] = 0x02eb40f9f620fda6b56c2861ffffffffff; + maxExpArray[87] = 0x02cc8340ecb0d0f520a6af58ffffffffff; + maxExpArray[88] = 0x02af09481380a0a35cf1ba02ffffffffff; + maxExpArray[89] = 0x0292c5bdd3b92ec810287b1b3fffffffff; + maxExpArray[90] = 0x0277abdcdab07d5a77ac6d6b9fffffffff; + maxExpArray[91] = 0x025daf6654b1eaa55fd64df5efffffffff; + maxExpArray[92] = 0x0244c49c648baa98192dce88b7ffffffff; + maxExpArray[93] = 0x022ce03cd5619a311b2471268bffffffff; + maxExpArray[94] = 0x0215f77c045fbe885654a44a0fffffffff; + maxExpArray[95] = 0x01ffffffffffffffffffffffffffffffff; + maxExpArray[96] = 0x01eaefdbdaaee7421fc4d3ede5ffffffff; + maxExpArray[97] = 0x01d6bd8b2eb257df7e8ca57b09bfffffff; + maxExpArray[98] = 0x01c35fedd14b861eb0443f7f133fffffff; + maxExpArray[99] = 0x01b0ce43b322bcde4a56e8ada5afffffff; + maxExpArray[100] = 0x019f0028ec1fff007f5a195a39dfffffff; + maxExpArray[101] = 0x018ded91f0e72ee74f49b15ba527ffffff; + maxExpArray[102] = 0x017d8ec7f04136f4e5615fd41a63ffffff; + maxExpArray[103] = 0x016ddc6556cdb84bdc8d12d22e6fffffff; + maxExpArray[104] = 0x015ecf52776a1155b5bd8395814f7fffff; + maxExpArray[105] = 0x015060c256cb23b3b3cc3754cf40ffffff; + maxExpArray[106] = 0x01428a2f98d728ae223ddab715be3fffff; + maxExpArray[107] = 0x013545598e5c23276ccf0ede68034fffff; + maxExpArray[108] = 0x01288c4161ce1d6f54b7f61081194fffff; + maxExpArray[109] = 0x011c592761c666aa641d5a01a40f17ffff; + maxExpArray[110] = 0x0110a688680a7530515f3e6e6cfdcdffff; + maxExpArray[111] = 0x01056f1b5bedf75c6bcb2ce8aed428ffff; + maxExpArray[112] = 0x00faadceceeff8a0890f3875f008277fff; + maxExpArray[113] = 0x00f05dc6b27edad306388a600f6ba0bfff; + maxExpArray[114] = 0x00e67a5a25da41063de1495d5b18cdbfff; + maxExpArray[115] = 0x00dcff115b14eedde6fc3aa5353f2e4fff; + maxExpArray[116] = 0x00d3e7a3924312399f9aae2e0f868f8fff; + maxExpArray[117] = 0x00cb2ff529eb71e41582cccd5a1ee26fff; + maxExpArray[118] = 0x00c2d415c3db974ab32a51840c0b67edff; + maxExpArray[119] = 0x00bad03e7d883f69ad5b0a186184e06bff; + maxExpArray[120] = 0x00b320d03b2c343d4829abd6075f0cc5ff; + maxExpArray[121] = 0x00abc25204e02828d73c6e80bcdb1a95bf; + maxExpArray[122] = 0x00a4b16f74ee4bb2040a1ec6c15fbbf2df; + maxExpArray[123] = 0x009deaf736ac1f569deb1b5ae3f36c130f; + maxExpArray[124] = 0x00976bd9952c7aa957f5937d790ef65037; + maxExpArray[125] = 0x009131271922eaa6064b73a22d0bd4f2bf; + maxExpArray[126] = 0x008b380f3558668c46c91c49a2f8e967b9; + maxExpArray[127] = 0x00857ddf0117efa215952912839f6473e6; + } + + /** + * @dev given a token supply, reserve balance, ratio and a deposit amount (in the reserve token), + * calculates the return for a given conversion (in the main token) + * + * Formula: + * Return = _supply * ((1 + _depositAmount / _reserveBalance) ^ (_reserveRatio / 1000000) - 1) + * + * @param _supply token total supply + * @param _reserveBalance total reserve balance + * @param _reserveRatio reserve ratio, represented in ppm, 1-1000000 + * @param _depositAmount deposit amount, in reserve token + * + * @return purchase return amount + */ + function calculatePurchaseReturn( + uint256 _supply, + uint256 _reserveBalance, + uint32 _reserveRatio, + uint256 _depositAmount + ) public view returns (uint256) { + // validate input + require( + _supply > 0 && _reserveBalance > 0 && _reserveRatio > 0 && _reserveRatio <= MAX_RATIO, + "invalid parameters" + ); + + // special case for 0 deposit amount + if (_depositAmount == 0) return 0; + + // special case if the ratio = 100% + if (_reserveRatio == MAX_RATIO) return _supply.mul(_depositAmount) / _reserveBalance; + + uint256 result; + uint8 precision; + uint256 baseN = _depositAmount.add(_reserveBalance); + (result, precision) = power(baseN, _reserveBalance, _reserveRatio, MAX_RATIO); + uint256 temp = _supply.mul(result) >> precision; + return temp - _supply; + } + + /** + * @dev given a token supply, reserve balance, ratio and a sell amount (in the main token), + * calculates the return for a given conversion (in the reserve token) + * + * Formula: + * Return = _reserveBalance * (1 - (1 - _sellAmount / _supply) ^ (1000000 / _reserveRatio)) + * + * @param _supply token total supply + * @param _reserveBalance total reserve + * @param _reserveRatio constant reserve Ratio, represented in ppm, 1-1000000 + * @param _sellAmount sell amount, in the token itself + * + * @return sale return amount + */ + function calculateSaleReturn( + uint256 _supply, + uint256 _reserveBalance, + uint32 _reserveRatio, + uint256 _sellAmount + ) public view returns (uint256) { + // validate input + require( + _supply > 0 && + _reserveBalance > 0 && + _reserveRatio > 0 && + _reserveRatio <= MAX_RATIO && + _sellAmount <= _supply, + "invalid parameters" + ); + + // special case for 0 sell amount + if (_sellAmount == 0) return 0; + + // special case for selling the entire supply + if (_sellAmount == _supply) return _reserveBalance; + + // special case if the ratio = 100% + if (_reserveRatio == MAX_RATIO) return _reserveBalance.mul(_sellAmount) / _supply; + + uint256 result; + uint8 precision; + uint256 baseD = _supply - _sellAmount; + (result, precision) = power(_supply, baseD, MAX_RATIO, _reserveRatio); + uint256 temp1 = _reserveBalance.mul(result); + uint256 temp2 = _reserveBalance << precision; + return (temp1 - temp2) / result; + } + + /** + * @dev given two reserve balances/ratios and a sell amount (in the first reserve token), + * calculates the return for a conversion from the first reserve token to the second reserve token (in the second reserve token) + * note that prior to version 4, you should use 'calculateCrossConnectorReturn' instead + * + * Formula: + * Return = _toReserveBalance * (1 - (_fromReserveBalance / (_fromReserveBalance + _amount)) ^ (_fromReserveRatio / _toReserveRatio)) + * + * @param _fromReserveBalance input reserve balance + * @param _fromReserveRatio input reserve ratio, represented in ppm, 1-1000000 + * @param _toReserveBalance output reserve balance + * @param _toReserveRatio output reserve ratio, represented in ppm, 1-1000000 + * @param _amount input reserve amount + * + * @return second reserve amount + */ + function calculateCrossReserveReturn( + uint256 _fromReserveBalance, + uint32 _fromReserveRatio, + uint256 _toReserveBalance, + uint32 _toReserveRatio, + uint256 _amount + ) public view returns (uint256) { + // validate input + require( + _fromReserveBalance > 0 && + _fromReserveRatio > 0 && + _fromReserveRatio <= MAX_RATIO && + _toReserveBalance > 0 && + _toReserveRatio > 0 && + _toReserveRatio <= MAX_RATIO + ); + + // special case for equal ratios + if (_fromReserveRatio == _toReserveRatio) + return _toReserveBalance.mul(_amount) / _fromReserveBalance.add(_amount); + + uint256 result; + uint8 precision; + uint256 baseN = _fromReserveBalance.add(_amount); + (result, precision) = power(baseN, _fromReserveBalance, _fromReserveRatio, _toReserveRatio); + uint256 temp1 = _toReserveBalance.mul(result); + uint256 temp2 = _toReserveBalance << precision; + return (temp1 - temp2) / result; + } + + /** + * @dev given a smart token supply, reserve balance, total ratio and an amount of requested smart tokens, + * calculates the amount of reserve tokens required for purchasing the given amount of smart tokens + * + * Formula: + * Return = _reserveBalance * (((_supply + _amount) / _supply) ^ (MAX_RATIO / _totalRatio) - 1) + * + * @param _supply smart token supply + * @param _reserveBalance reserve token balance + * @param _totalRatio total ratio, represented in ppm, 2-2000000 + * @param _amount requested amount of smart tokens + * + * @return amount of reserve tokens + */ + function calculateFundCost( + uint256 _supply, + uint256 _reserveBalance, + uint32 _totalRatio, + uint256 _amount + ) public view returns (uint256) { + // validate input + require( + _supply > 0 && _reserveBalance > 0 && _totalRatio > 1 && _totalRatio <= MAX_RATIO * 2 + ); + + // special case for 0 amount + if (_amount == 0) return 0; + + // special case if the total ratio = 100% + if (_totalRatio == MAX_RATIO) return (_amount.mul(_reserveBalance) - 1) / _supply + 1; + + uint256 result; + uint8 precision; + uint256 baseN = _supply.add(_amount); + (result, precision) = power(baseN, _supply, MAX_RATIO, _totalRatio); + uint256 temp = ((_reserveBalance.mul(result) - 1) >> precision) + 1; + return temp - _reserveBalance; + } + + /** + * @dev given a smart token supply, reserve balance, total ratio and an amount of smart tokens to liquidate, + * calculates the amount of reserve tokens received for selling the given amount of smart tokens + * + * Formula: + * Return = _reserveBalance * (1 - ((_supply - _amount) / _supply) ^ (MAX_RATIO / _totalRatio)) + * + * @param _supply smart token supply + * @param _reserveBalance reserve token balance + * @param _totalRatio total ratio, represented in ppm, 2-2000000 + * @param _amount amount of smart tokens to liquidate + * + * @return amount of reserve tokens + */ + function calculateLiquidateReturn( + uint256 _supply, + uint256 _reserveBalance, + uint32 _totalRatio, + uint256 _amount + ) public view returns (uint256) { + // validate input + require( + _supply > 0 && + _reserveBalance > 0 && + _totalRatio > 1 && + _totalRatio <= MAX_RATIO * 2 && + _amount <= _supply + ); + + // special case for 0 amount + if (_amount == 0) return 0; + + // special case for liquidating the entire supply + if (_amount == _supply) return _reserveBalance; + + // special case if the total ratio = 100% + if (_totalRatio == MAX_RATIO) return _amount.mul(_reserveBalance) / _supply; + + uint256 result; + uint8 precision; + uint256 baseD = _supply - _amount; + (result, precision) = power(_supply, baseD, MAX_RATIO, _totalRatio); + uint256 temp1 = _reserveBalance.mul(result); + uint256 temp2 = _reserveBalance << precision; + return (temp1 - temp2) / result; + } + + /** + * @dev General Description: + * Determine a value of precision. + * Calculate an integer approximation of (_baseN / _baseD) ^ (_expN / _expD) * 2 ^ precision. + * Return the result along with the precision used. + * + * Detailed Description: + * Instead of calculating "base ^ exp", we calculate "e ^ (log(base) * exp)". + * The value of "log(base)" is represented with an integer slightly smaller than "log(base) * 2 ^ precision". + * The larger "precision" is, the more accurately this value represents the real value. + * However, the larger "precision" is, the more bits are required in order to store this value. + * And the exponentiation function, which takes "x" and calculates "e ^ x", is limited to a maximum exponent (maximum value of "x"). + * This maximum exponent depends on the "precision" used, and it is given by "maxExpArray[precision] >> (MAX_PRECISION - precision)". + * Hence we need to determine the highest precision which can be used for the given input, before calling the exponentiation function. + * This allows us to compute "base ^ exp" with maximum accuracy and without exceeding 256 bits in any of the intermediate computations. + * This functions assumes that "_expN < 2 ^ 256 / log(MAX_NUM - 1)", otherwise the multiplication should be replaced with a "safeMul". + * Since we rely on unsigned-integer arithmetic and "base < 1" ==> "log(base) < 0", this function does not support "_baseN < _baseD". + */ + function power( + uint256 _baseN, + uint256 _baseD, + uint32 _expN, + uint32 _expD + ) internal view returns (uint256, uint8) { + require(_baseN < MAX_NUM); + + uint256 baseLog; + uint256 base = (_baseN * FIXED_1) / _baseD; + if (base < OPT_LOG_MAX_VAL) { + baseLog = optimalLog(base); + } else { + baseLog = generalLog(base); + } + + uint256 baseLogTimesExp = (baseLog * _expN) / _expD; + if (baseLogTimesExp < OPT_EXP_MAX_VAL) { + return (optimalExp(baseLogTimesExp), MAX_PRECISION); + } else { + uint8 precision = findPositionInMaxExpArray(baseLogTimesExp); + return ( + generalExp(baseLogTimesExp >> (MAX_PRECISION - precision), precision), + precision + ); + } + } + + /** + * @dev computes log(x / FIXED_1) * FIXED_1. + * This functions assumes that "x >= FIXED_1", because the output would be negative otherwise. + */ + function generalLog(uint256 x) internal pure returns (uint256) { + uint256 res = 0; + + // If x >= 2, then we compute the integer part of log2(x), which is larger than 0. + if (x >= FIXED_2) { + uint8 count = floorLog2(x / FIXED_1); + x >>= count; // now x < 2 + res = count * FIXED_1; + } + + // If x > 1, then we compute the fraction part of log2(x), which is larger than 0. + if (x > FIXED_1) { + for (uint8 i = MAX_PRECISION; i > 0; --i) { + x = (x * x) / FIXED_1; // now 1 < x < 4 + if (x >= FIXED_2) { + x >>= 1; // now 1 < x < 2 + res += ONE << (i - 1); + } + } + } + + return (res * LN2_NUMERATOR) / LN2_DENOMINATOR; + } + + /** + * @dev computes the largest integer smaller than or equal to the binary logarithm of the input. + */ + function floorLog2(uint256 _n) internal pure returns (uint8) { + uint8 res = 0; + + if (_n < 256) { + // At most 8 iterations + while (_n > 1) { + _n >>= 1; + res += 1; + } + } else { + // Exactly 8 iterations + for (uint8 s = 128; s > 0; s >>= 1) { + if (_n >= (ONE << s)) { + _n >>= s; + res |= s; + } + } + } + + return res; + } + + /** + * @dev the global "maxExpArray" is sorted in descending order, and therefore the following statements are equivalent: + * - This function finds the position of [the smallest value in "maxExpArray" larger than or equal to "x"] + * - This function finds the highest position of [a value in "maxExpArray" larger than or equal to "x"] + */ + function findPositionInMaxExpArray(uint256 _x) internal view returns (uint8) { + uint8 lo = MIN_PRECISION; + uint8 hi = MAX_PRECISION; + + while (lo + 1 < hi) { + uint8 mid = (lo + hi) / 2; + if (maxExpArray[mid] >= _x) lo = mid; + else hi = mid; + } + + if (maxExpArray[hi] >= _x) return hi; + if (maxExpArray[lo] >= _x) return lo; + + require(false); + return 0; + } + + /** + * @dev this function can be auto-generated by the script 'PrintFunctionGeneralExp.py'. + * it approximates "e ^ x" via maclaurin summation: "(x^0)/0! + (x^1)/1! + ... + (x^n)/n!". + * it returns "e ^ (x / 2 ^ precision) * 2 ^ precision", that is, the result is upshifted for accuracy. + * the global "maxExpArray" maps each "precision" to "((maximumExponent + 1) << (MAX_PRECISION - precision)) - 1". + * the maximum permitted value for "x" is therefore given by "maxExpArray[precision] >> (MAX_PRECISION - precision)". + */ + function generalExp(uint256 _x, uint8 _precision) internal pure returns (uint256) { + uint256 xi = _x; + uint256 res = 0; + + xi = (xi * _x) >> _precision; + res += xi * 0x3442c4e6074a82f1797f72ac0000000; // add x^02 * (33! / 02!) + xi = (xi * _x) >> _precision; + res += xi * 0x116b96f757c380fb287fd0e40000000; // add x^03 * (33! / 03!) + xi = (xi * _x) >> _precision; + res += xi * 0x045ae5bdd5f0e03eca1ff4390000000; // add x^04 * (33! / 04!) + xi = (xi * _x) >> _precision; + res += xi * 0x00defabf91302cd95b9ffda50000000; // add x^05 * (33! / 05!) + xi = (xi * _x) >> _precision; + res += xi * 0x002529ca9832b22439efff9b8000000; // add x^06 * (33! / 06!) + xi = (xi * _x) >> _precision; + res += xi * 0x00054f1cf12bd04e516b6da88000000; // add x^07 * (33! / 07!) + xi = (xi * _x) >> _precision; + res += xi * 0x0000a9e39e257a09ca2d6db51000000; // add x^08 * (33! / 08!) + xi = (xi * _x) >> _precision; + res += xi * 0x000012e066e7b839fa050c309000000; // add x^09 * (33! / 09!) + xi = (xi * _x) >> _precision; + res += xi * 0x000001e33d7d926c329a1ad1a800000; // add x^10 * (33! / 10!) + xi = (xi * _x) >> _precision; + res += xi * 0x0000002bee513bdb4a6b19b5f800000; // add x^11 * (33! / 11!) + xi = (xi * _x) >> _precision; + res += xi * 0x00000003a9316fa79b88eccf2a00000; // add x^12 * (33! / 12!) + xi = (xi * _x) >> _precision; + res += xi * 0x0000000048177ebe1fa812375200000; // add x^13 * (33! / 13!) + xi = (xi * _x) >> _precision; + res += xi * 0x0000000005263fe90242dcbacf00000; // add x^14 * (33! / 14!) + xi = (xi * _x) >> _precision; + res += xi * 0x000000000057e22099c030d94100000; // add x^15 * (33! / 15!) + xi = (xi * _x) >> _precision; + res += xi * 0x0000000000057e22099c030d9410000; // add x^16 * (33! / 16!) + xi = (xi * _x) >> _precision; + res += xi * 0x00000000000052b6b54569976310000; // add x^17 * (33! / 17!) + xi = (xi * _x) >> _precision; + res += xi * 0x00000000000004985f67696bf748000; // add x^18 * (33! / 18!) + xi = (xi * _x) >> _precision; + res += xi * 0x000000000000003dea12ea99e498000; // add x^19 * (33! / 19!) + xi = (xi * _x) >> _precision; + res += xi * 0x00000000000000031880f2214b6e000; // add x^20 * (33! / 20!) + xi = (xi * _x) >> _precision; + res += xi * 0x000000000000000025bcff56eb36000; // add x^21 * (33! / 21!) + xi = (xi * _x) >> _precision; + res += xi * 0x000000000000000001b722e10ab1000; // add x^22 * (33! / 22!) + xi = (xi * _x) >> _precision; + res += xi * 0x0000000000000000001317c70077000; // add x^23 * (33! / 23!) + xi = (xi * _x) >> _precision; + res += xi * 0x00000000000000000000cba84aafa00; // add x^24 * (33! / 24!) + xi = (xi * _x) >> _precision; + res += xi * 0x00000000000000000000082573a0a00; // add x^25 * (33! / 25!) + xi = (xi * _x) >> _precision; + res += xi * 0x00000000000000000000005035ad900; // add x^26 * (33! / 26!) + xi = (xi * _x) >> _precision; + res += xi * 0x000000000000000000000002f881b00; // add x^27 * (33! / 27!) + xi = (xi * _x) >> _precision; + res += xi * 0x0000000000000000000000001b29340; // add x^28 * (33! / 28!) + xi = (xi * _x) >> _precision; + res += xi * 0x00000000000000000000000000efc40; // add x^29 * (33! / 29!) + xi = (xi * _x) >> _precision; + res += xi * 0x0000000000000000000000000007fe0; // add x^30 * (33! / 30!) + xi = (xi * _x) >> _precision; + res += xi * 0x0000000000000000000000000000420; // add x^31 * (33! / 31!) + xi = (xi * _x) >> _precision; + res += xi * 0x0000000000000000000000000000021; // add x^32 * (33! / 32!) + xi = (xi * _x) >> _precision; + res += xi * 0x0000000000000000000000000000001; // add x^33 * (33! / 33!) + + return res / 0x688589cc0e9505e2f2fee5580000000 + _x + (ONE << _precision); // divide by 33! and then add x^1 / 1! + x^0 / 0! + } + + /** + * @dev computes log(x / FIXED_1) * FIXED_1 + * Input range: FIXED_1 <= x <= LOG_EXP_MAX_VAL - 1 + * Auto-generated via 'PrintFunctionOptimalLog.py' + * Detailed description: + * - Rewrite the input as a product of natural exponents and a single residual r, such that 1 < r < 2 + * - The natural logarithm of each (pre-calculated) exponent is the degree of the exponent + * - The natural logarithm of r is calculated via Taylor series for log(1 + x), where x = r - 1 + * - The natural logarithm of the input is calculated by summing up the intermediate results above + * - For example: log(250) = log(e^4 * e^1 * e^0.5 * 1.021692859) = 4 + 1 + 0.5 + log(1 + 0.021692859) + */ + function optimalLog(uint256 x) internal pure returns (uint256) { + uint256 res = 0; + + uint256 y; + uint256 z; + uint256 w; + + if (x >= 0xd3094c70f034de4b96ff7d5b6f99fcd8) { + res += 0x40000000000000000000000000000000; + x = (x * FIXED_1) / 0xd3094c70f034de4b96ff7d5b6f99fcd8; + } // add 1 / 2^1 + if (x >= 0xa45af1e1f40c333b3de1db4dd55f29a7) { + res += 0x20000000000000000000000000000000; + x = (x * FIXED_1) / 0xa45af1e1f40c333b3de1db4dd55f29a7; + } // add 1 / 2^2 + if (x >= 0x910b022db7ae67ce76b441c27035c6a1) { + res += 0x10000000000000000000000000000000; + x = (x * FIXED_1) / 0x910b022db7ae67ce76b441c27035c6a1; + } // add 1 / 2^3 + if (x >= 0x88415abbe9a76bead8d00cf112e4d4a8) { + res += 0x08000000000000000000000000000000; + x = (x * FIXED_1) / 0x88415abbe9a76bead8d00cf112e4d4a8; + } // add 1 / 2^4 + if (x >= 0x84102b00893f64c705e841d5d4064bd3) { + res += 0x04000000000000000000000000000000; + x = (x * FIXED_1) / 0x84102b00893f64c705e841d5d4064bd3; + } // add 1 / 2^5 + if (x >= 0x8204055aaef1c8bd5c3259f4822735a2) { + res += 0x02000000000000000000000000000000; + x = (x * FIXED_1) / 0x8204055aaef1c8bd5c3259f4822735a2; + } // add 1 / 2^6 + if (x >= 0x810100ab00222d861931c15e39b44e99) { + res += 0x01000000000000000000000000000000; + x = (x * FIXED_1) / 0x810100ab00222d861931c15e39b44e99; + } // add 1 / 2^7 + if (x >= 0x808040155aabbbe9451521693554f733) { + res += 0x00800000000000000000000000000000; + x = (x * FIXED_1) / 0x808040155aabbbe9451521693554f733; + } // add 1 / 2^8 + + z = y = x - FIXED_1; + w = (y * y) / FIXED_1; + res += + (z * (0x100000000000000000000000000000000 - y)) / + 0x100000000000000000000000000000000; + z = (z * w) / FIXED_1; // add y^01 / 01 - y^02 / 02 + res += + (z * (0x0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - y)) / + 0x200000000000000000000000000000000; + z = (z * w) / FIXED_1; // add y^03 / 03 - y^04 / 04 + res += + (z * (0x099999999999999999999999999999999 - y)) / + 0x300000000000000000000000000000000; + z = (z * w) / FIXED_1; // add y^05 / 05 - y^06 / 06 + res += + (z * (0x092492492492492492492492492492492 - y)) / + 0x400000000000000000000000000000000; + z = (z * w) / FIXED_1; // add y^07 / 07 - y^08 / 08 + res += + (z * (0x08e38e38e38e38e38e38e38e38e38e38e - y)) / + 0x500000000000000000000000000000000; + z = (z * w) / FIXED_1; // add y^09 / 09 - y^10 / 10 + res += + (z * (0x08ba2e8ba2e8ba2e8ba2e8ba2e8ba2e8b - y)) / + 0x600000000000000000000000000000000; + z = (z * w) / FIXED_1; // add y^11 / 11 - y^12 / 12 + res += + (z * (0x089d89d89d89d89d89d89d89d89d89d89 - y)) / + 0x700000000000000000000000000000000; + z = (z * w) / FIXED_1; // add y^13 / 13 - y^14 / 14 + res += + (z * (0x088888888888888888888888888888888 - y)) / + 0x800000000000000000000000000000000; // add y^15 / 15 - y^16 / 16 + + return res; + } + + /** + * @dev computes e ^ (x / FIXED_1) * FIXED_1 + * input range: 0 <= x <= OPT_EXP_MAX_VAL - 1 + * auto-generated via 'PrintFunctionOptimalExp.py' + * Detailed description: + * - Rewrite the input as a sum of binary exponents and a single residual r, as small as possible + * - The exponentiation of each binary exponent is given (pre-calculated) + * - The exponentiation of r is calculated via Taylor series for e^x, where x = r + * - The exponentiation of the input is calculated by multiplying the intermediate results above + * - For example: e^5.521692859 = e^(4 + 1 + 0.5 + 0.021692859) = e^4 * e^1 * e^0.5 * e^0.021692859 + */ + function optimalExp(uint256 x) internal pure returns (uint256) { + uint256 res = 0; + + uint256 y; + uint256 z; + + z = y = x % 0x10000000000000000000000000000000; // get the input modulo 2^(-3) + z = (z * y) / FIXED_1; + res += z * 0x10e1b3be415a0000; // add y^02 * (20! / 02!) + z = (z * y) / FIXED_1; + res += z * 0x05a0913f6b1e0000; // add y^03 * (20! / 03!) + z = (z * y) / FIXED_1; + res += z * 0x0168244fdac78000; // add y^04 * (20! / 04!) + z = (z * y) / FIXED_1; + res += z * 0x004807432bc18000; // add y^05 * (20! / 05!) + z = (z * y) / FIXED_1; + res += z * 0x000c0135dca04000; // add y^06 * (20! / 06!) + z = (z * y) / FIXED_1; + res += z * 0x0001b707b1cdc000; // add y^07 * (20! / 07!) + z = (z * y) / FIXED_1; + res += z * 0x000036e0f639b800; // add y^08 * (20! / 08!) + z = (z * y) / FIXED_1; + res += z * 0x00000618fee9f800; // add y^09 * (20! / 09!) + z = (z * y) / FIXED_1; + res += z * 0x0000009c197dcc00; // add y^10 * (20! / 10!) + z = (z * y) / FIXED_1; + res += z * 0x0000000e30dce400; // add y^11 * (20! / 11!) + z = (z * y) / FIXED_1; + res += z * 0x000000012ebd1300; // add y^12 * (20! / 12!) + z = (z * y) / FIXED_1; + res += z * 0x0000000017499f00; // add y^13 * (20! / 13!) + z = (z * y) / FIXED_1; + res += z * 0x0000000001a9d480; // add y^14 * (20! / 14!) + z = (z * y) / FIXED_1; + res += z * 0x00000000001c6380; // add y^15 * (20! / 15!) + z = (z * y) / FIXED_1; + res += z * 0x000000000001c638; // add y^16 * (20! / 16!) + z = (z * y) / FIXED_1; + res += z * 0x0000000000001ab8; // add y^17 * (20! / 17!) + z = (z * y) / FIXED_1; + res += z * 0x000000000000017c; // add y^18 * (20! / 18!) + z = (z * y) / FIXED_1; + res += z * 0x0000000000000014; // add y^19 * (20! / 19!) + z = (z * y) / FIXED_1; + res += z * 0x0000000000000001; // add y^20 * (20! / 20!) + res = res / 0x21c3677c82b40000 + y + FIXED_1; // divide by 20! and then add y^1 / 1! + y^0 / 0! + + if ((x & 0x010000000000000000000000000000000) != 0) + res = (res * 0x1c3d6a24ed82218787d624d3e5eba95f9) / 0x18ebef9eac820ae8682b9793ac6d1e776; // multiply by e^2^(-3) + if ((x & 0x020000000000000000000000000000000) != 0) + res = (res * 0x18ebef9eac820ae8682b9793ac6d1e778) / 0x1368b2fc6f9609fe7aceb46aa619baed4; // multiply by e^2^(-2) + if ((x & 0x040000000000000000000000000000000) != 0) + res = (res * 0x1368b2fc6f9609fe7aceb46aa619baed5) / 0x0bc5ab1b16779be3575bd8f0520a9f21f; // multiply by e^2^(-1) + if ((x & 0x080000000000000000000000000000000) != 0) + res = (res * 0x0bc5ab1b16779be3575bd8f0520a9f21e) / 0x0454aaa8efe072e7f6ddbab84b40a55c9; // multiply by e^2^(+0) + if ((x & 0x100000000000000000000000000000000) != 0) + res = (res * 0x0454aaa8efe072e7f6ddbab84b40a55c5) / 0x00960aadc109e7a3bf4578099615711ea; // multiply by e^2^(+1) + if ((x & 0x200000000000000000000000000000000) != 0) + res = (res * 0x00960aadc109e7a3bf4578099615711d7) / 0x0002bf84208204f5977f9a8cf01fdce3d; // multiply by e^2^(+2) + if ((x & 0x400000000000000000000000000000000) != 0) + res = (res * 0x0002bf84208204f5977f9a8cf01fdc307) / 0x0000003c6ab775dd0b95b4cbee7e65d11; // multiply by e^2^(+3) + + return res; + } + + /** + * @dev deprecated, backward compatibility + */ + function calculateCrossConnectorReturn( + uint256 _fromConnectorBalance, + uint32 _fromConnectorWeight, + uint256 _toConnectorBalance, + uint32 _toConnectorWeight, + uint256 _amount + ) public view returns (uint256) { + return + calculateCrossReserveReturn( + _fromConnectorBalance, + _fromConnectorWeight, + _toConnectorBalance, + _toConnectorWeight, + _amount + ); + } +} diff --git a/packages/legacy/verified/Curation/sources/contracts/base/IMulticall.sol b/packages/legacy/verified/Curation/sources/contracts/base/IMulticall.sol new file mode 100644 index 000000000..3269f694b --- /dev/null +++ b/packages/legacy/verified/Curation/sources/contracts/base/IMulticall.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; +pragma abicoder v2; + +/** + * @title Multicall interface + * @notice Enables calling multiple methods in a single call to the contract + */ +interface IMulticall { + /** + * @notice Call multiple functions in the current contract and return the data from all of them if they all succeed + * @param data The encoded function data for each of the calls to make to this contract + * @return results The results from each of the calls passed in via data + */ + function multicall(bytes[] calldata data) external returns (bytes[] memory results); +} diff --git a/packages/legacy/verified/Curation/sources/contracts/curation/Curation.sol b/packages/legacy/verified/Curation/sources/contracts/curation/Curation.sol new file mode 100644 index 000000000..04c3a6217 --- /dev/null +++ b/packages/legacy/verified/Curation/sources/contracts/curation/Curation.sol @@ -0,0 +1,488 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; +pragma abicoder v2; + +import { AddressUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; +import { SafeMathUpgradeable } from "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; +import { ClonesUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/ClonesUpgradeable.sol"; + +import { BancorFormula } from "../bancor/BancorFormula.sol"; +import { GraphUpgradeable } from "../upgrades/GraphUpgradeable.sol"; +import { TokenUtils } from "../utils/TokenUtils.sol"; +import { IRewardsManager } from "../rewards/IRewardsManager.sol"; +import { Managed } from "../governance/Managed.sol"; +import { IGraphToken } from "../token/IGraphToken.sol"; +import { CurationV2Storage } from "./CurationStorage.sol"; +import { ICuration } from "./ICuration.sol"; +import { IGraphCurationToken } from "./IGraphCurationToken.sol"; + +/** + * @title Curation contract + * @dev Allows curators to signal on subgraph deployments that might be relevant to indexers by + * staking Graph Tokens (GRT). Additionally, curators earn fees from the Query Market related to the + * subgraph deployment they curate. + * A curators deposit goes to a curation pool along with the deposits of other curators, + * only one such pool exists for each subgraph deployment. + * The contract mints Graph Curation Shares (GCS) according to a bonding curve for each individual + * curation pool where GRT is deposited. + * Holders can burn GCS using this contract to get GRT tokens back according to the + * bonding curve. + */ +contract Curation is CurationV2Storage, GraphUpgradeable { + using SafeMathUpgradeable for uint256; + + /// @dev 100% in parts per million + uint32 private constant MAX_PPM = 1000000; + + /// @dev Amount of signal you get with your minimum token deposit + uint256 private constant SIGNAL_PER_MINIMUM_DEPOSIT = 1e18; // 1 signal as 18 decimal number + + // -- Events -- + + /** + * @dev Emitted when `curator` deposited `tokens` on `subgraphDeploymentID` as curation signal. + * The `curator` receives `signal` amount according to the curation pool bonding curve. + * An amount of `curationTax` will be collected and burned. + */ + event Signalled( + address indexed curator, + bytes32 indexed subgraphDeploymentID, + uint256 tokens, + uint256 signal, + uint256 curationTax + ); + + /** + * @dev Emitted when `curator` burned `signal` for a `subgraphDeploymentID`. + * The curator will receive `tokens` according to the value of the bonding curve. + */ + event Burned( + address indexed curator, + bytes32 indexed subgraphDeploymentID, + uint256 tokens, + uint256 signal + ); + + /** + * @dev Emitted when `tokens` amount were collected for `subgraphDeploymentID` as part of fees + * distributed by an indexer from query fees received from state channels. + */ + event Collected(bytes32 indexed subgraphDeploymentID, uint256 tokens); + + /** + * @notice Initialize this contract. + * @param _controller Address of the controller contract that manages this contract + * @param _bondingCurve Address of the bonding curve contract (e.g. a BancorFormula) + * @param _curationTokenMaster Address of the master copy to use for curation tokens + * @param _defaultReserveRatio Default reserve ratio for a curation pool in PPM + * @param _curationTaxPercentage Percentage of curation tax to charge when depositing GRT tokens + * @param _minimumCurationDeposit Minimum amount of tokens that can be deposited on a new subgraph deployment + */ + function initialize( + address _controller, + address _bondingCurve, + address _curationTokenMaster, + uint32 _defaultReserveRatio, + uint32 _curationTaxPercentage, + uint256 _minimumCurationDeposit + ) external onlyImpl initializer { + Managed._initialize(_controller); + + require(_bondingCurve != address(0), "Bonding curve must be set"); + bondingCurve = _bondingCurve; + + // Settings + _setDefaultReserveRatio(_defaultReserveRatio); + _setCurationTaxPercentage(_curationTaxPercentage); + _setMinimumCurationDeposit(_minimumCurationDeposit); + _setCurationTokenMaster(_curationTokenMaster); + } + + /** + * @dev Set the default reserve ratio percentage for a curation pool. + * @notice Update the default reserve ratio to `_defaultReserveRatio` + * @param _defaultReserveRatio Reserve ratio (in PPM) + */ + function setDefaultReserveRatio(uint32 _defaultReserveRatio) external override onlyGovernor { + _setDefaultReserveRatio(_defaultReserveRatio); + } + + /** + * @dev Set the minimum deposit amount for curators. + * @notice Update the minimum deposit amount to `_minimumCurationDeposit` + * @param _minimumCurationDeposit Minimum amount of tokens required deposit + */ + function setMinimumCurationDeposit(uint256 _minimumCurationDeposit) + external + override + onlyGovernor + { + _setMinimumCurationDeposit(_minimumCurationDeposit); + } + + /** + * @notice Set the curation tax percentage to charge when a curator deposits GRT tokens. + * @param _percentage Curation tax percentage charged when depositing GRT tokens + */ + function setCurationTaxPercentage(uint32 _percentage) external override onlyGovernor { + _setCurationTaxPercentage(_percentage); + } + + /** + * @notice Set the master copy to use as clones for the curation token. + * @param _curationTokenMaster Address of implementation contract to use for curation tokens + */ + function setCurationTokenMaster(address _curationTokenMaster) external override onlyGovernor { + _setCurationTokenMaster(_curationTokenMaster); + } + + /** + * @notice Assign Graph Tokens collected as curation fees to the curation pool reserve. + * @dev This function can only be called by the Staking contract and will do the bookkeeping of + * transferred tokens into this contract. + * @param _subgraphDeploymentID SubgraphDeployment where funds should be allocated as reserves + * @param _tokens Amount of Graph Tokens to add to reserves + */ + function collect(bytes32 _subgraphDeploymentID, uint256 _tokens) external override { + // Only Staking contract is authorized as caller + require(msg.sender == address(staking()), "Caller must be the staking contract"); + + // Must be curated to accept tokens + require( + isCurated(_subgraphDeploymentID), + "Subgraph deployment must be curated to collect fees" + ); + + // Collect new funds into reserve + CurationPool storage curationPool = pools[_subgraphDeploymentID]; + curationPool.tokens = curationPool.tokens.add(_tokens); + + emit Collected(_subgraphDeploymentID, _tokens); + } + + /** + * @notice Deposit Graph Tokens in exchange for signal of a SubgraphDeployment curation pool. + * @param _subgraphDeploymentID Subgraph deployment pool from where to mint signal + * @param _tokensIn Amount of Graph Tokens to deposit + * @param _signalOutMin Expected minimum amount of signal to receive + * @return Amount of signal minted + * @return Amount of curation tax burned + */ + function mint( + bytes32 _subgraphDeploymentID, + uint256 _tokensIn, + uint256 _signalOutMin + ) external override notPartialPaused returns (uint256, uint256) { + // Need to deposit some funds + require(_tokensIn != 0, "Cannot deposit zero tokens"); + + // Exchange GRT tokens for GCS of the subgraph pool + (uint256 signalOut, uint256 curationTax) = tokensToSignal(_subgraphDeploymentID, _tokensIn); + + // Slippage protection + require(signalOut >= _signalOutMin, "Slippage protection"); + + address curator = msg.sender; + CurationPool storage curationPool = pools[_subgraphDeploymentID]; + + // If it hasn't been curated before then initialize the curve + if (!isCurated(_subgraphDeploymentID)) { + curationPool.reserveRatio = defaultReserveRatio; + + // If no signal token for the pool - create one + if (address(curationPool.gcs) == address(0)) { + // Use a minimal proxy to reduce gas cost + IGraphCurationToken gcs = IGraphCurationToken( + ClonesUpgradeable.clone(curationTokenMaster) + ); + gcs.initialize(address(this)); + curationPool.gcs = gcs; + } + } + + // Trigger update rewards calculation snapshot + _updateRewards(_subgraphDeploymentID); + + // Transfer tokens from the curator to this contract + // Burn the curation tax + // NOTE: This needs to happen after _updateRewards snapshot as that function + // is using balanceOf(curation) + IGraphToken _graphToken = graphToken(); + TokenUtils.pullTokens(_graphToken, curator, _tokensIn); + TokenUtils.burnTokens(_graphToken, curationTax); + + // Update curation pool + curationPool.tokens = curationPool.tokens.add(_tokensIn.sub(curationTax)); + curationPool.gcs.mint(curator, signalOut); + + emit Signalled(curator, _subgraphDeploymentID, _tokensIn, signalOut, curationTax); + + return (signalOut, curationTax); + } + + /** + * @dev Return an amount of signal to get tokens back. + * @notice Burn _signal from the SubgraphDeployment curation pool + * @param _subgraphDeploymentID SubgraphDeployment the curator is returning signal + * @param _signalIn Amount of signal to return + * @param _tokensOutMin Expected minimum amount of tokens to receive + * @return Tokens returned + */ + function burn( + bytes32 _subgraphDeploymentID, + uint256 _signalIn, + uint256 _tokensOutMin + ) external override notPartialPaused returns (uint256) { + address curator = msg.sender; + + // Validations + require(_signalIn != 0, "Cannot burn zero signal"); + require( + getCuratorSignal(curator, _subgraphDeploymentID) >= _signalIn, + "Cannot burn more signal than you own" + ); + + // Get the amount of tokens to refund based on returned signal + uint256 tokensOut = signalToTokens(_subgraphDeploymentID, _signalIn); + + // Slippage protection + require(tokensOut >= _tokensOutMin, "Slippage protection"); + + // Trigger update rewards calculation + _updateRewards(_subgraphDeploymentID); + + // Update curation pool + CurationPool storage curationPool = pools[_subgraphDeploymentID]; + curationPool.tokens = curationPool.tokens.sub(tokensOut); + curationPool.gcs.burnFrom(curator, _signalIn); + + // If all signal burnt delete the curation pool except for the + // curation token contract to avoid recreating it on a new mint + if (getCurationPoolSignal(_subgraphDeploymentID) == 0) { + curationPool.tokens = 0; + curationPool.reserveRatio = 0; + } + + // Return the tokens to the curator + TokenUtils.pushTokens(graphToken(), curator, tokensOut); + + emit Burned(curator, _subgraphDeploymentID, tokensOut, _signalIn); + + return tokensOut; + } + + /** + * @notice Get the amount of token reserves in a curation pool. + * @param _subgraphDeploymentID Subgraph deployment curation poool + * @return Amount of token reserves in the curation pool + */ + function getCurationPoolTokens(bytes32 _subgraphDeploymentID) + external + view + override + returns (uint256) + { + return pools[_subgraphDeploymentID].tokens; + } + + /** + * @notice Check if any GRT tokens are deposited for a SubgraphDeployment. + * @param _subgraphDeploymentID SubgraphDeployment to check if curated + * @return True if curated, false otherwise + */ + function isCurated(bytes32 _subgraphDeploymentID) public view override returns (bool) { + return pools[_subgraphDeploymentID].tokens != 0; + } + + /** + * @notice Get the amount of signal a curator has in a curation pool. + * @param _curator Curator owning the signal tokens + * @param _subgraphDeploymentID Subgraph deployment curation pool + * @return Amount of signal owned by a curator for the subgraph deployment + */ + function getCuratorSignal(address _curator, bytes32 _subgraphDeploymentID) + public + view + override + returns (uint256) + { + IGraphCurationToken gcs = pools[_subgraphDeploymentID].gcs; + return (address(gcs) == address(0)) ? 0 : gcs.balanceOf(_curator); + } + + /** + * @notice Get the amount of signal in a curation pool. + * @param _subgraphDeploymentID Subgraph deployment curation poool + * @return Amount of signal minted for the subgraph deployment + */ + function getCurationPoolSignal(bytes32 _subgraphDeploymentID) + public + view + override + returns (uint256) + { + IGraphCurationToken gcs = pools[_subgraphDeploymentID].gcs; + return (address(gcs) == address(0)) ? 0 : gcs.totalSupply(); + } + + /** + * @notice Calculate amount of signal that can be bought with tokens in a curation pool. + * This function considers and excludes the deposit tax. + * @param _subgraphDeploymentID Subgraph deployment to mint signal + * @param _tokensIn Amount of tokens used to mint signal + * @return Amount of signal that can be bought + * @return Amount of tokens that will be burned as curation tax + */ + function tokensToSignal(bytes32 _subgraphDeploymentID, uint256 _tokensIn) + public + view + override + returns (uint256, uint256) + { + uint256 curationTax = _tokensIn.mul(uint256(curationTaxPercentage)).div(MAX_PPM); + uint256 signalOut = _tokensToSignal(_subgraphDeploymentID, _tokensIn.sub(curationTax)); + return (signalOut, curationTax); + } + + /** + * @dev Calculate amount of signal that can be bought with tokens in a curation pool. + * @param _subgraphDeploymentID Subgraph deployment to mint signal + * @param _tokensIn Amount of tokens used to mint signal + * @return Amount of signal that can be bought with tokens + */ + function _tokensToSignal(bytes32 _subgraphDeploymentID, uint256 _tokensIn) + private + view + returns (uint256) + { + // Get curation pool tokens and signal + CurationPool memory curationPool = pools[_subgraphDeploymentID]; + + // Init curation pool + if (curationPool.tokens == 0) { + require( + _tokensIn >= minimumCurationDeposit, + "Curation deposit is below minimum required" + ); + return + BancorFormula(bondingCurve) + .calculatePurchaseReturn( + SIGNAL_PER_MINIMUM_DEPOSIT, + minimumCurationDeposit, + defaultReserveRatio, + _tokensIn.sub(minimumCurationDeposit) + ) + .add(SIGNAL_PER_MINIMUM_DEPOSIT); + } + + return + BancorFormula(bondingCurve).calculatePurchaseReturn( + getCurationPoolSignal(_subgraphDeploymentID), + curationPool.tokens, + curationPool.reserveRatio, + _tokensIn + ); + } + + /** + * @notice Calculate number of tokens to get when burning signal from a curation pool. + * @param _subgraphDeploymentID Subgraph deployment to burn signal + * @param _signalIn Amount of signal to burn + * @return Amount of tokens to get for the specified amount of signal + */ + function signalToTokens(bytes32 _subgraphDeploymentID, uint256 _signalIn) + public + view + override + returns (uint256) + { + CurationPool memory curationPool = pools[_subgraphDeploymentID]; + uint256 curationPoolSignal = getCurationPoolSignal(_subgraphDeploymentID); + require( + curationPool.tokens != 0, + "Subgraph deployment must be curated to perform calculations" + ); + require( + curationPoolSignal >= _signalIn, + "Signal must be above or equal to signal issued in the curation pool" + ); + + return + BancorFormula(bondingCurve).calculateSaleReturn( + curationPoolSignal, + curationPool.tokens, + curationPool.reserveRatio, + _signalIn + ); + } + + /** + * @dev Internal: Set the default reserve ratio percentage for a curation pool. + * @notice Update the default reserver ratio to `_defaultReserveRatio` + * @param _defaultReserveRatio Reserve ratio (in PPM) + */ + function _setDefaultReserveRatio(uint32 _defaultReserveRatio) private { + // Reserve Ratio must be within 0% to 100% (inclusive, in PPM) + require(_defaultReserveRatio != 0, "Default reserve ratio must be > 0"); + require( + _defaultReserveRatio <= MAX_PPM, + "Default reserve ratio cannot be higher than MAX_PPM" + ); + + defaultReserveRatio = _defaultReserveRatio; + emit ParameterUpdated("defaultReserveRatio"); + } + + /** + * @dev Internal: Set the minimum deposit amount for curators. + * @notice Update the minimum deposit amount to `_minimumCurationDeposit` + * @param _minimumCurationDeposit Minimum amount of tokens required deposit + */ + function _setMinimumCurationDeposit(uint256 _minimumCurationDeposit) private { + require(_minimumCurationDeposit != 0, "Minimum curation deposit cannot be 0"); + + minimumCurationDeposit = _minimumCurationDeposit; + emit ParameterUpdated("minimumCurationDeposit"); + } + + /** + * @dev Internal: Set the curation tax percentage (in PPM) to charge when a curator deposits GRT tokens. + * @param _percentage Curation tax charged when depositing GRT tokens in PPM + */ + function _setCurationTaxPercentage(uint32 _percentage) private { + require( + _percentage <= MAX_PPM, + "Curation tax percentage must be below or equal to MAX_PPM" + ); + + curationTaxPercentage = _percentage; + emit ParameterUpdated("curationTaxPercentage"); + } + + /** + * @dev Internal: Set the master copy to use as clones for the curation token. + * @param _curationTokenMaster Address of implementation contract to use for curation tokens + */ + function _setCurationTokenMaster(address _curationTokenMaster) private { + require(_curationTokenMaster != address(0), "Token master must be non-empty"); + require( + AddressUpgradeable.isContract(_curationTokenMaster), + "Token master must be a contract" + ); + + curationTokenMaster = _curationTokenMaster; + emit ParameterUpdated("curationTokenMaster"); + } + + /** + * @dev Triggers an update of rewards due to a change in signal. + * @param _subgraphDeploymentID Subgraph deployment updated + */ + function _updateRewards(bytes32 _subgraphDeploymentID) private { + IRewardsManager rewardsManager = rewardsManager(); + if (address(rewardsManager) != address(0)) { + rewardsManager.onSubgraphSignalUpdate(_subgraphDeploymentID); + } + } +} diff --git a/packages/legacy/verified/Curation/sources/contracts/curation/CurationStorage.sol b/packages/legacy/verified/Curation/sources/contracts/curation/CurationStorage.sol new file mode 100644 index 000000000..bcbf0df6b --- /dev/null +++ b/packages/legacy/verified/Curation/sources/contracts/curation/CurationStorage.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol"; + +import { ICuration } from "./ICuration.sol"; +import { IGraphCurationToken } from "./IGraphCurationToken.sol"; +import { Managed } from "../governance/Managed.sol"; + +/** + * @title Curation Storage version 1 + * @dev This contract holds the first version of the storage variables + * for the Curation and L2Curation contracts. + * When adding new variables, create a new version that inherits this and update + * the contracts to use the new version instead. + */ +abstract contract CurationV1Storage is Managed, ICuration { + // -- Pool -- + + /** + * @dev CurationPool structure that holds the pool's state + * for a particular subgraph deployment. + */ + struct CurationPool { + uint256 tokens; // GRT Tokens stored as reserves for the subgraph deployment + uint32 reserveRatio; // Ratio for the bonding curve, unused and deprecated in L2 where it will always be 100% but appear as 0 + IGraphCurationToken gcs; // Curation token contract for this curation pool + } + + // -- State -- + + /// Tax charged when curators deposit funds. + /// Parts per million. (Allows for 4 decimal points, 999,999 = 99.9999%) + uint32 public override curationTaxPercentage; + + /// Default reserve ratio to configure curator shares bonding curve + /// Parts per million. (Allows for 4 decimal points, 999,999 = 99.9999%). + /// Unused in L2. + uint32 public defaultReserveRatio; + + /// Master copy address that holds implementation of curation token. + /// @dev This is used as the target for GraphCurationToken clones. + address public curationTokenMaster; + + /// Minimum amount allowed to be deposited by curators to initialize a pool + /// @dev This is the `startPoolBalance` for the bonding curve + uint256 public minimumCurationDeposit; + + /// Bonding curve library + /// Unused in L2. + address public bondingCurve; + + /// @dev Mapping of subgraphDeploymentID => CurationPool + /// There is only one CurationPool per SubgraphDeploymentID + mapping(bytes32 => CurationPool) public pools; +} + +/** + * @title Curation Storage version 2 + * @dev This contract holds the second version of the storage variables + * for the Curation and L2Curation contracts. + * It doesn't add new variables at this contract's level, but adds the Initializable + * contract to the inheritance chain, which includes storage variables. + * When adding new variables, create a new version that inherits this and update + * the contracts to use the new version instead. + */ +abstract contract CurationV2Storage is CurationV1Storage, Initializable { + // Nothing here, just adding Initializable +} diff --git a/packages/legacy/verified/Curation/sources/contracts/curation/ICuration.sol b/packages/legacy/verified/Curation/sources/contracts/curation/ICuration.sol new file mode 100644 index 000000000..dffff46cd --- /dev/null +++ b/packages/legacy/verified/Curation/sources/contracts/curation/ICuration.sol @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +/** + * @title Curation Interface + * @dev Interface for the Curation contract (and L2Curation too) + */ +interface ICuration { + // -- Configuration -- + + /** + * @notice Update the default reserve ratio to `_defaultReserveRatio` + * @param _defaultReserveRatio Reserve ratio (in PPM) + */ + function setDefaultReserveRatio(uint32 _defaultReserveRatio) external; + + /** + * @notice Update the minimum deposit amount needed to intialize a new subgraph + * @param _minimumCurationDeposit Minimum amount of tokens required deposit + */ + function setMinimumCurationDeposit(uint256 _minimumCurationDeposit) external; + + /** + * @notice Set the curation tax percentage to charge when a curator deposits GRT tokens. + * @param _percentage Curation tax percentage charged when depositing GRT tokens + */ + function setCurationTaxPercentage(uint32 _percentage) external; + + /** + * @notice Set the master copy to use as clones for the curation token. + * @param _curationTokenMaster Address of implementation contract to use for curation tokens + */ + function setCurationTokenMaster(address _curationTokenMaster) external; + + // -- Curation -- + + /** + * @notice Deposit Graph Tokens in exchange for signal of a SubgraphDeployment curation pool. + * @param _subgraphDeploymentID Subgraph deployment pool from where to mint signal + * @param _tokensIn Amount of Graph Tokens to deposit + * @param _signalOutMin Expected minimum amount of signal to receive + * @return Amount of signal minted + * @return Amount of curation tax burned + */ + function mint( + bytes32 _subgraphDeploymentID, + uint256 _tokensIn, + uint256 _signalOutMin + ) external returns (uint256, uint256); + + /** + * @notice Burn _signal from the SubgraphDeployment curation pool + * @param _subgraphDeploymentID SubgraphDeployment the curator is returning signal + * @param _signalIn Amount of signal to return + * @param _tokensOutMin Expected minimum amount of tokens to receive + * @return Tokens returned + */ + function burn( + bytes32 _subgraphDeploymentID, + uint256 _signalIn, + uint256 _tokensOutMin + ) external returns (uint256); + + /** + * @notice Assign Graph Tokens collected as curation fees to the curation pool reserve. + * @param _subgraphDeploymentID SubgraphDeployment where funds should be allocated as reserves + * @param _tokens Amount of Graph Tokens to add to reserves + */ + function collect(bytes32 _subgraphDeploymentID, uint256 _tokens) external; + + // -- Getters -- + + /** + * @notice Check if any GRT tokens are deposited for a SubgraphDeployment. + * @param _subgraphDeploymentID SubgraphDeployment to check if curated + * @return True if curated, false otherwise + */ + function isCurated(bytes32 _subgraphDeploymentID) external view returns (bool); + + /** + * @notice Get the amount of signal a curator has in a curation pool. + * @param _curator Curator owning the signal tokens + * @param _subgraphDeploymentID Subgraph deployment curation pool + * @return Amount of signal owned by a curator for the subgraph deployment + */ + function getCuratorSignal(address _curator, bytes32 _subgraphDeploymentID) + external + view + returns (uint256); + + /** + * @notice Get the amount of signal in a curation pool. + * @param _subgraphDeploymentID Subgraph deployment curation poool + * @return Amount of signal minted for the subgraph deployment + */ + function getCurationPoolSignal(bytes32 _subgraphDeploymentID) external view returns (uint256); + + /** + * @notice Get the amount of token reserves in a curation pool. + * @param _subgraphDeploymentID Subgraph deployment curation poool + * @return Amount of token reserves in the curation pool + */ + function getCurationPoolTokens(bytes32 _subgraphDeploymentID) external view returns (uint256); + + /** + * @notice Calculate amount of signal that can be bought with tokens in a curation pool. + * This function considers and excludes the deposit tax. + * @param _subgraphDeploymentID Subgraph deployment to mint signal + * @param _tokensIn Amount of tokens used to mint signal + * @return Amount of signal that can be bought + * @return Amount of tokens that will be burned as curation tax + */ + function tokensToSignal(bytes32 _subgraphDeploymentID, uint256 _tokensIn) + external + view + returns (uint256, uint256); + + /** + * @notice Calculate number of tokens to get when burning signal from a curation pool. + * @param _subgraphDeploymentID Subgraph deployment to burn signal + * @param _signalIn Amount of signal to burn + * @return Amount of tokens to get for the specified amount of signal + */ + function signalToTokens(bytes32 _subgraphDeploymentID, uint256 _signalIn) + external + view + returns (uint256); + + /** + * @notice Tax charged when curators deposit funds. + * Parts per million. (Allows for 4 decimal points, 999,999 = 99.9999%) + * @return Curation tax percentage expressed in PPM + */ + function curationTaxPercentage() external view returns (uint32); +} diff --git a/packages/legacy/verified/Curation/sources/contracts/curation/IGraphCurationToken.sol b/packages/legacy/verified/Curation/sources/contracts/curation/IGraphCurationToken.sol new file mode 100644 index 000000000..43679aba6 --- /dev/null +++ b/packages/legacy/verified/Curation/sources/contracts/curation/IGraphCurationToken.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; + +interface IGraphCurationToken is IERC20Upgradeable { + function initialize(address _owner) external; + + function burnFrom(address _account, uint256 _amount) external; + + function mint(address _to, uint256 _amount) external; +} diff --git a/packages/legacy/verified/Curation/sources/contracts/discovery/IGNS.sol b/packages/legacy/verified/Curation/sources/contracts/discovery/IGNS.sol new file mode 100644 index 000000000..80cc99820 --- /dev/null +++ b/packages/legacy/verified/Curation/sources/contracts/discovery/IGNS.sol @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +/** + * @title Interface for GNS + */ +interface IGNS { + // -- Pool -- + + /** + * @dev The SubgraphData struct holds information about subgraphs + * and their signal; both nSignal (i.e. name signal at the GNS level) + * and vSignal (i.e. version signal at the Curation contract level) + */ + struct SubgraphData { + uint256 vSignal; // The token of the subgraph-deployment bonding curve + uint256 nSignal; // The token of the subgraph bonding curve + mapping(address => uint256) curatorNSignal; + bytes32 subgraphDeploymentID; + uint32 reserveRatioDeprecated; // Ratio for the bonding curve, always 1 in PPM, deprecated. + bool disabled; + uint256 withdrawableGRT; + } + + /** + * @dev The LegacySubgraphKey struct holds the account and sequence ID + * used to generate subgraph IDs in legacy subgraphs. + */ + struct LegacySubgraphKey { + address account; + uint256 accountSeqID; + } + + // -- Configuration -- + + /** + * @notice Approve curation contract to pull funds. + */ + function approveAll() external; + + /** + * @notice Set the owner fee percentage. This is used to prevent a subgraph owner to drain all + * the name curators tokens while upgrading or deprecating and is configurable in parts per million. + * @param _ownerTaxPercentage Owner tax percentage + */ + function setOwnerTaxPercentage(uint32 _ownerTaxPercentage) external; + + // -- Publishing -- + + /** + * @notice Allows a graph account to set a default name + * @param _graphAccount Account that is setting its name + * @param _nameSystem Name system account already has ownership of a name in + * @param _nameIdentifier The unique identifier that is used to identify the name in the system + * @param _name The name being set as default + */ + function setDefaultName( + address _graphAccount, + uint8 _nameSystem, + bytes32 _nameIdentifier, + string calldata _name + ) external; + + /** + * @notice Allows a subgraph owner to update the metadata of a subgraph they have published + * @param _subgraphID Subgraph ID + * @param _subgraphMetadata IPFS hash for the subgraph metadata + */ + function updateSubgraphMetadata(uint256 _subgraphID, bytes32 _subgraphMetadata) external; + + /** + * @notice Publish a new subgraph. + * @param _subgraphDeploymentID Subgraph deployment for the subgraph + * @param _versionMetadata IPFS hash for the subgraph version metadata + * @param _subgraphMetadata IPFS hash for the subgraph metadata + */ + function publishNewSubgraph( + bytes32 _subgraphDeploymentID, + bytes32 _versionMetadata, + bytes32 _subgraphMetadata + ) external; + + /** + * @notice Publish a new version of an existing subgraph. + * @param _subgraphID Subgraph ID + * @param _subgraphDeploymentID Subgraph deployment ID of the new version + * @param _versionMetadata IPFS hash for the subgraph version metadata + */ + function publishNewVersion( + uint256 _subgraphID, + bytes32 _subgraphDeploymentID, + bytes32 _versionMetadata + ) external; + + /** + * @notice Deprecate a subgraph. The bonding curve is destroyed, the vSignal is burned, and the GNS + * contract holds the GRT from burning the vSignal, which all curators can withdraw manually. + * Can only be done by the subgraph owner. + * @param _subgraphID Subgraph ID + */ + function deprecateSubgraph(uint256 _subgraphID) external; + + // -- Curation -- + + /** + * @notice Deposit GRT into a subgraph and mint signal. + * @param _subgraphID Subgraph ID + * @param _tokensIn The amount of tokens the nameCurator wants to deposit + * @param _nSignalOutMin Expected minimum amount of name signal to receive + */ + function mintSignal( + uint256 _subgraphID, + uint256 _tokensIn, + uint256 _nSignalOutMin + ) external; + + /** + * @notice Burn signal for a subgraph and return the GRT. + * @param _subgraphID Subgraph ID + * @param _nSignal The amount of nSignal the nameCurator wants to burn + * @param _tokensOutMin Expected minimum amount of tokens to receive + */ + function burnSignal( + uint256 _subgraphID, + uint256 _nSignal, + uint256 _tokensOutMin + ) external; + + /** + * @notice Move subgraph signal from sender to `_recipient` + * @param _subgraphID Subgraph ID + * @param _recipient Address to send the signal to + * @param _amount The amount of nSignal to transfer + */ + function transferSignal( + uint256 _subgraphID, + address _recipient, + uint256 _amount + ) external; + + /** + * @notice Withdraw tokens from a deprecated subgraph. + * When the subgraph is deprecated, any curator can call this function and + * withdraw the GRT they are entitled for its original deposit + * @param _subgraphID Subgraph ID + */ + function withdraw(uint256 _subgraphID) external; + + // -- Getters -- + + /** + * @notice Return the owner of a subgraph. + * @param _tokenID Subgraph ID + * @return Owner address + */ + function ownerOf(uint256 _tokenID) external view returns (address); + + /** + * @notice Return the total signal on the subgraph. + * @param _subgraphID Subgraph ID + * @return Total signal on the subgraph + */ + function subgraphSignal(uint256 _subgraphID) external view returns (uint256); + + /** + * @notice Return the total tokens on the subgraph at current value. + * @param _subgraphID Subgraph ID + * @return Total tokens on the subgraph + */ + function subgraphTokens(uint256 _subgraphID) external view returns (uint256); + + /** + * @notice Calculate subgraph signal to be returned for an amount of tokens. + * @param _subgraphID Subgraph ID + * @param _tokensIn Tokens being exchanged for subgraph signal + * @return Amount of subgraph signal and curation tax + */ + function tokensToNSignal(uint256 _subgraphID, uint256 _tokensIn) + external + view + returns ( + uint256, + uint256, + uint256 + ); + + /** + * @notice Calculate tokens returned for an amount of subgraph signal. + * @param _subgraphID Subgraph ID + * @param _nSignalIn Subgraph signal being exchanged for tokens + * @return Amount of tokens returned for an amount of subgraph signal + */ + function nSignalToTokens(uint256 _subgraphID, uint256 _nSignalIn) + external + view + returns (uint256, uint256); + + /** + * @notice Calculate subgraph signal to be returned for an amount of subgraph deployment signal. + * @param _subgraphID Subgraph ID + * @param _vSignalIn Amount of subgraph deployment signal to exchange for subgraph signal + * @return Amount of subgraph signal that can be bought + */ + function vSignalToNSignal(uint256 _subgraphID, uint256 _vSignalIn) + external + view + returns (uint256); + + /** + * @notice Calculate subgraph deployment signal to be returned for an amount of subgraph signal. + * @param _subgraphID Subgraph ID + * @param _nSignalIn Subgraph signal being exchanged for subgraph deployment signal + * @return Amount of subgraph deployment signal that can be returned + */ + function nSignalToVSignal(uint256 _subgraphID, uint256 _nSignalIn) + external + view + returns (uint256); + + /** + * @notice Get the amount of subgraph signal a curator has. + * @param _subgraphID Subgraph ID + * @param _curator Curator address + * @return Amount of subgraph signal owned by a curator + */ + function getCuratorSignal(uint256 _subgraphID, address _curator) + external + view + returns (uint256); + + /** + * @notice Return whether a subgraph is published. + * @param _subgraphID Subgraph ID + * @return Return true if subgraph is currently published + */ + function isPublished(uint256 _subgraphID) external view returns (bool); + + /** + * @notice Return whether a subgraph is a legacy subgraph (created before subgraph NFTs). + * @param _subgraphID Subgraph ID + * @return Return true if subgraph is a legacy subgraph + */ + function isLegacySubgraph(uint256 _subgraphID) external view returns (bool); + + /** + * @notice Returns account and sequence ID for a legacy subgraph (created before subgraph NFTs). + * @param _subgraphID Subgraph ID + * @return account Account that created the subgraph (or 0 if it's not a legacy subgraph) + * @return seqID Sequence number for the subgraph + */ + function getLegacySubgraphKey(uint256 _subgraphID) + external + view + returns (address account, uint256 seqID); +} diff --git a/packages/legacy/verified/Curation/sources/contracts/epochs/IEpochManager.sol b/packages/legacy/verified/Curation/sources/contracts/epochs/IEpochManager.sol new file mode 100644 index 000000000..36b1f47a3 --- /dev/null +++ b/packages/legacy/verified/Curation/sources/contracts/epochs/IEpochManager.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +interface IEpochManager { + // -- Configuration -- + + function setEpochLength(uint256 _epochLength) external; + + // -- Epochs + + function runEpoch() external; + + // -- Getters -- + + function isCurrentEpochRun() external view returns (bool); + + function blockNum() external view returns (uint256); + + function blockHash(uint256 _block) external view returns (bytes32); + + function currentEpoch() external view returns (uint256); + + function currentEpochBlock() external view returns (uint256); + + function currentEpochBlockSinceStart() external view returns (uint256); + + function epochsSince(uint256 _epoch) external view returns (uint256); + + function epochsSinceUpdate() external view returns (uint256); +} diff --git a/packages/legacy/verified/Curation/sources/contracts/governance/IController.sol b/packages/legacy/verified/Curation/sources/contracts/governance/IController.sol new file mode 100644 index 000000000..7df3d94ee --- /dev/null +++ b/packages/legacy/verified/Curation/sources/contracts/governance/IController.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; + +interface IController { + function getGovernor() external view returns (address); + + // -- Registry -- + + function setContractProxy(bytes32 _id, address _contractAddress) external; + + function unsetContractProxy(bytes32 _id) external; + + function updateController(bytes32 _id, address _controller) external; + + function getContractProxy(bytes32 _id) external view returns (address); + + // -- Pausing -- + + function setPartialPaused(bool _partialPaused) external; + + function setPaused(bool _paused) external; + + function setPauseGuardian(address _newPauseGuardian) external; + + function paused() external view returns (bool); + + function partialPaused() external view returns (bool); +} diff --git a/packages/legacy/verified/Curation/sources/contracts/governance/IManaged.sol b/packages/legacy/verified/Curation/sources/contracts/governance/IManaged.sol new file mode 100644 index 000000000..76f05e0fb --- /dev/null +++ b/packages/legacy/verified/Curation/sources/contracts/governance/IManaged.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import { IController } from "./IController.sol"; + +/** + * @title Managed Interface + * @dev Interface for contracts that can be managed by a controller. + */ +interface IManaged { + /** + * @notice Set the controller that manages this contract + * @dev Only the current controller can set a new controller + * @param _controller Address of the new controller + */ + function setController(address _controller) external; + + /** + * @notice Sync protocol contract addresses from the Controller registry + * @dev This function will cache all the contracts using the latest addresses. + * Anyone can call the function whenever a Proxy contract change in the + * controller to ensure the protocol is using the latest version. + */ + function syncAllContracts() external; + + /** + * @notice Get the Controller that manages this contract + * @return The Controller as an IController interface + */ + function controller() external view returns (IController); +} diff --git a/packages/legacy/verified/Curation/sources/contracts/governance/Managed.sol b/packages/legacy/verified/Curation/sources/contracts/governance/Managed.sol new file mode 100644 index 000000000..9b0ea29c8 --- /dev/null +++ b/packages/legacy/verified/Curation/sources/contracts/governance/Managed.sol @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import { IController } from "./IController.sol"; + +import { ICuration } from "../curation/ICuration.sol"; +import { IEpochManager } from "../epochs/IEpochManager.sol"; +import { IRewardsManager } from "../rewards/IRewardsManager.sol"; +import { IStaking } from "../staking/IStaking.sol"; +import { IGraphToken } from "../token/IGraphToken.sol"; +import { ITokenGateway } from "../arbitrum/ITokenGateway.sol"; +import { IGNS } from "../discovery/IGNS.sol"; + +import { IManaged } from "./IManaged.sol"; + +/** + * @title Graph Managed contract + * @dev The Managed contract provides an interface to interact with the Controller. + * It also provides local caching for contract addresses. This mechanism relies on calling the + * public `syncAllContracts()` function whenever a contract changes in the controller. + * + * Inspired by Livepeer: + * https://github.com/livepeer/protocol/blob/streamflow/contracts/Controller.sol + */ +abstract contract Managed is IManaged { + // -- State -- + + /// Controller that manages this contract + IController public override controller; + /// @dev Cache for the addresses of the contracts retrieved from the controller + mapping(bytes32 => address) private _addressCache; + /// @dev Gap for future storage variables + uint256[10] private __gap; + + // Immutables + bytes32 private immutable CURATION = keccak256("Curation"); + bytes32 private immutable EPOCH_MANAGER = keccak256("EpochManager"); + bytes32 private immutable REWARDS_MANAGER = keccak256("RewardsManager"); + bytes32 private immutable STAKING = keccak256("Staking"); + bytes32 private immutable GRAPH_TOKEN = keccak256("GraphToken"); + bytes32 private immutable GRAPH_TOKEN_GATEWAY = keccak256("GraphTokenGateway"); + bytes32 private immutable GNS = keccak256("GNS"); + + // -- Events -- + + /// Emitted when a contract parameter has been updated + event ParameterUpdated(string param); + /// Emitted when the controller address has been set + event SetController(address controller); + + /// Emitted when contract with `nameHash` is synced to `contractAddress`. + event ContractSynced(bytes32 indexed nameHash, address contractAddress); + + // -- Modifiers -- + + /** + * @dev Revert if the controller is paused or partially paused + */ + function _notPartialPaused() internal view { + require(!controller.paused(), "Paused"); + require(!controller.partialPaused(), "Partial-paused"); + } + + /** + * @dev Revert if the controller is paused + */ + function _notPaused() internal view virtual { + require(!controller.paused(), "Paused"); + } + + /** + * @dev Revert if the caller is not the governor + */ + function _onlyGovernor() internal view { + require(msg.sender == controller.getGovernor(), "Only Controller governor"); + } + + /** + * @dev Revert if the caller is not the Controller + */ + function _onlyController() internal view { + require(msg.sender == address(controller), "Caller must be Controller"); + } + + /** + * @dev Revert if the controller is paused or partially paused + */ + modifier notPartialPaused() { + _notPartialPaused(); + _; + } + + /** + * @dev Revert if the controller is paused + */ + modifier notPaused() { + _notPaused(); + _; + } + + /** + * @dev Revert if the caller is not the Controller + */ + modifier onlyController() { + _onlyController(); + _; + } + + /** + * @dev Revert if the caller is not the governor + */ + modifier onlyGovernor() { + _onlyGovernor(); + _; + } + + // -- Functions -- + + /** + * @dev Initialize a Managed contract + * @param _controller Address for the Controller that manages this contract + */ + function _initialize(address _controller) internal { + _setController(_controller); + } + + /** + * @notice Set Controller. Only callable by current controller. + * @param _controller Controller contract address + */ + function setController(address _controller) external override onlyController { + _setController(_controller); + } + + /** + * @dev Set controller. + * @param _controller Controller contract address + */ + function _setController(address _controller) internal { + require(_controller != address(0), "Controller must be set"); + controller = IController(_controller); + emit SetController(_controller); + } + + /** + * @dev Return Curation interface + * @return Curation contract registered with Controller + */ + function curation() internal view returns (ICuration) { + return ICuration(_resolveContract(CURATION)); + } + + /** + * @dev Return EpochManager interface + * @return Epoch manager contract registered with Controller + */ + function epochManager() internal view returns (IEpochManager) { + return IEpochManager(_resolveContract(EPOCH_MANAGER)); + } + + /** + * @dev Return RewardsManager interface + * @return Rewards manager contract registered with Controller + */ + function rewardsManager() internal view returns (IRewardsManager) { + return IRewardsManager(_resolveContract(REWARDS_MANAGER)); + } + + /** + * @dev Return Staking interface + * @return Staking contract registered with Controller + */ + function staking() internal view returns (IStaking) { + return IStaking(_resolveContract(STAKING)); + } + + /** + * @dev Return GraphToken interface + * @return Graph token contract registered with Controller + */ + function graphToken() internal view returns (IGraphToken) { + return IGraphToken(_resolveContract(GRAPH_TOKEN)); + } + + /** + * @dev Return GraphTokenGateway (L1 or L2) interface + * @return Graph token gateway contract registered with Controller + */ + function graphTokenGateway() internal view returns (ITokenGateway) { + return ITokenGateway(_resolveContract(GRAPH_TOKEN_GATEWAY)); + } + + /** + * @dev Return GNS (L1 or L2) interface. + * @return Address of the GNS contract registered with Controller, as an IGNS interface. + */ + function gns() internal view returns (IGNS) { + return IGNS(_resolveContract(GNS)); + } + + /** + * @dev Resolve a contract address from the cache or the Controller if not found. + * @param _nameHash keccak256 hash of the contract name + * @return Address of the contract + */ + function _resolveContract(bytes32 _nameHash) internal view returns (address) { + address contractAddress = _addressCache[_nameHash]; + if (contractAddress == address(0)) { + contractAddress = controller.getContractProxy(_nameHash); + } + return contractAddress; + } + + /** + * @dev Cache a contract address from the Controller registry. + * @param _nameHash keccak256 hash of the name of the contract to sync into the cache + */ + function _syncContract(bytes32 _nameHash) internal { + address contractAddress = controller.getContractProxy(_nameHash); + if (_addressCache[_nameHash] != contractAddress) { + _addressCache[_nameHash] = contractAddress; + emit ContractSynced(_nameHash, contractAddress); + } + } + + /** + * @notice Sync protocol contract addresses from the Controller registry + * @dev This function will cache all the contracts using the latest addresses + * Anyone can call the function whenever a Proxy contract change in the + * controller to ensure the protocol is using the latest version + */ + function syncAllContracts() external override { + _syncContract(CURATION); + _syncContract(EPOCH_MANAGER); + _syncContract(REWARDS_MANAGER); + _syncContract(STAKING); + _syncContract(GRAPH_TOKEN); + _syncContract(GRAPH_TOKEN_GATEWAY); + _syncContract(GNS); + } +} diff --git a/packages/legacy/verified/Curation/sources/contracts/rewards/IRewardsManager.sol b/packages/legacy/verified/Curation/sources/contracts/rewards/IRewardsManager.sol new file mode 100644 index 000000000..87d3c2455 --- /dev/null +++ b/packages/legacy/verified/Curation/sources/contracts/rewards/IRewardsManager.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +interface IRewardsManager { + /** + * @dev Stores accumulated rewards and snapshots related to a particular SubgraphDeployment. + */ + struct Subgraph { + uint256 accRewardsForSubgraph; + uint256 accRewardsForSubgraphSnapshot; + uint256 accRewardsPerSignalSnapshot; + uint256 accRewardsPerAllocatedToken; + } + + // -- Config -- + + function setIssuancePerBlock(uint256 _issuancePerBlock) external; + + function setMinimumSubgraphSignal(uint256 _minimumSubgraphSignal) external; + + // -- Denylist -- + + function setSubgraphAvailabilityOracle(address _subgraphAvailabilityOracle) external; + + function setDenied(bytes32 _subgraphDeploymentID, bool _deny) external; + + function setDeniedMany(bytes32[] calldata _subgraphDeploymentID, bool[] calldata _deny) + external; + + function isDenied(bytes32 _subgraphDeploymentID) external view returns (bool); + + // -- Getters -- + + function getNewRewardsPerSignal() external view returns (uint256); + + function getAccRewardsPerSignal() external view returns (uint256); + + function getAccRewardsForSubgraph(bytes32 _subgraphDeploymentID) + external + view + returns (uint256); + + function getAccRewardsPerAllocatedToken(bytes32 _subgraphDeploymentID) + external + view + returns (uint256, uint256); + + function getRewards(address _allocationID) external view returns (uint256); + + // -- Updates -- + + function updateAccRewardsPerSignal() external returns (uint256); + + function takeRewards(address _allocationID) external returns (uint256); + + // -- Hooks -- + + function onSubgraphSignalUpdate(bytes32 _subgraphDeploymentID) external returns (uint256); + + function onSubgraphAllocationUpdate(bytes32 _subgraphDeploymentID) external returns (uint256); +} diff --git a/packages/legacy/verified/Curation/sources/contracts/staking/IStaking.sol b/packages/legacy/verified/Curation/sources/contracts/staking/IStaking.sol new file mode 100644 index 000000000..53ec76646 --- /dev/null +++ b/packages/legacy/verified/Curation/sources/contracts/staking/IStaking.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; +pragma abicoder v2; + +import { IStakingBase } from "./IStakingBase.sol"; +import { IStakingExtension } from "./IStakingExtension.sol"; +import { Stakes } from "./libs/Stakes.sol"; +import { IStakingData } from "./IStakingData.sol"; +import { Rebates } from "./libs/Rebates.sol"; +import { IMulticall } from "../base/IMulticall.sol"; +import { IManaged } from "../governance/IManaged.sol"; + +/** + * @title Interface for the Staking contract + * @notice This is the interface that should be used when interacting with the Staking contract. + * @dev Note that Staking doesn't actually inherit this interface. This is because of + * the custom setup of the Staking contract where part of the functionality is implemented + * in a separate contract (StakingExtension) to which calls are delegated through the fallback function. + */ +interface IStaking is IStakingBase, IStakingExtension, IMulticall, IManaged { + // Nothing to see here +} diff --git a/packages/legacy/verified/Curation/sources/contracts/staking/IStakingBase.sol b/packages/legacy/verified/Curation/sources/contracts/staking/IStakingBase.sol new file mode 100644 index 000000000..b76cc488d --- /dev/null +++ b/packages/legacy/verified/Curation/sources/contracts/staking/IStakingBase.sol @@ -0,0 +1,437 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; +pragma abicoder v2; + +import { IStakingData } from "./IStakingData.sol"; + +/** + * @title Base interface for the Staking contract. + * @dev This interface includes only what's implemented in the base Staking contract. + * It does not include the L1 and L2 specific functionality. It also does not include + * several functions that are implemented in the StakingExtension contract, and are called + * via delegatecall through the fallback function. See IStaking.sol for an interface + * that includes the full functionality. + */ +interface IStakingBase is IStakingData { + /** + * @dev Emitted when `indexer` stakes `tokens` amount. + */ + event StakeDeposited(address indexed indexer, uint256 tokens); + + /** + * @dev Emitted when `indexer` unstaked and locked `tokens` amount until `until` block. + */ + event StakeLocked(address indexed indexer, uint256 tokens, uint256 until); + + /** + * @dev Emitted when `indexer` withdrew `tokens` staked. + */ + event StakeWithdrawn(address indexed indexer, uint256 tokens); + + /** + * @dev Emitted when `indexer` allocated `tokens` amount to `subgraphDeploymentID` + * during `epoch`. + * `allocationID` indexer derived address used to identify the allocation. + * `metadata` additional information related to the allocation. + */ + event AllocationCreated( + address indexed indexer, + bytes32 indexed subgraphDeploymentID, + uint256 epoch, + uint256 tokens, + address indexed allocationID, + bytes32 metadata + ); + + /** + * @dev Emitted when `indexer` collected `tokens` amount in `epoch` for `allocationID`. + * These funds are related to `subgraphDeploymentID`. + * The `from` value is the sender of the collected funds. + */ + event AllocationCollected( + address indexed indexer, + bytes32 indexed subgraphDeploymentID, + uint256 epoch, + uint256 tokens, + address indexed allocationID, + address from, + uint256 curationFees, + uint256 rebateFees + ); + + /** + * @dev Emitted when `indexer` close an allocation in `epoch` for `allocationID`. + * An amount of `tokens` get unallocated from `subgraphDeploymentID`. + * The `effectiveAllocation` are the tokens allocated from creation to closing. + * This event also emits the POI (proof of indexing) submitted by the indexer. + * `isPublic` is true if the sender was someone other than the indexer. + */ + event AllocationClosed( + address indexed indexer, + bytes32 indexed subgraphDeploymentID, + uint256 epoch, + uint256 tokens, + address indexed allocationID, + uint256 effectiveAllocation, + address sender, + bytes32 poi, + bool isPublic + ); + + /** + * @dev Emitted when `indexer` claimed a rebate on `subgraphDeploymentID` during `epoch` + * related to the `forEpoch` rebate pool. + * The rebate is for `tokens` amount and `unclaimedAllocationsCount` are left for claim + * in the rebate pool. `delegationFees` collected and sent to delegation pool. + */ + event RebateClaimed( + address indexed indexer, + bytes32 indexed subgraphDeploymentID, + address indexed allocationID, + uint256 epoch, + uint256 forEpoch, + uint256 tokens, + uint256 unclaimedAllocationsCount, + uint256 delegationFees + ); + + /** + * @dev Emitted when `indexer` update the delegation parameters for its delegation pool. + */ + event DelegationParametersUpdated( + address indexed indexer, + uint32 indexingRewardCut, + uint32 queryFeeCut, + uint32 cooldownBlocks + ); + + /** + * @dev Emitted when `caller` set `assetHolder` address as `allowed` to send funds + * to staking contract. + */ + event AssetHolderUpdate(address indexed caller, address indexed assetHolder, bool allowed); + + /** + * @dev Emitted when `indexer` set `operator` access. + */ + event SetOperator(address indexed indexer, address indexed operator, bool allowed); + + /** + * @dev Emitted when `indexer` set an address to receive rewards. + */ + event SetRewardsDestination(address indexed indexer, address indexed destination); + + /** + * @dev Emitted when `extensionImpl` was set as the address of the StakingExtension contract + * to which extended functionality is delegated. + */ + event ExtensionImplementationSet(address extensionImpl); + + /** + * @dev Possible states an allocation can be. + * States: + * - Null = indexer == address(0) + * - Active = not Null && tokens > 0 + * - Closed = Active && closedAtEpoch != 0 + * - Finalized = Closed && closedAtEpoch + channelDisputeEpochs > now() + * - Claimed = not Null && tokens == 0 + */ + enum AllocationState { + Null, + Active, + Closed, + Finalized, + Claimed + } + + /** + * @notice Initialize this contract. + * @param _controller Address of the controller that manages this contract + * @param _minimumIndexerStake Minimum amount of tokens that an indexer must stake + * @param _thawingPeriod Number of blocks that tokens get locked after unstaking + * @param _protocolPercentage Percentage of query fees that are burned as protocol fee (in PPM) + * @param _curationPercentage Percentage of query fees that are given to curators (in PPM) + * @param _channelDisputeEpochs The period in epochs that needs to pass before fees in rebate pool can be claimed + * @param _maxAllocationEpochs The maximum number of epochs that an allocation can be active + * @param _delegationUnbondingPeriod The period in epochs that tokens get locked after undelegating + * @param _delegationRatio The ratio between an indexer's own stake and the delegation they can use + * @param _rebateAlphaNumerator The numerator of the alpha factor used to calculate the rebate + * @param _rebateAlphaDenominator The denominator of the alpha factor used to calculate the rebate + * @param _extensionImpl Address of the StakingExtension implementation + */ + function initialize( + address _controller, + uint256 _minimumIndexerStake, + uint32 _thawingPeriod, + uint32 _protocolPercentage, + uint32 _curationPercentage, + uint32 _channelDisputeEpochs, + uint32 _maxAllocationEpochs, + uint32 _delegationUnbondingPeriod, + uint32 _delegationRatio, + uint32 _rebateAlphaNumerator, + uint32 _rebateAlphaDenominator, + address _extensionImpl + ) external; + + /** + * @notice Set the address of the StakingExtension implementation. + * @dev This function can only be called by the governor. + * @param _extensionImpl Address of the StakingExtension implementation + */ + function setExtensionImpl(address _extensionImpl) external; + + /** + * @notice Set the address of the counterpart (L1 or L2) staking contract. + * @dev This function can only be called by the governor. + * @param _counterpart Address of the counterpart staking contract in the other chain, without any aliasing. + */ + function setCounterpartStakingAddress(address _counterpart) external; + + /** + * @notice Set the minimum stake needed to be an Indexer + * @dev This function can only be called by the governor. + * @param _minimumIndexerStake Minimum amount of tokens that an indexer must stake + */ + function setMinimumIndexerStake(uint256 _minimumIndexerStake) external; + + /** + * @notice Set the number of blocks that tokens get locked after unstaking + * @dev This function can only be called by the governor. + * @param _thawingPeriod Number of blocks that tokens get locked after unstaking + */ + function setThawingPeriod(uint32 _thawingPeriod) external; + + /** + * @notice Set the curation percentage of query fees sent to curators. + * @dev This function can only be called by the governor. + * @param _percentage Percentage of query fees sent to curators + */ + function setCurationPercentage(uint32 _percentage) external; + + /** + * @notice Set a protocol percentage to burn when collecting query fees. + * @dev This function can only be called by the governor. + * @param _percentage Percentage of query fees to burn as protocol fee + */ + function setProtocolPercentage(uint32 _percentage) external; + + /** + * @notice Set the period in epochs that need to pass before fees in rebate pool can be claimed. + * @dev This function can only be called by the governor. + * @param _channelDisputeEpochs Period in epochs + */ + function setChannelDisputeEpochs(uint32 _channelDisputeEpochs) external; + + /** + * @notice Set the max time allowed for indexers to allocate on a subgraph + * before others are allowed to close the allocation. + * @dev This function can only be called by the governor. + * @param _maxAllocationEpochs Allocation duration limit in epochs + */ + function setMaxAllocationEpochs(uint32 _maxAllocationEpochs) external; + + /** + * @notice Set the rebate ratio (fees to allocated stake). + * @dev This function can only be called by the governor. + * @param _alphaNumerator Numerator of `alpha` in the cobb-douglas function + * @param _alphaDenominator Denominator of `alpha` in the cobb-douglas function + */ + function setRebateRatio(uint32 _alphaNumerator, uint32 _alphaDenominator) external; + + /** + * @notice Set an address as allowed asset holder. + * @dev This function can only be called by the governor. + * @param _assetHolder Address of allowed source for state channel funds + * @param _allowed True if asset holder is allowed + */ + function setAssetHolder(address _assetHolder, bool _allowed) external; + + /** + * @notice Authorize or unauthorize an address to be an operator for the caller. + * @param _operator Address to authorize or unauthorize + * @param _allowed Whether the operator is authorized or not + */ + function setOperator(address _operator, bool _allowed) external; + + /** + * @notice Deposit tokens on the indexer's stake. + * The amount staked must be over the minimumIndexerStake. + * @param _tokens Amount of tokens to stake + */ + function stake(uint256 _tokens) external; + + /** + * @notice Deposit tokens on the Indexer stake, on behalf of the Indexer. + * The amount staked must be over the minimumIndexerStake. + * @param _indexer Address of the indexer + * @param _tokens Amount of tokens to stake + */ + function stakeTo(address _indexer, uint256 _tokens) external; + + /** + * @notice Unstake tokens from the indexer stake, lock them until the thawing period expires. + * @dev NOTE: The function accepts an amount greater than the currently staked tokens. + * If that happens, it will try to unstake the max amount of tokens it can. + * The reason for this behaviour is to avoid time conditions while the transaction + * is in flight. + * @param _tokens Amount of tokens to unstake + */ + function unstake(uint256 _tokens) external; + + /** + * @notice Withdraw indexer tokens once the thawing period has passed. + */ + function withdraw() external; + + /** + * @notice Set the destination where to send rewards for an indexer. + * @param _destination Rewards destination address. If set to zero, rewards will be restaked + */ + function setRewardsDestination(address _destination) external; + + /** + * @notice Set the delegation parameters for the caller. + * @param _indexingRewardCut Percentage of indexing rewards left for the indexer + * @param _queryFeeCut Percentage of query fees left for the indexer + * @param _cooldownBlocks Period that need to pass to update delegation parameters + */ + function setDelegationParameters( + uint32 _indexingRewardCut, + uint32 _queryFeeCut, + uint32 _cooldownBlocks + ) external; + + /** + * @notice Allocate available tokens to a subgraph deployment. + * @param _subgraphDeploymentID ID of the SubgraphDeployment where tokens will be allocated + * @param _tokens Amount of tokens to allocate + * @param _allocationID The allocation identifier + * @param _metadata IPFS hash for additional information about the allocation + * @param _proof A 65-bytes Ethereum signed message of `keccak256(indexerAddress,allocationID)` + */ + function allocate( + bytes32 _subgraphDeploymentID, + uint256 _tokens, + address _allocationID, + bytes32 _metadata, + bytes calldata _proof + ) external; + + /** + * @notice Allocate available tokens to a subgraph deployment from and indexer's stake. + * The caller must be the indexer or the indexer's operator. + * @param _indexer Indexer address to allocate funds from. + * @param _subgraphDeploymentID ID of the SubgraphDeployment where tokens will be allocated + * @param _tokens Amount of tokens to allocate + * @param _allocationID The allocation identifier + * @param _metadata IPFS hash for additional information about the allocation + * @param _proof A 65-bytes Ethereum signed message of `keccak256(indexerAddress,allocationID)` + */ + function allocateFrom( + address _indexer, + bytes32 _subgraphDeploymentID, + uint256 _tokens, + address _allocationID, + bytes32 _metadata, + bytes calldata _proof + ) external; + + /** + * @notice Close an allocation and free the staked tokens. + * To be eligible for rewards a proof of indexing must be presented. + * Presenting a bad proof is subject to slashable condition. + * To opt out of rewards set _poi to 0x0 + * @param _allocationID The allocation identifier + * @param _poi Proof of indexing submitted for the allocated period + */ + function closeAllocation(address _allocationID, bytes32 _poi) external; + + /** + * @notice Collect query fees from state channels and assign them to an allocation. + * Funds received are only accepted from a valid sender. + * @dev To avoid reverting on the withdrawal from channel flow this function will: + * 1) Accept calls with zero tokens. + * 2) Accept calls after an allocation passed the dispute period, in that case, all + * the received tokens are burned. + * @param _tokens Amount of tokens to collect + * @param _allocationID Allocation where the tokens will be assigned + */ + function collect(uint256 _tokens, address _allocationID) external; + + /** + * @notice Claim tokens from the rebate pool. + * @param _allocationID Allocation from where we are claiming tokens + * @param _restake True if restake fees instead of transfer to indexer + */ + function claim(address _allocationID, bool _restake) external; + + /** + * @dev Claim tokens from the rebate pool for many allocations. + * @param _allocationID Array of allocations from where we are claiming tokens + * @param _restake True if restake fees instead of transfer to indexer + */ + function claimMany(address[] calldata _allocationID, bool _restake) external; + + /** + * @notice Return true if operator is allowed for indexer. + * @param _operator Address of the operator + * @param _indexer Address of the indexer + * @return True if operator is allowed for indexer, false otherwise + */ + function isOperator(address _operator, address _indexer) external view returns (bool); + + /** + * @notice Getter that returns if an indexer has any stake. + * @param _indexer Address of the indexer + * @return True if indexer has staked tokens + */ + function hasStake(address _indexer) external view returns (bool); + + /** + * @notice Get the total amount of tokens staked by the indexer. + * @param _indexer Address of the indexer + * @return Amount of tokens staked by the indexer + */ + function getIndexerStakedTokens(address _indexer) external view returns (uint256); + + /** + * @notice Get the total amount of tokens available to use in allocations. + * This considers the indexer stake and delegated tokens according to delegation ratio + * @param _indexer Address of the indexer + * @return Amount of tokens available to allocate including delegation + */ + function getIndexerCapacity(address _indexer) external view returns (uint256); + + /** + * @notice Return the allocation by ID. + * @param _allocationID Address used as allocation identifier + * @return Allocation data + */ + function getAllocation(address _allocationID) external view returns (Allocation memory); + + /** + * @notice Return the current state of an allocation + * @param _allocationID Allocation identifier + * @return AllocationState enum with the state of the allocation + */ + function getAllocationState(address _allocationID) external view returns (AllocationState); + + /** + * @notice Return if allocationID is used. + * @param _allocationID Address used as signer by the indexer for an allocation + * @return True if allocationID already used + */ + function isAllocation(address _allocationID) external view returns (bool); + + /** + * @notice Return the total amount of tokens allocated to subgraph. + * @param _subgraphDeploymentID Deployment ID for the subgraph + * @return Total tokens allocated to subgraph + */ + function getSubgraphAllocatedTokens(bytes32 _subgraphDeploymentID) + external + view + returns (uint256); +} diff --git a/packages/legacy/verified/Curation/sources/contracts/staking/IStakingData.sol b/packages/legacy/verified/Curation/sources/contracts/staking/IStakingData.sol new file mode 100644 index 000000000..4446077b5 --- /dev/null +++ b/packages/legacy/verified/Curation/sources/contracts/staking/IStakingData.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; + +/** + * @title Staking Data interface + * @dev This interface defines some structures used by the Staking contract. + */ +interface IStakingData { + /** + * @dev Allocate GRT tokens for the purpose of serving queries of a subgraph deployment + * An allocation is created in the allocate() function and consumed in claim() + */ + struct Allocation { + address indexer; + bytes32 subgraphDeploymentID; + uint256 tokens; // Tokens allocated to a SubgraphDeployment + uint256 createdAtEpoch; // Epoch when it was created + uint256 closedAtEpoch; // Epoch when it was closed + uint256 collectedFees; // Collected fees for the allocation + uint256 effectiveAllocation; // Effective allocation when closed + uint256 accRewardsPerAllocatedToken; // Snapshot used for reward calc + } + + // -- Delegation Data -- + + /** + * @dev Delegation pool information. One per indexer. + */ + struct DelegationPool { + uint32 cooldownBlocks; // Blocks to wait before updating parameters + uint32 indexingRewardCut; // in PPM + uint32 queryFeeCut; // in PPM + uint256 updatedAtBlock; // Block when the pool was last updated + uint256 tokens; // Total tokens as pool reserves + uint256 shares; // Total shares minted in the pool + mapping(address => Delegation) delegators; // Mapping of delegator => Delegation + } + + /** + * @dev Individual delegation data of a delegator in a pool. + */ + struct Delegation { + uint256 shares; // Shares owned by a delegator in the pool + uint256 tokensLocked; // Tokens locked for undelegation + uint256 tokensLockedUntil; // Block when locked tokens can be withdrawn + } +} diff --git a/packages/legacy/verified/Curation/sources/contracts/staking/IStakingExtension.sol b/packages/legacy/verified/Curation/sources/contracts/staking/IStakingExtension.sol new file mode 100644 index 000000000..e63f5c035 --- /dev/null +++ b/packages/legacy/verified/Curation/sources/contracts/staking/IStakingExtension.sol @@ -0,0 +1,351 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; +pragma abicoder v2; + +import { IStakingData } from "./IStakingData.sol"; +import { Rebates } from "./libs/Rebates.sol"; +import { Stakes } from "./libs/Stakes.sol"; + +/** + * @title Interface for the StakingExtension contract + * @dev This interface defines the events and functions implemented + * in the StakingExtension contract, which is used to extend the functionality + * of the Staking contract while keeping it within the 24kB mainnet size limit. + * In particular, this interface includes delegation functions and various storage + * getters. + */ +interface IStakingExtension is IStakingData { + /** + * @dev DelegationPool struct as returned by delegationPools(), since + * the original DelegationPool in IStakingData.sol contains a nested mapping. + */ + struct DelegationPoolReturn { + uint32 cooldownBlocks; // Blocks to wait before updating parameters + uint32 indexingRewardCut; // in PPM + uint32 queryFeeCut; // in PPM + uint256 updatedAtBlock; // Block when the pool was last updated + uint256 tokens; // Total tokens as pool reserves + uint256 shares; // Total shares minted in the pool + } + + /** + * @dev Emitted when `delegator` delegated `tokens` to the `indexer`, the delegator + * gets `shares` for the delegation pool proportionally to the tokens staked. + */ + event StakeDelegated( + address indexed indexer, + address indexed delegator, + uint256 tokens, + uint256 shares + ); + + /** + * @dev Emitted when `delegator` undelegated `tokens` from `indexer`. + * Tokens get locked for withdrawal after a period of time. + */ + event StakeDelegatedLocked( + address indexed indexer, + address indexed delegator, + uint256 tokens, + uint256 shares, + uint256 until + ); + + /** + * @dev Emitted when `delegator` withdrew delegated `tokens` from `indexer`. + */ + event StakeDelegatedWithdrawn( + address indexed indexer, + address indexed delegator, + uint256 tokens + ); + + /** + * @dev Emitted when `indexer` was slashed for a total of `tokens` amount. + * Tracks `reward` amount of tokens given to `beneficiary`. + */ + event StakeSlashed( + address indexed indexer, + uint256 tokens, + uint256 reward, + address beneficiary + ); + + /** + * @dev Emitted when `caller` set `slasher` address as `allowed` to slash stakes. + */ + event SlasherUpdate(address indexed caller, address indexed slasher, bool allowed); + + /** + * @notice Set the delegation ratio. + * If set to 10 it means the indexer can use up to 10x the indexer staked amount + * from their delegated tokens + * @dev This function is only callable by the governor + * @param _delegationRatio Delegation capacity multiplier + */ + function setDelegationRatio(uint32 _delegationRatio) external; + + /** + * @notice Set the minimum time in blocks an indexer needs to wait to change delegation parameters. + * Indexers can set a custom amount time for their own cooldown, but it must be greater than this. + * @dev This function is only callable by the governor + * @param _blocks Number of blocks to set the delegation parameters cooldown period + */ + function setDelegationParametersCooldown(uint32 _blocks) external; + + /** + * @notice Set the time, in epochs, a Delegator needs to wait to withdraw tokens after undelegating. + * @dev This function is only callable by the governor + * @param _delegationUnbondingPeriod Period in epochs to wait for token withdrawals after undelegating + */ + function setDelegationUnbondingPeriod(uint32 _delegationUnbondingPeriod) external; + + /** + * @notice Set a delegation tax percentage to burn when delegated funds are deposited. + * @dev This function is only callable by the governor + * @param _percentage Percentage of delegated tokens to burn as delegation tax, expressed in parts per million + */ + function setDelegationTaxPercentage(uint32 _percentage) external; + + /** + * @notice Set or unset an address as allowed slasher. + * @dev This function can only be called by the governor. + * @param _slasher Address of the party allowed to slash indexers + * @param _allowed True if slasher is allowed + */ + function setSlasher(address _slasher, bool _allowed) external; + + /** + * @notice Delegate tokens to an indexer. + * @param _indexer Address of the indexer to which tokens are delegated + * @param _tokens Amount of tokens to delegate + * @return Amount of shares issued from the delegation pool + */ + function delegate(address _indexer, uint256 _tokens) external returns (uint256); + + /** + * @notice Undelegate tokens from an indexer. Tokens will be locked for the unbonding period. + * @param _indexer Address of the indexer to which tokens had been delegated + * @param _shares Amount of shares to return and undelegate tokens + * @return Amount of tokens returned for the shares of the delegation pool + */ + function undelegate(address _indexer, uint256 _shares) external returns (uint256); + + /** + * @notice Withdraw undelegated tokens once the unbonding period has passed, and optionally + * re-delegate to a new indexer. + * @param _indexer Withdraw available tokens delegated to indexer + * @param _newIndexer Re-delegate to indexer address if non-zero, withdraw if zero address + */ + function withdrawDelegated(address _indexer, address _newIndexer) external returns (uint256); + + /** + * @notice Slash the indexer stake. Delegated tokens are not subject to slashing. + * @dev Can only be called by the slasher role. + * @param _indexer Address of indexer to slash + * @param _tokens Amount of tokens to slash from the indexer stake + * @param _reward Amount of reward tokens to send to a beneficiary + * @param _beneficiary Address of a beneficiary to receive a reward for the slashing + */ + function slash( + address _indexer, + uint256 _tokens, + uint256 _reward, + address _beneficiary + ) external; + + /** + * @notice Return the delegation from a delegator to an indexer. + * @param _indexer Address of the indexer where funds have been delegated + * @param _delegator Address of the delegator + * @return Delegation data + */ + function getDelegation(address _indexer, address _delegator) + external + view + returns (Delegation memory); + + /** + * @notice Return whether the delegator has delegated to the indexer. + * @param _indexer Address of the indexer where funds have been delegated + * @param _delegator Address of the delegator + * @return True if delegator has tokens delegated to the indexer + */ + function isDelegator(address _indexer, address _delegator) external view returns (bool); + + /** + * @notice Returns amount of delegated tokens ready to be withdrawn after unbonding period. + * @param _delegation Delegation of tokens from delegator to indexer + * @return Amount of tokens to withdraw + */ + function getWithdraweableDelegatedTokens(Delegation memory _delegation) + external + view + returns (uint256); + + /** + * @notice Getter for the delegationRatio, i.e. the delegation capacity multiplier: + * If delegation ratio is 100, and an Indexer has staked 5 GRT, + * then they can use up to 500 GRT from the delegated stake + * @return Delegation ratio + */ + function delegationRatio() external view returns (uint32); + + /** + * @notice Getter for delegationParametersCooldown: + * Minimum time in blocks an indexer needs to wait to change delegation parameters + * @return Delegation parameters cooldown in blocks + */ + function delegationParametersCooldown() external view returns (uint32); + + /** + * @notice Getter for delegationUnbondingPeriod: + * Time in epochs a delegator needs to wait to withdraw delegated stake + * @return Delegation unbonding period in epochs + */ + function delegationUnbondingPeriod() external view returns (uint32); + + /** + * @notice Getter for delegationTaxPercentage: + * Percentage of tokens to tax a delegation deposit, expressed in parts per million + * @return Delegation tax percentage in parts per million + */ + function delegationTaxPercentage() external view returns (uint32); + + /** + * @notice Getter for delegationPools[_indexer]: + * gets the delegation pool structure for a particular indexer. + * @param _indexer Address of the indexer for which to query the delegation pool + * @return Delegation pool as a DelegationPoolReturn struct + */ + function delegationPools(address _indexer) external view returns (DelegationPoolReturn memory); + + /** + * @notice Getter for operatorAuth[_indexer][_maybeOperator]: + * returns true if the operator is authorized to operate on behalf of the indexer. + * @param _indexer The indexer address for which to query authorization + * @param _maybeOperator The address that may or may not be an operator + * @return True if the operator is authorized to operate on behalf of the indexer + */ + function operatorAuth(address _indexer, address _maybeOperator) external view returns (bool); + + /** + * @notice Getter for rewardsDestination[_indexer]: + * returns the address where the indexer's rewards are sent. + * @param _indexer The indexer address for which to query the rewards destination + * @return The address where the indexer's rewards are sent, zero if none is set in which case rewards are re-staked + */ + function rewardsDestination(address _indexer) external view returns (address); + + /** + * @notice Getter for assetHolders[_maybeAssetHolder]: + * returns true if the address is an asset holder, i.e. an entity that can collect + * query fees into the Staking contract. + * @param _maybeAssetHolder The address that may or may not be an asset holder + * @return True if the address is an asset holder + */ + function assetHolders(address _maybeAssetHolder) external view returns (bool); + + /** + * @notice Getter for subgraphAllocations[_subgraphDeploymentId]: + * returns the amount of tokens allocated to a subgraph deployment. + * @param _subgraphDeploymentId The subgraph deployment for which to query the allocations + * @return The amount of tokens allocated to the subgraph deployment + */ + function subgraphAllocations(bytes32 _subgraphDeploymentId) external view returns (uint256); + + /** + * @notice Getter for rebates[_epoch]: + * gets the rebate pool for a particular epoch. + * @param _epoch Epoch for which to query the rebate pool + * @return Rebate pool for the specified epoch, as a Rebates.Pool struct + */ + function rebates(uint256 _epoch) external view returns (Rebates.Pool memory); + + /** + * @notice Getter for slashers[_maybeSlasher]: + * returns true if the address is a slasher, i.e. an entity that can slash indexers + * @param _maybeSlasher Address for which to check the slasher role + * @return True if the address is a slasher + */ + function slashers(address _maybeSlasher) external view returns (bool); + + /** + * @notice Getter for minimumIndexerStake: the minimum + * amount of GRT that an indexer needs to stake. + * @return Minimum indexer stake in GRT + */ + function minimumIndexerStake() external view returns (uint256); + + /** + * @notice Getter for thawingPeriod: the time in blocks an + * indexer needs to wait to unstake tokens. + * @return Thawing period in blocks + */ + function thawingPeriod() external view returns (uint32); + + /** + * @notice Getter for curationPercentage: the percentage of + * query fees that are distributed to curators. + * @return Curation percentage in parts per million + */ + function curationPercentage() external view returns (uint32); + + /** + * @notice Getter for protocolPercentage: the percentage of + * query fees that are burned as protocol fees. + * @return Protocol percentage in parts per million + */ + function protocolPercentage() external view returns (uint32); + + /** + * @notice Getter for channelDisputeEpochs: the time in epochs + * between closing an allocation and the moment it becomes finalized so + * query fees can be claimed. + * @return Channel dispute period in epochs + */ + function channelDisputeEpochs() external view returns (uint32); + + /** + * @notice Getter for maxAllocationEpochs: the maximum time in epochs + * that an allocation can be open before anyone is allowed to close it. This + * also caps the effective allocation when sending the allocation's query fees + * to the rebate pool. + * @return Maximum allocation period in epochs + */ + function maxAllocationEpochs() external view returns (uint32); + + /** + * @notice Getter for alphaNumerator: the numerator of the Cobb-Douglas + * rebate ratio. + * @return Rebate ratio numerator + */ + function alphaNumerator() external view returns (uint32); + + /** + * @notice Getter for alphaDenominator: the denominator of the Cobb-Douglas + * rebate ratio. + * @return Rebate ratio denominator + */ + function alphaDenominator() external view returns (uint32); + + /** + * @notice Getter for stakes[_indexer]: + * gets the stake information for an indexer as a Stakes.Indexer struct. + * @param _indexer Indexer address for which to query the stake information + * @return Stake information for the specified indexer, as a Stakes.Indexer struct + */ + function stakes(address _indexer) external view returns (Stakes.Indexer memory); + + /** + * @notice Getter for allocations[_allocationID]: + * gets an allocation's information as an IStakingData.Allocation struct. + * @param _allocationID Allocation ID for which to query the allocation information + * @return The specified allocation, as an IStakingData.Allocation struct + */ + function allocations(address _allocationID) + external + view + returns (IStakingData.Allocation memory); +} diff --git a/packages/legacy/verified/Curation/sources/contracts/staking/libs/Cobbs.sol b/packages/legacy/verified/Curation/sources/contracts/staking/libs/Cobbs.sol new file mode 100644 index 000000000..f8effed02 --- /dev/null +++ b/packages/legacy/verified/Curation/sources/contracts/staking/libs/Cobbs.sol @@ -0,0 +1,92 @@ +/* + + Copyright 2019 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.7.6; +pragma abicoder v2; + +import "./LibFixedMath.sol"; + +library LibCobbDouglas { + /// @dev The cobb-douglas function used to compute fee-based rewards for + /// staking pools in a given epoch. This function does not perform + /// bounds checking on the inputs, but the following conditions + /// need to be true: + /// 0 <= fees / totalFees <= 1 + /// 0 <= stake / totalStake <= 1 + /// 0 <= alphaNumerator / alphaDenominator <= 1 + /// @param totalRewards collected over an epoch. + /// @param fees Fees attributed to the the staking pool. + /// @param totalFees Total fees collected across all pools that earned rewards. + /// @param stake Stake attributed to the staking pool. + /// @param totalStake Total stake across all pools that earned rewards. + /// @param alphaNumerator Numerator of `alpha` in the cobb-douglas function. + /// @param alphaDenominator Denominator of `alpha` in the cobb-douglas + /// function. + /// @return rewards Rewards owed to the staking pool. + function cobbDouglas( + uint256 totalRewards, + uint256 fees, + uint256 totalFees, + uint256 stake, + uint256 totalStake, + uint32 alphaNumerator, + uint32 alphaDenominator + ) public pure returns (uint256 rewards) { + int256 feeRatio = LibFixedMath.toFixed(fees, totalFees); + int256 stakeRatio = LibFixedMath.toFixed(stake, totalStake); + if (feeRatio == 0 || stakeRatio == 0) { + return rewards = 0; + } + // The cobb-doublas function has the form: + // `totalRewards * feeRatio ^ alpha * stakeRatio ^ (1-alpha)` + // This is equivalent to: + // `totalRewards * stakeRatio * e^(alpha * (ln(feeRatio / stakeRatio)))` + // However, because `ln(x)` has the domain of `0 < x < 1` + // and `exp(x)` has the domain of `x < 0`, + // and fixed-point math easily overflows with multiplication, + // we will choose the following if `stakeRatio > feeRatio`: + // `totalRewards * stakeRatio / e^(alpha * (ln(stakeRatio / feeRatio)))` + + // Compute + // `e^(alpha * ln(feeRatio/stakeRatio))` if feeRatio <= stakeRatio + // or + // `e^(alpa * ln(stakeRatio/feeRatio))` if feeRatio > stakeRatio + int256 n = feeRatio <= stakeRatio + ? LibFixedMath.div(feeRatio, stakeRatio) + : LibFixedMath.div(stakeRatio, feeRatio); + n = LibFixedMath.exp( + LibFixedMath.mulDiv( + LibFixedMath.ln(n), + int256(alphaNumerator), + int256(alphaDenominator) + ) + ); + // Compute + // `totalRewards * n` if feeRatio <= stakeRatio + // or + // `totalRewards / n` if stakeRatio > feeRatio + // depending on the choice we made earlier. + n = feeRatio <= stakeRatio + ? LibFixedMath.mul(stakeRatio, n) + : LibFixedMath.div(stakeRatio, n); + // Multiply the above with totalRewards. + rewards = LibFixedMath.uintMul(n, totalRewards); + } +} diff --git a/packages/legacy/verified/Curation/sources/contracts/staking/libs/LibFixedMath.sol b/packages/legacy/verified/Curation/sources/contracts/staking/libs/LibFixedMath.sol new file mode 100644 index 000000000..0a830848a --- /dev/null +++ b/packages/legacy/verified/Curation/sources/contracts/staking/libs/LibFixedMath.sol @@ -0,0 +1,408 @@ +/* + + Copyright 2017 Bprotocol Foundation, 2019 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.7.6; + +// solhint-disable indent +/// @dev Signed, fixed-point, 127-bit precision math library. +library LibFixedMath { + // 1 + int256 private constant FIXED_1 = + int256(0x0000000000000000000000000000000080000000000000000000000000000000); + // 2**255 + int256 private constant MIN_FIXED_VAL = + int256(0x8000000000000000000000000000000000000000000000000000000000000000); + // 1^2 (in fixed-point) + int256 private constant FIXED_1_SQUARED = + int256(0x4000000000000000000000000000000000000000000000000000000000000000); + // 1 + int256 private constant LN_MAX_VAL = FIXED_1; + // e ^ -63.875 + int256 private constant LN_MIN_VAL = + int256(0x0000000000000000000000000000000000000000000000000000000733048c5a); + // 0 + int256 private constant EXP_MAX_VAL = 0; + // -63.875 + int256 private constant EXP_MIN_VAL = + -int256(0x0000000000000000000000000000001ff0000000000000000000000000000000); + + /// @dev Get one as a fixed-point number. + function one() internal pure returns (int256 f) { + f = FIXED_1; + } + + /// @dev Returns the addition of two fixed point numbers, reverting on overflow. + function add(int256 a, int256 b) internal pure returns (int256 c) { + c = _add(a, b); + } + + /// @dev Returns the addition of two fixed point numbers, reverting on overflow. + function sub(int256 a, int256 b) internal pure returns (int256 c) { + if (b == MIN_FIXED_VAL) { + revert("out-of-bounds"); + } + c = _add(a, -b); + } + + /// @dev Returns the multiplication of two fixed point numbers, reverting on overflow. + function mul(int256 a, int256 b) internal pure returns (int256 c) { + c = _mul(a, b) / FIXED_1; + } + + /// @dev Returns the division of two fixed point numbers. + function div(int256 a, int256 b) internal pure returns (int256 c) { + c = _div(_mul(a, FIXED_1), b); + } + + /// @dev Performs (a * n) / d, without scaling for precision. + function mulDiv( + int256 a, + int256 n, + int256 d + ) internal pure returns (int256 c) { + c = _div(_mul(a, n), d); + } + + /// @dev Returns the unsigned integer result of multiplying a fixed-point + /// number with an integer, reverting if the multiplication overflows. + /// Negative results are clamped to zero. + function uintMul(int256 f, uint256 u) internal pure returns (uint256) { + if (int256(u) < int256(0)) { + revert("out-of-bounds"); + } + int256 c = _mul(f, int256(u)); + if (c <= 0) { + return 0; + } + return uint256(uint256(c) >> 127); + } + + /// @dev Returns the absolute value of a fixed point number. + function abs(int256 f) internal pure returns (int256 c) { + if (f == MIN_FIXED_VAL) { + revert("out-of-bounds"); + } + if (f >= 0) { + c = f; + } else { + c = -f; + } + } + + /// @dev Returns 1 / `x`, where `x` is a fixed-point number. + function invert(int256 f) internal pure returns (int256 c) { + c = _div(FIXED_1_SQUARED, f); + } + + /// @dev Convert signed `n` / 1 to a fixed-point number. + function toFixed(int256 n) internal pure returns (int256 f) { + f = _mul(n, FIXED_1); + } + + /// @dev Convert signed `n` / `d` to a fixed-point number. + function toFixed(int256 n, int256 d) internal pure returns (int256 f) { + f = _div(_mul(n, FIXED_1), d); + } + + /// @dev Convert unsigned `n` / 1 to a fixed-point number. + /// Reverts if `n` is too large to fit in a fixed-point number. + function toFixed(uint256 n) internal pure returns (int256 f) { + if (int256(n) < int256(0)) { + revert("out-of-bounds"); + } + f = _mul(int256(n), FIXED_1); + } + + /// @dev Convert unsigned `n` / `d` to a fixed-point number. + /// Reverts if `n` / `d` is too large to fit in a fixed-point number. + function toFixed(uint256 n, uint256 d) internal pure returns (int256 f) { + if (int256(n) < int256(0)) { + revert("out-of-bounds"); + } + if (int256(d) < int256(0)) { + revert("out-of-bounds"); + } + f = _div(_mul(int256(n), FIXED_1), int256(d)); + } + + /// @dev Convert a fixed-point number to an integer. + function toInteger(int256 f) internal pure returns (int256 n) { + return f / FIXED_1; + } + + /// @dev Get the natural logarithm of a fixed-point number 0 < `x` <= LN_MAX_VAL + function ln(int256 x) internal pure returns (int256 r) { + if (x > LN_MAX_VAL) { + revert("out-of-bounds"); + } + if (x <= 0) { + revert("too-small"); + } + if (x == FIXED_1) { + return 0; + } + if (x <= LN_MIN_VAL) { + return EXP_MIN_VAL; + } + + int256 y; + int256 z; + int256 w; + + // Rewrite the input as a quotient of negative natural exponents and a single residual q, such that 1 < q < 2 + // For example: log(0.3) = log(e^-1 * e^-0.25 * 1.0471028872385522) + // = 1 - 0.25 - log(1 + 0.0471028872385522) + // e ^ -32 + if (x <= int256(0x00000000000000000000000000000000000000000001c8464f76164760000000)) { + r -= int256(0x0000000000000000000000000000001000000000000000000000000000000000); // - 32 + x = + (x * FIXED_1) / + int256(0x00000000000000000000000000000000000000000001c8464f76164760000000); // / e ^ -32 + } + // e ^ -16 + if (x <= int256(0x00000000000000000000000000000000000000f1aaddd7742e90000000000000)) { + r -= int256(0x0000000000000000000000000000000800000000000000000000000000000000); // - 16 + x = + (x * FIXED_1) / + int256(0x00000000000000000000000000000000000000f1aaddd7742e90000000000000); // / e ^ -16 + } + // e ^ -8 + if (x <= int256(0x00000000000000000000000000000000000afe10820813d78000000000000000)) { + r -= int256(0x0000000000000000000000000000000400000000000000000000000000000000); // - 8 + x = + (x * FIXED_1) / + int256(0x00000000000000000000000000000000000afe10820813d78000000000000000); // / e ^ -8 + } + // e ^ -4 + if (x <= int256(0x0000000000000000000000000000000002582ab704279ec00000000000000000)) { + r -= int256(0x0000000000000000000000000000000200000000000000000000000000000000); // - 4 + x = + (x * FIXED_1) / + int256(0x0000000000000000000000000000000002582ab704279ec00000000000000000); // / e ^ -4 + } + // e ^ -2 + if (x <= int256(0x000000000000000000000000000000001152aaa3bf81cc000000000000000000)) { + r -= int256(0x0000000000000000000000000000000100000000000000000000000000000000); // - 2 + x = + (x * FIXED_1) / + int256(0x000000000000000000000000000000001152aaa3bf81cc000000000000000000); // / e ^ -2 + } + // e ^ -1 + if (x <= int256(0x000000000000000000000000000000002f16ac6c59de70000000000000000000)) { + r -= int256(0x0000000000000000000000000000000080000000000000000000000000000000); // - 1 + x = + (x * FIXED_1) / + int256(0x000000000000000000000000000000002f16ac6c59de70000000000000000000); // / e ^ -1 + } + // e ^ -0.5 + if (x <= int256(0x000000000000000000000000000000004da2cbf1be5828000000000000000000)) { + r -= int256(0x0000000000000000000000000000000040000000000000000000000000000000); // - 0.5 + x = + (x * FIXED_1) / + int256(0x000000000000000000000000000000004da2cbf1be5828000000000000000000); // / e ^ -0.5 + } + // e ^ -0.25 + if (x <= int256(0x0000000000000000000000000000000063afbe7ab2082c000000000000000000)) { + r -= int256(0x0000000000000000000000000000000020000000000000000000000000000000); // - 0.25 + x = + (x * FIXED_1) / + int256(0x0000000000000000000000000000000063afbe7ab2082c000000000000000000); // / e ^ -0.25 + } + // e ^ -0.125 + if (x <= int256(0x0000000000000000000000000000000070f5a893b608861e1f58934f97aea57d)) { + r -= int256(0x0000000000000000000000000000000010000000000000000000000000000000); // - 0.125 + x = + (x * FIXED_1) / + int256(0x0000000000000000000000000000000070f5a893b608861e1f58934f97aea57d); // / e ^ -0.125 + } + // `x` is now our residual in the range of 1 <= x <= 2 (or close enough). + + // Add the taylor series for log(1 + z), where z = x - 1 + z = y = x - FIXED_1; + w = (y * y) / FIXED_1; + r += (z * (0x100000000000000000000000000000000 - y)) / 0x100000000000000000000000000000000; + z = (z * w) / FIXED_1; // add y^01 / 01 - y^02 / 02 + r += (z * (0x0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - y)) / 0x200000000000000000000000000000000; + z = (z * w) / FIXED_1; // add y^03 / 03 - y^04 / 04 + r += (z * (0x099999999999999999999999999999999 - y)) / 0x300000000000000000000000000000000; + z = (z * w) / FIXED_1; // add y^05 / 05 - y^06 / 06 + r += (z * (0x092492492492492492492492492492492 - y)) / 0x400000000000000000000000000000000; + z = (z * w) / FIXED_1; // add y^07 / 07 - y^08 / 08 + r += (z * (0x08e38e38e38e38e38e38e38e38e38e38e - y)) / 0x500000000000000000000000000000000; + z = (z * w) / FIXED_1; // add y^09 / 09 - y^10 / 10 + r += (z * (0x08ba2e8ba2e8ba2e8ba2e8ba2e8ba2e8b - y)) / 0x600000000000000000000000000000000; + z = (z * w) / FIXED_1; // add y^11 / 11 - y^12 / 12 + r += (z * (0x089d89d89d89d89d89d89d89d89d89d89 - y)) / 0x700000000000000000000000000000000; + z = (z * w) / FIXED_1; // add y^13 / 13 - y^14 / 14 + r += (z * (0x088888888888888888888888888888888 - y)) / 0x800000000000000000000000000000000; // add y^15 / 15 - y^16 / 16 + } + + /// @dev Compute the natural exponent for a fixed-point number EXP_MIN_VAL <= `x` <= 1 + function exp(int256 x) internal pure returns (int256 r) { + if (x < EXP_MIN_VAL) { + // Saturate to zero below EXP_MIN_VAL. + return 0; + } + if (x == 0) { + return FIXED_1; + } + if (x > EXP_MAX_VAL) { + revert("out-of-bounds"); + } + + // Rewrite the input as a product of natural exponents and a + // single residual q, where q is a number of small magnitude. + // For example: e^-34.419 = e^(-32 - 2 - 0.25 - 0.125 - 0.044) + // = e^-32 * e^-2 * e^-0.25 * e^-0.125 * e^-0.044 + // -> q = -0.044 + + // Multiply with the taylor series for e^q + int256 y; + int256 z; + // q = x % 0.125 (the residual) + z = y = x % 0x0000000000000000000000000000000010000000000000000000000000000000; + z = (z * y) / FIXED_1; + r += z * 0x10e1b3be415a0000; // add y^02 * (20! / 02!) + z = (z * y) / FIXED_1; + r += z * 0x05a0913f6b1e0000; // add y^03 * (20! / 03!) + z = (z * y) / FIXED_1; + r += z * 0x0168244fdac78000; // add y^04 * (20! / 04!) + z = (z * y) / FIXED_1; + r += z * 0x004807432bc18000; // add y^05 * (20! / 05!) + z = (z * y) / FIXED_1; + r += z * 0x000c0135dca04000; // add y^06 * (20! / 06!) + z = (z * y) / FIXED_1; + r += z * 0x0001b707b1cdc000; // add y^07 * (20! / 07!) + z = (z * y) / FIXED_1; + r += z * 0x000036e0f639b800; // add y^08 * (20! / 08!) + z = (z * y) / FIXED_1; + r += z * 0x00000618fee9f800; // add y^09 * (20! / 09!) + z = (z * y) / FIXED_1; + r += z * 0x0000009c197dcc00; // add y^10 * (20! / 10!) + z = (z * y) / FIXED_1; + r += z * 0x0000000e30dce400; // add y^11 * (20! / 11!) + z = (z * y) / FIXED_1; + r += z * 0x000000012ebd1300; // add y^12 * (20! / 12!) + z = (z * y) / FIXED_1; + r += z * 0x0000000017499f00; // add y^13 * (20! / 13!) + z = (z * y) / FIXED_1; + r += z * 0x0000000001a9d480; // add y^14 * (20! / 14!) + z = (z * y) / FIXED_1; + r += z * 0x00000000001c6380; // add y^15 * (20! / 15!) + z = (z * y) / FIXED_1; + r += z * 0x000000000001c638; // add y^16 * (20! / 16!) + z = (z * y) / FIXED_1; + r += z * 0x0000000000001ab8; // add y^17 * (20! / 17!) + z = (z * y) / FIXED_1; + r += z * 0x000000000000017c; // add y^18 * (20! / 18!) + z = (z * y) / FIXED_1; + r += z * 0x0000000000000014; // add y^19 * (20! / 19!) + z = (z * y) / FIXED_1; + r += z * 0x0000000000000001; // add y^20 * (20! / 20!) + r = r / 0x21c3677c82b40000 + y + FIXED_1; // divide by 20! and then add y^1 / 1! + y^0 / 0! + + // Multiply with the non-residual terms. + x = -x; + // e ^ -32 + if ((x & int256(0x0000000000000000000000000000001000000000000000000000000000000000)) != 0) { + r = + (r * int256(0x00000000000000000000000000000000000000f1aaddd7742e56d32fb9f99744)) / + int256(0x0000000000000000000000000043cbaf42a000812488fc5c220ad7b97bf6e99e); // * e ^ -32 + } + // e ^ -16 + if ((x & int256(0x0000000000000000000000000000000800000000000000000000000000000000)) != 0) { + r = + (r * int256(0x00000000000000000000000000000000000afe10820813d65dfe6a33c07f738f)) / + int256(0x000000000000000000000000000005d27a9f51c31b7c2f8038212a0574779991); // * e ^ -16 + } + // e ^ -8 + if ((x & int256(0x0000000000000000000000000000000400000000000000000000000000000000)) != 0) { + r = + (r * int256(0x0000000000000000000000000000000002582ab704279e8efd15e0265855c47a)) / + int256(0x0000000000000000000000000000001b4c902e273a58678d6d3bfdb93db96d02); // * e ^ -8 + } + // e ^ -4 + if ((x & int256(0x0000000000000000000000000000000200000000000000000000000000000000)) != 0) { + r = + (r * int256(0x000000000000000000000000000000001152aaa3bf81cb9fdb76eae12d029571)) / + int256(0x00000000000000000000000000000003b1cc971a9bb5b9867477440d6d157750); // * e ^ -4 + } + // e ^ -2 + if ((x & int256(0x0000000000000000000000000000000100000000000000000000000000000000)) != 0) { + r = + (r * int256(0x000000000000000000000000000000002f16ac6c59de6f8d5d6f63c1482a7c86)) / + int256(0x000000000000000000000000000000015bf0a8b1457695355fb8ac404e7a79e3); // * e ^ -2 + } + // e ^ -1 + if ((x & int256(0x0000000000000000000000000000000080000000000000000000000000000000)) != 0) { + r = + (r * int256(0x000000000000000000000000000000004da2cbf1be5827f9eb3ad1aa9866ebb3)) / + int256(0x00000000000000000000000000000000d3094c70f034de4b96ff7d5b6f99fcd8); // * e ^ -1 + } + // e ^ -0.5 + if ((x & int256(0x0000000000000000000000000000000040000000000000000000000000000000)) != 0) { + r = + (r * int256(0x0000000000000000000000000000000063afbe7ab2082ba1a0ae5e4eb1b479dc)) / + int256(0x00000000000000000000000000000000a45af1e1f40c333b3de1db4dd55f29a7); // * e ^ -0.5 + } + // e ^ -0.25 + if ((x & int256(0x0000000000000000000000000000000020000000000000000000000000000000)) != 0) { + r = + (r * int256(0x0000000000000000000000000000000070f5a893b608861e1f58934f97aea57d)) / + int256(0x00000000000000000000000000000000910b022db7ae67ce76b441c27035c6a1); // * e ^ -0.25 + } + // e ^ -0.125 + if ((x & int256(0x0000000000000000000000000000000010000000000000000000000000000000)) != 0) { + r = + (r * int256(0x00000000000000000000000000000000783eafef1c0a8f3978c7f81824d62ebf)) / + int256(0x0000000000000000000000000000000088415abbe9a76bead8d00cf112e4d4a8); // * e ^ -0.125 + } + } + + /// @dev Returns the multiplication two numbers, reverting on overflow. + function _mul(int256 a, int256 b) private pure returns (int256 c) { + if (a == 0) { + return 0; + } + c = a * b; + if (c / a != b || c / b != a) { + revert("overflow"); + } + } + + /// @dev Returns the division of two numbers, reverting on division by zero. + function _div(int256 a, int256 b) private pure returns (int256 c) { + if (b == 0) { + revert("overflow"); + } + if (a == MIN_FIXED_VAL && b == -1) { + revert("overflow"); + } + c = a / b; + } + + /// @dev Adds two numbers, reverting on overflow. + function _add(int256 a, int256 b) private pure returns (int256 c) { + c = a + b; + if ((a < 0 && b < 0 && c > a) || (a > 0 && b > 0 && c < a)) { + revert("overflow"); + } + } +} diff --git a/packages/legacy/verified/Curation/sources/contracts/staking/libs/MathUtils.sol b/packages/legacy/verified/Curation/sources/contracts/staking/libs/MathUtils.sol new file mode 100644 index 000000000..4194b6da8 --- /dev/null +++ b/packages/legacy/verified/Curation/sources/contracts/staking/libs/MathUtils.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import "@openzeppelin/contracts/math/SafeMath.sol"; + +/** + * @title MathUtils Library + * @notice A collection of functions to perform math operations + */ +library MathUtils { + using SafeMath for uint256; + + /** + * @dev Calculates the weighted average of two values pondering each of these + * values based on configured weights. The contribution of each value N is + * weightN/(weightA + weightB). + * @param valueA The amount for value A + * @param weightA The weight to use for value A + * @param valueB The amount for value B + * @param weightB The weight to use for value B + */ + function weightedAverage( + uint256 valueA, + uint256 weightA, + uint256 valueB, + uint256 weightB + ) internal pure returns (uint256) { + return valueA.mul(weightA).add(valueB.mul(weightB)).div(weightA.add(weightB)); + } + + /** + * @dev Returns the minimum of two numbers. + */ + function min(uint256 x, uint256 y) internal pure returns (uint256) { + return x <= y ? x : y; + } + + /** + * @dev Returns the difference between two numbers or zero if negative. + */ + function diffOrZero(uint256 x, uint256 y) internal pure returns (uint256) { + return (x > y) ? x.sub(y) : 0; + } +} diff --git a/packages/legacy/verified/Curation/sources/contracts/staking/libs/Rebates.sol b/packages/legacy/verified/Curation/sources/contracts/staking/libs/Rebates.sol new file mode 100644 index 000000000..9323a9747 --- /dev/null +++ b/packages/legacy/verified/Curation/sources/contracts/staking/libs/Rebates.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; +pragma abicoder v2; + +import "@openzeppelin/contracts/math/SafeMath.sol"; + +import "./Cobbs.sol"; + +/** + * @title A collection of data structures and functions to manage Rebates + * Used for low-level state changes, require() conditions should be evaluated + * at the caller function scope. + */ +library Rebates { + using SafeMath for uint256; + using Rebates for Rebates.Pool; + + // Tracks stats for allocations closed on a particular epoch for claiming + // The pool also keeps tracks of total query fees collected and stake used + // Only one rebate pool exists per epoch + struct Pool { + uint256 fees; // total query fees in the rebate pool + uint256 effectiveAllocatedStake; // total effective allocation of stake + uint256 claimedRewards; // total claimed rewards from the rebate pool + uint32 unclaimedAllocationsCount; // amount of unclaimed allocations + uint32 alphaNumerator; // numerator of `alpha` in the cobb-douglas function + uint32 alphaDenominator; // denominator of `alpha` in the cobb-douglas function + } + + /** + * @dev Init the rebate pool with the rebate ratio. + * @param _alphaNumerator Numerator of `alpha` in the cobb-douglas function + * @param _alphaDenominator Denominator of `alpha` in the cobb-douglas function + */ + function init( + Rebates.Pool storage pool, + uint32 _alphaNumerator, + uint32 _alphaDenominator + ) internal { + pool.alphaNumerator = _alphaNumerator; + pool.alphaDenominator = _alphaDenominator; + } + + /** + * @dev Return true if the rebate pool was already initialized. + */ + function exists(Rebates.Pool storage pool) internal view returns (bool) { + return pool.effectiveAllocatedStake > 0; + } + + /** + * @dev Return the amount of unclaimed fees. + */ + function unclaimedFees(Rebates.Pool storage pool) internal view returns (uint256) { + return pool.fees.sub(pool.claimedRewards); + } + + /** + * @dev Deposit tokens into the rebate pool. + * @param _indexerFees Amount of fees collected in tokens + * @param _indexerEffectiveAllocatedStake Effective stake allocated by indexer for a period of epochs + */ + function addToPool( + Rebates.Pool storage pool, + uint256 _indexerFees, + uint256 _indexerEffectiveAllocatedStake + ) internal { + pool.fees = pool.fees.add(_indexerFees); + pool.effectiveAllocatedStake = pool.effectiveAllocatedStake.add( + _indexerEffectiveAllocatedStake + ); + pool.unclaimedAllocationsCount += 1; + } + + /** + * @dev Redeem tokens from the rebate pool. + * @param _indexerFees Amount of fees collected in tokens + * @param _indexerEffectiveAllocatedStake Effective stake allocated by indexer for a period of epochs + * @return Amount of reward tokens according to Cobb-Douglas rebate formula + */ + function redeem( + Rebates.Pool storage pool, + uint256 _indexerFees, + uint256 _indexerEffectiveAllocatedStake + ) internal returns (uint256) { + uint256 rebateReward = 0; + + // Calculate the rebate rewards for the indexer + if (pool.fees > 0 && pool.effectiveAllocatedStake > 0) { + rebateReward = LibCobbDouglas.cobbDouglas( + pool.fees, // totalRewards + _indexerFees, + pool.fees, + _indexerEffectiveAllocatedStake, + pool.effectiveAllocatedStake, + pool.alphaNumerator, + pool.alphaDenominator + ); + + // Under NO circumstance we will reward more than total fees in the pool + uint256 _unclaimedFees = pool.unclaimedFees(); + if (rebateReward > _unclaimedFees) { + rebateReward = _unclaimedFees; + } + } + + // Update pool state + pool.unclaimedAllocationsCount -= 1; + pool.claimedRewards = pool.claimedRewards.add(rebateReward); + + return rebateReward; + } +} diff --git a/packages/legacy/verified/Curation/sources/contracts/staking/libs/Stakes.sol b/packages/legacy/verified/Curation/sources/contracts/staking/libs/Stakes.sol new file mode 100644 index 000000000..5882944d8 --- /dev/null +++ b/packages/legacy/verified/Curation/sources/contracts/staking/libs/Stakes.sol @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; +pragma abicoder v2; + +import "@openzeppelin/contracts/math/SafeMath.sol"; + +import "./MathUtils.sol"; + +/** + * @title A collection of data structures and functions to manage the Indexer Stake state. + * Used for low-level state changes, require() conditions should be evaluated + * at the caller function scope. + */ +library Stakes { + using SafeMath for uint256; + using Stakes for Stakes.Indexer; + + struct Indexer { + uint256 tokensStaked; // Tokens on the indexer stake (staked by the indexer) + uint256 tokensAllocated; // Tokens used in allocations + uint256 tokensLocked; // Tokens locked for withdrawal subject to thawing period + uint256 tokensLockedUntil; // Block when locked tokens can be withdrawn + } + + /** + * @dev Deposit tokens to the indexer stake. + * @param stake Stake data + * @param _tokens Amount of tokens to deposit + */ + function deposit(Stakes.Indexer storage stake, uint256 _tokens) internal { + stake.tokensStaked = stake.tokensStaked.add(_tokens); + } + + /** + * @dev Release tokens from the indexer stake. + * @param stake Stake data + * @param _tokens Amount of tokens to release + */ + function release(Stakes.Indexer storage stake, uint256 _tokens) internal { + stake.tokensStaked = stake.tokensStaked.sub(_tokens); + } + + /** + * @dev Allocate tokens from the main stack to a SubgraphDeployment. + * @param stake Stake data + * @param _tokens Amount of tokens to allocate + */ + function allocate(Stakes.Indexer storage stake, uint256 _tokens) internal { + stake.tokensAllocated = stake.tokensAllocated.add(_tokens); + } + + /** + * @dev Unallocate tokens from a SubgraphDeployment back to the main stack. + * @param stake Stake data + * @param _tokens Amount of tokens to unallocate + */ + function unallocate(Stakes.Indexer storage stake, uint256 _tokens) internal { + stake.tokensAllocated = stake.tokensAllocated.sub(_tokens); + } + + /** + * @dev Lock tokens until a thawing period pass. + * @param stake Stake data + * @param _tokens Amount of tokens to unstake + * @param _period Period in blocks that need to pass before withdrawal + */ + function lockTokens( + Stakes.Indexer storage stake, + uint256 _tokens, + uint256 _period + ) internal { + // Take into account period averaging for multiple unstake requests + uint256 lockingPeriod = _period; + if (stake.tokensLocked > 0) { + lockingPeriod = MathUtils.weightedAverage( + MathUtils.diffOrZero(stake.tokensLockedUntil, block.number), // Remaining thawing period + stake.tokensLocked, // Weighted by remaining unstaked tokens + _period, // Thawing period + _tokens // Weighted by new tokens to unstake + ); + } + + // Update balances + stake.tokensLocked = stake.tokensLocked.add(_tokens); + stake.tokensLockedUntil = block.number.add(lockingPeriod); + } + + /** + * @dev Unlock tokens. + * @param stake Stake data + * @param _tokens Amount of tokens to unkock + */ + function unlockTokens(Stakes.Indexer storage stake, uint256 _tokens) internal { + stake.tokensLocked = stake.tokensLocked.sub(_tokens); + if (stake.tokensLocked == 0) { + stake.tokensLockedUntil = 0; + } + } + + /** + * @dev Take all tokens out from the locked stake for withdrawal. + * @param stake Stake data + * @return Amount of tokens being withdrawn + */ + function withdrawTokens(Stakes.Indexer storage stake) internal returns (uint256) { + // Calculate tokens that can be released + uint256 tokensToWithdraw = stake.tokensWithdrawable(); + + if (tokensToWithdraw > 0) { + // Reset locked tokens + stake.unlockTokens(tokensToWithdraw); + + // Decrease indexer stake + stake.release(tokensToWithdraw); + } + + return tokensToWithdraw; + } + + /** + * @dev Return the amount of tokens used in allocations and locked for withdrawal. + * @param stake Stake data + * @return Token amount + */ + function tokensUsed(Stakes.Indexer memory stake) internal pure returns (uint256) { + return stake.tokensAllocated.add(stake.tokensLocked); + } + + /** + * @dev Return the amount of tokens staked not considering the ones that are already going + * through the thawing period or are ready for withdrawal. We call it secure stake because + * it is not subject to change by a withdraw call from the indexer. + * @param stake Stake data + * @return Token amount + */ + function tokensSecureStake(Stakes.Indexer memory stake) internal pure returns (uint256) { + return stake.tokensStaked.sub(stake.tokensLocked); + } + + /** + * @dev Tokens free balance on the indexer stake that can be used for any purpose. + * Any token that is allocated cannot be used as well as tokens that are going through the + * thawing period or are withdrawable + * Calc: tokensStaked - tokensAllocated - tokensLocked + * @param stake Stake data + * @return Token amount + */ + function tokensAvailable(Stakes.Indexer memory stake) internal pure returns (uint256) { + return stake.tokensAvailableWithDelegation(0); + } + + /** + * @dev Tokens free balance on the indexer stake that can be used for allocations. + * This function accepts a parameter for extra delegated capacity that takes into + * account delegated tokens + * @param stake Stake data + * @param _delegatedCapacity Amount of tokens used from delegators to calculate availability + * @return Token amount + */ + function tokensAvailableWithDelegation(Stakes.Indexer memory stake, uint256 _delegatedCapacity) + internal + pure + returns (uint256) + { + uint256 tokensCapacity = stake.tokensStaked.add(_delegatedCapacity); + uint256 _tokensUsed = stake.tokensUsed(); + // If more tokens are used than the current capacity, the indexer is overallocated. + // This means the indexer doesn't have available capacity to create new allocations. + // We can reach this state when the indexer has funds allocated and then any + // of these conditions happen: + // - The delegationCapacity ratio is reduced. + // - The indexer stake is slashed. + // - A delegator removes enough stake. + if (_tokensUsed > tokensCapacity) { + // Indexer stake is over allocated: return 0 to avoid stake to be used until + // the overallocation is restored by staking more tokens, unallocating tokens + // or using more delegated funds + return 0; + } + return tokensCapacity.sub(_tokensUsed); + } + + /** + * @dev Tokens available for withdrawal after thawing period. + * @param stake Stake data + * @return Token amount + */ + function tokensWithdrawable(Stakes.Indexer memory stake) internal view returns (uint256) { + // No tokens to withdraw before locking period + if (stake.tokensLockedUntil == 0 || block.number < stake.tokensLockedUntil) { + return 0; + } + return stake.tokensLocked; + } +} diff --git a/packages/legacy/verified/Curation/sources/contracts/token/IGraphToken.sol b/packages/legacy/verified/Curation/sources/contracts/token/IGraphToken.sol new file mode 100644 index 000000000..8255e18d5 --- /dev/null +++ b/packages/legacy/verified/Curation/sources/contracts/token/IGraphToken.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface IGraphToken is IERC20 { + // -- Mint and Burn -- + + function burn(uint256 amount) external; + + function burnFrom(address _from, uint256 amount) external; + + function mint(address _to, uint256 _amount) external; + + // -- Mint Admin -- + + function addMinter(address _account) external; + + function removeMinter(address _account) external; + + function renounceMinter() external; + + function isMinter(address _account) external view returns (bool); + + // -- Permit -- + + function permit( + address _owner, + address _spender, + uint256 _value, + uint256 _deadline, + uint8 _v, + bytes32 _r, + bytes32 _s + ) external; + + // -- Allowance -- + + function increaseAllowance(address spender, uint256 addedValue) external returns (bool); + + function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool); +} diff --git a/packages/legacy/verified/Curation/sources/contracts/upgrades/GraphUpgradeable.sol b/packages/legacy/verified/Curation/sources/contracts/upgrades/GraphUpgradeable.sol new file mode 100644 index 000000000..92ce80ad7 --- /dev/null +++ b/packages/legacy/verified/Curation/sources/contracts/upgrades/GraphUpgradeable.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import { IGraphProxy } from "./IGraphProxy.sol"; + +/** + * @title Graph Upgradeable + * @dev This contract is intended to be inherited from upgradeable contracts. + */ +abstract contract GraphUpgradeable { + /** + * @dev Storage slot with the address of the current implementation. + * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is + * validated in the constructor. + */ + bytes32 internal constant IMPLEMENTATION_SLOT = + 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + /** + * @dev Check if the caller is the proxy admin. + */ + modifier onlyProxyAdmin(IGraphProxy _proxy) { + require(msg.sender == _proxy.admin(), "Caller must be the proxy admin"); + _; + } + + /** + * @dev Check if the caller is the implementation. + */ + modifier onlyImpl() { + require(msg.sender == _implementation(), "Only implementation"); + _; + } + + /** + * @dev Returns the current implementation. + * @return impl Address of the current implementation + */ + function _implementation() internal view returns (address impl) { + bytes32 slot = IMPLEMENTATION_SLOT; + // solhint-disable-next-line no-inline-assembly + assembly { + impl := sload(slot) + } + } + + /** + * @notice Accept to be an implementation of proxy. + * @param _proxy Proxy to accept + */ + function acceptProxy(IGraphProxy _proxy) external onlyProxyAdmin(_proxy) { + _proxy.acceptUpgrade(); + } + + /** + * @notice Accept to be an implementation of proxy and then call a function from the new + * implementation as specified by `_data`, which should be an encoded function call. This is + * useful to initialize new storage variables in the proxied contract. + * @param _proxy Proxy to accept + * @param _data Calldata for the initialization function call (including selector) + */ + function acceptProxyAndCall(IGraphProxy _proxy, bytes calldata _data) + external + onlyProxyAdmin(_proxy) + { + _proxy.acceptUpgradeAndCall(_data); + } +} diff --git a/packages/legacy/verified/Curation/sources/contracts/upgrades/IGraphProxy.sol b/packages/legacy/verified/Curation/sources/contracts/upgrades/IGraphProxy.sol new file mode 100644 index 000000000..61946e948 --- /dev/null +++ b/packages/legacy/verified/Curation/sources/contracts/upgrades/IGraphProxy.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +interface IGraphProxy { + function admin() external returns (address); + + function setAdmin(address _newAdmin) external; + + function implementation() external returns (address); + + function pendingImplementation() external returns (address); + + function upgradeTo(address _newImplementation) external; + + function acceptUpgrade() external; + + function acceptUpgradeAndCall(bytes calldata data) external; +} diff --git a/packages/legacy/verified/Curation/sources/contracts/utils/TokenUtils.sol b/packages/legacy/verified/Curation/sources/contracts/utils/TokenUtils.sol new file mode 100644 index 000000000..cbb23ce97 --- /dev/null +++ b/packages/legacy/verified/Curation/sources/contracts/utils/TokenUtils.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import "../token/IGraphToken.sol"; + +library TokenUtils { + /** + * @dev Pull tokens from an address to this contract. + * @param _graphToken Token to transfer + * @param _from Address sending the tokens + * @param _amount Amount of tokens to transfer + */ + function pullTokens( + IGraphToken _graphToken, + address _from, + uint256 _amount + ) internal { + if (_amount > 0) { + require(_graphToken.transferFrom(_from, address(this), _amount), "!transfer"); + } + } + + /** + * @dev Push tokens from this contract to a receiving address. + * @param _graphToken Token to transfer + * @param _to Address receiving the tokens + * @param _amount Amount of tokens to transfer + */ + function pushTokens( + IGraphToken _graphToken, + address _to, + uint256 _amount + ) internal { + if (_amount > 0) { + require(_graphToken.transfer(_to, _amount), "!transfer"); + } + } + + /** + * @dev Burn tokens held by this contract. + * @param _graphToken Token to burn + * @param _amount Amount of tokens to burn + */ + function burnTokens(IGraphToken _graphToken, uint256 _amount) internal { + if (_amount > 0) { + _graphToken.burn(_amount); + } + } +} diff --git a/packages/legacy/verified/GraphToken/compiler.json b/packages/legacy/verified/GraphToken/compiler.json new file mode 100644 index 000000000..a7d46f2fe --- /dev/null +++ b/packages/legacy/verified/GraphToken/compiler.json @@ -0,0 +1,23 @@ +{ + "version": "0.7.4+commit.3f05b770", + "settings": { + "metadata": { + "bytecodeHash": "ipfs" + }, + "libraries": {}, + "optimizer": { + "runs": 200, + "enabled": true + }, + "evmVersion": "istanbul", + "remappings": [] + }, + "target": { + "source": "GraphToken.sol", + "name": "GraphToken" + }, + "sourcifyMatch": "match", + "sourceHashes": { + "GraphToken.sol": "81d76b86b7aa1540eba4449fecd14a3d28529e7aab148319a7719754c5bcf479" + } +} diff --git a/packages/legacy/verified/GraphToken/sources/GraphToken.sol b/packages/legacy/verified/GraphToken/sources/GraphToken.sol new file mode 100644 index 000000000..f822494be --- /dev/null +++ b/packages/legacy/verified/GraphToken/sources/GraphToken.sol @@ -0,0 +1,962 @@ +// Sources flattened with hardhat v2.0.2 https://hardhat.org + +// File @openzeppelin/contracts/GSN/Context.sol@v3.3.0-solc-0.7 + +// SPDX-License-Identifier: MIT + +pragma solidity ^0.7.0; + +/* + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with GSN meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address payable) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes memory) { + this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 + return msg.data; + } +} + + +// File @openzeppelin/contracts/token/ERC20/IERC20.sol@v3.3.0-solc-0.7 + +pragma solidity ^0.7.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} + + +// File @openzeppelin/contracts/math/SafeMath.sol@v3.3.0-solc-0.7 + +pragma solidity ^0.7.0; + +/** + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return sub(a, b, "SafeMath: subtraction overflow"); + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + uint256 c = a - b; + + return c; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return div(a, b, "SafeMath: division by zero"); + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts with custom message on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + + return c; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return mod(a, b, "SafeMath: modulo by zero"); + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts with custom message when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b != 0, errorMessage); + return a % b; + } +} + + +// File @openzeppelin/contracts/token/ERC20/ERC20.sol@v3.3.0-solc-0.7 + +pragma solidity ^0.7.0; + + + +/** + * @dev Implementation of the {IERC20} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * For a generic mechanism see {ERC20PresetMinterPauser}. + * + * TIP: For a detailed writeup see our guide + * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * We have followed general OpenZeppelin guidelines: functions revert instead + * of returning `false` on failure. This behavior is nonetheless conventional + * and does not conflict with the expectations of ERC20 applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +contract ERC20 is Context, IERC20 { + using SafeMath for uint256; + + mapping (address => uint256) private _balances; + + mapping (address => mapping (address => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + uint8 private _decimals; + + /** + * @dev Sets the values for {name} and {symbol}, initializes {decimals} with + * a default value of 18. + * + * To select a different value for {decimals}, use {_setupDecimals}. + * + * All three of these values are immutable: they can only be set once during + * construction. + */ + constructor (string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + _decimals = 18; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5,05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is + * called. + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view returns (uint8) { + return _decimals; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view override returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view override returns (uint256) { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `recipient` cannot be the zero address. + * - the caller must have a balance of at least `amount`. + */ + function transfer(address recipient, uint256 amount) public virtual override returns (bool) { + _transfer(_msgSender(), recipient, amount); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) public view virtual override returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 amount) public virtual override returns (bool) { + _approve(_msgSender(), spender, amount); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * Requirements: + * + * - `sender` and `recipient` cannot be the zero address. + * - `sender` must have a balance of at least `amount`. + * - the caller must have allowance for ``sender``'s tokens of at least + * `amount`. + */ + function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) { + _transfer(sender, recipient, amount); + _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance")); + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue)); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `subtractedValue`. + */ + function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero")); + return true; + } + + /** + * @dev Moves tokens `amount` from `sender` to `recipient`. + * + * This is internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * Requirements: + * + * - `sender` cannot be the zero address. + * - `recipient` cannot be the zero address. + * - `sender` must have a balance of at least `amount`. + */ + function _transfer(address sender, address recipient, uint256 amount) internal virtual { + require(sender != address(0), "ERC20: transfer from the zero address"); + require(recipient != address(0), "ERC20: transfer to the zero address"); + + _beforeTokenTransfer(sender, recipient, amount); + + _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance"); + _balances[recipient] = _balances[recipient].add(amount); + emit Transfer(sender, recipient, amount); + } + + /** @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * Requirements: + * + * - `to` cannot be the zero address. + */ + function _mint(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: mint to the zero address"); + + _beforeTokenTransfer(address(0), account, amount); + + _totalSupply = _totalSupply.add(amount); + _balances[account] = _balances[account].add(amount); + emit Transfer(address(0), account, amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, reducing the + * total supply. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + */ + function _burn(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: burn from the zero address"); + + _beforeTokenTransfer(account, address(0), amount); + + _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance"); + _totalSupply = _totalSupply.sub(amount); + emit Transfer(account, address(0), amount); + } + + /** + * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + */ + function _approve(address owner, address spender, uint256 amount) internal virtual { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + /** + * @dev Sets {decimals} to a value other than the default one of 18. + * + * WARNING: This function should only be called from the constructor. Most + * applications that interact with token contracts will not expect + * {decimals} to ever change, and may work incorrectly if it does. + */ + function _setupDecimals(uint8 decimals_) internal { + _decimals = decimals_; + } + + /** + * @dev Hook that is called before any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * will be to transferred to `to`. + * - when `from` is zero, `amount` tokens will be minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { } +} + + +// File @openzeppelin/contracts/token/ERC20/ERC20Burnable.sol@v3.3.0-solc-0.7 + +pragma solidity ^0.7.0; + + +/** + * @dev Extension of {ERC20} that allows token holders to destroy both their own + * tokens and those that they have an allowance for, in a way that can be + * recognized off-chain (via event analysis). + */ +abstract contract ERC20Burnable is Context, ERC20 { + using SafeMath for uint256; + + /** + * @dev Destroys `amount` tokens from the caller. + * + * See {ERC20-_burn}. + */ + function burn(uint256 amount) public virtual { + _burn(_msgSender(), amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, deducting from the caller's + * allowance. + * + * See {ERC20-_burn} and {ERC20-allowance}. + * + * Requirements: + * + * - the caller must have allowance for ``accounts``'s tokens of at least + * `amount`. + */ + function burnFrom(address account, uint256 amount) public virtual { + uint256 decreasedAllowance = allowance(account, _msgSender()).sub(amount, "ERC20: burn amount exceeds allowance"); + + _approve(account, _msgSender(), decreasedAllowance); + _burn(account, amount); + } +} + + +// File @openzeppelin/contracts/cryptography/ECDSA.sol@v3.3.0-solc-0.7 + +pragma solidity ^0.7.0; + +/** + * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. + * + * These functions can be used to verify that a message was signed by the holder + * of the private keys of a given address. + */ +library ECDSA { + /** + * @dev Returns the address that signed a hashed message (`hash`) with + * `signature`. This address can then be used for verification purposes. + * + * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: + * this function rejects them by requiring the `s` value to be in the lower + * half order, and the `v` value to be either 27 or 28. + * + * IMPORTANT: `hash` _must_ be the result of a hash operation for the + * verification to be secure: it is possible to craft signatures that + * recover to arbitrary addresses for non-hashed data. A safe way to ensure + * this is by receiving a hash of the original message (which may otherwise + * be too long), and then calling {toEthSignedMessageHash} on it. + */ + function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { + // Check the signature length + if (signature.length != 65) { + revert("ECDSA: invalid signature length"); + } + + // Divide the signature in r, s and v variables + bytes32 r; + bytes32 s; + uint8 v; + + // ecrecover takes the signature parameters, and the only way to get them + // currently is to use assembly. + // solhint-disable-next-line no-inline-assembly + assembly { + r := mload(add(signature, 0x20)) + s := mload(add(signature, 0x40)) + v := byte(0, mload(add(signature, 0x60))) + } + + // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature + // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines + // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most + // signatures from current libraries generate a unique signature with an s-value in the lower half order. + // + // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value + // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or + // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept + // these malleable signatures as well. + require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, "ECDSA: invalid signature 's' value"); + require(v == 27 || v == 28, "ECDSA: invalid signature 'v' value"); + + // If the signature is valid (and not malleable), return the signer address + address signer = ecrecover(hash, v, r, s); + require(signer != address(0), "ECDSA: invalid signature"); + + return signer; + } + + /** + * @dev Returns an Ethereum Signed Message, created from a `hash`. This + * replicates the behavior of the + * https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`eth_sign`] + * JSON-RPC method. + * + * See {recover}. + */ + function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) { + // 32 is the length in bytes of hash, + // enforced by the type signature above + return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); + } +} + + +// File contracts/governance/Governed.sol + +pragma solidity ^0.7.3; + +/** + * @title Graph Governance contract + * @dev All contracts that will be owned by a Governor entity should extend this contract. + */ +contract Governed { + // -- State -- + + address public governor; + address public pendingGovernor; + + // -- Events -- + + event NewPendingOwnership(address indexed from, address indexed to); + event NewOwnership(address indexed from, address indexed to); + + /** + * @dev Check if the caller is the governor. + */ + modifier onlyGovernor { + require(msg.sender == governor, "Only Governor can call"); + _; + } + + /** + * @dev Initialize the governor to the contract caller. + */ + function _initialize(address _initGovernor) internal { + governor = _initGovernor; + } + + /** + * @dev Admin function to begin change of governor. The `_newGovernor` must call + * `acceptOwnership` to finalize the transfer. + * @param _newGovernor Address of new `governor` + */ + function transferOwnership(address _newGovernor) external onlyGovernor { + require(_newGovernor != address(0), "Governor must be set"); + + address oldPendingGovernor = pendingGovernor; + pendingGovernor = _newGovernor; + + emit NewPendingOwnership(oldPendingGovernor, pendingGovernor); + } + + /** + * @dev Admin function for pending governor to accept role and update governor. + * This function must called by the pending governor. + */ + function acceptOwnership() external { + require( + pendingGovernor != address(0) && msg.sender == pendingGovernor, + "Caller must be pending governor" + ); + + address oldGovernor = governor; + address oldPendingGovernor = pendingGovernor; + + governor = pendingGovernor; + pendingGovernor = address(0); + + emit NewOwnership(oldGovernor, governor); + emit NewPendingOwnership(oldPendingGovernor, pendingGovernor); + } +} + + +// File contracts/token/GraphToken.sol + +pragma solidity ^0.7.3; + + + + +/** + * @title GraphToken contract + * @dev This is the implementation of the ERC20 Graph Token. + * The implementation exposes a Permit() function to allow for a spender to send a signed message + * and approve funds to a spender following EIP2612 to make integration with other contracts easier. + * + * The token is initially owned by the deployer address that can mint tokens to create the initial + * distribution. For convenience, an initial supply can be passed in the constructor that will be + * assigned to the deployer. + * + * The governor can add the RewardsManager contract to mint indexing rewards. + * + */ +contract GraphToken is Governed, ERC20, ERC20Burnable { + using SafeMath for uint256; + + // -- EIP712 -- + // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md#definition-of-domainseparator + + bytes32 private constant DOMAIN_TYPE_HASH = keccak256( + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)" + ); + bytes32 private constant DOMAIN_NAME_HASH = keccak256("Graph Token"); + bytes32 private constant DOMAIN_VERSION_HASH = keccak256("0"); + bytes32 + private constant DOMAIN_SALT = 0x51f3d585afe6dfeb2af01bba0889a36c1db03beec88c6a4d0c53817069026afa; // Randomly generated salt + bytes32 private constant PERMIT_TYPEHASH = keccak256( + "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" + ); + + // -- State -- + + bytes32 private DOMAIN_SEPARATOR; + mapping(address => bool) private _minters; + mapping(address => uint256) public nonces; + + // -- Events -- + + event MinterAdded(address indexed account); + event MinterRemoved(address indexed account); + + modifier onlyMinter() { + require(isMinter(msg.sender), "Only minter can call"); + _; + } + + /** + * @dev Graph Token Contract Constructor. + * @param _initialSupply Initial supply of GRT + */ + constructor(uint256 _initialSupply) ERC20("Graph Token", "GRT") { + Governed._initialize(msg.sender); + + // The Governor has the initial supply of tokens + _mint(msg.sender, _initialSupply); + + // The Governor is the default minter + _addMinter(msg.sender); + + // EIP-712 domain separator + DOMAIN_SEPARATOR = keccak256( + abi.encode( + DOMAIN_TYPE_HASH, + DOMAIN_NAME_HASH, + DOMAIN_VERSION_HASH, + _getChainID(), + address(this), + DOMAIN_SALT + ) + ); + } + + /** + * @dev Approve token allowance by validating a message signed by the holder. + * @param _owner Address of the token holder + * @param _spender Address of the approved spender + * @param _value Amount of tokens to approve the spender + * @param _deadline Expiration time of the signed permit + * @param _v Signature version + * @param _r Signature r value + * @param _s Signature s value + */ + function permit( + address _owner, + address _spender, + uint256 _value, + uint256 _deadline, + uint8 _v, + bytes32 _r, + bytes32 _s + ) external { + bytes32 digest = keccak256( + abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR, + keccak256( + abi.encode( + PERMIT_TYPEHASH, + _owner, + _spender, + _value, + nonces[_owner], + _deadline + ) + ) + ) + ); + nonces[_owner] = nonces[_owner].add(1); + + address recoveredAddress = ECDSA.recover(digest, abi.encodePacked(_r, _s, _v)); + require(_owner == recoveredAddress, "GRT: invalid permit"); + require(_deadline == 0 || block.timestamp <= _deadline, "GRT: expired permit"); + + _approve(_owner, _spender, _value); + } + + /** + * @dev Add a new minter. + * @param _account Address of the minter + */ + function addMinter(address _account) external onlyGovernor { + _addMinter(_account); + } + + /** + * @dev Remove a minter. + * @param _account Address of the minter + */ + function removeMinter(address _account) external onlyGovernor { + _removeMinter(_account); + } + + /** + * @dev Renounce to be a minter. + */ + function renounceMinter() external { + _removeMinter(msg.sender); + } + + /** + * @dev Mint new tokens. + * @param _to Address to send the newly minted tokens + * @param _amount Amount of tokens to mint + */ + function mint(address _to, uint256 _amount) external onlyMinter { + _mint(_to, _amount); + } + + /** + * @dev Return if the `_account` is a minter or not. + * @param _account Address to check + * @return True if the `_account` is minter + */ + function isMinter(address _account) public view returns (bool) { + return _minters[_account]; + } + + /** + * @dev Add a new minter. + * @param _account Address of the minter + */ + function _addMinter(address _account) private { + _minters[_account] = true; + emit MinterAdded(_account); + } + + /** + * @dev Remove a minter. + * @param _account Address of the minter + */ + function _removeMinter(address _account) private { + _minters[_account] = false; + emit MinterRemoved(_account); + } + + /** + * @dev Get the running network chain ID. + * @return The chain ID + */ + function _getChainID() private pure returns (uint256) { + uint256 id; + assembly { + id := chainid() + } + return id; + } +} \ No newline at end of file diff --git a/packages/legacy/verified/L1GNS/compiler.json b/packages/legacy/verified/L1GNS/compiler.json new file mode 100644 index 000000000..bc56ee14d --- /dev/null +++ b/packages/legacy/verified/L1GNS/compiler.json @@ -0,0 +1,61 @@ +{ + "version": "0.7.6+commit.7338295f", + "settings": { + "metadata": { + "bytecodeHash": "ipfs", + "useLiteralContent": true + }, + "libraries": {}, + "optimizer": { + "runs": 200, + "enabled": true + }, + "evmVersion": "istanbul", + "remappings": [] + }, + "target": { + "source": "contracts/discovery/L1GNS.sol", + "name": "L1GNS" + }, + "sourcifyMatch": "exact_match", + "sourceHashes": { + "contracts/discovery/GNS.sol": "9aab180fab88a15243b9a91724532a6f6932ff2428109a80139a7a9c62b68071", + "contracts/base/Multicall.sol": "5b00bd1e7c7cfb6f6f1f0c837e6976af5960deb2b858eecae246773647dd084c", + "contracts/discovery/IGNS.sol": "0a4c68898721a068313a7fa32b99ebe9da1a432067dcb968a4d7a11e5d72e619", + "contracts/base/IMulticall.sol": "bbc79b9531ebaf4245bab4b7fed363d2e479cc9ce24d579e56a4d8ab44094dea", + "contracts/discovery/L1GNS.sol": "5d5062f4c950760b42accffa2a67da2f51effb3ec3eb1e8d0dc94a2667d29105", + "contracts/staking/IStaking.sol": "e2839479d19e1da6160b5bbad8c39976811c99f33d5bea1f11f8ea20c5a3ce3b", + "contracts/utils/TokenUtils.sol": "f1069ba54bdf449002414f051d99538ca02d8253a1670ec1398727002ffff45b", + "contracts/token/IGraphToken.sol": "244ca084fd409c9ad3969e7ac5548346495466442ff7f802288b112d687705d2", + "contracts/curation/ICuration.sol": "34624b8df89549050f23bb3c606c9c60440330ab23551f4d4f1f0b94aef6afc9", + "contracts/governance/Managed.sol": "ea920fd87b021bee5df867d98e770db5abda51823b31f019ced405a4e09fdf72", + "contracts/staking/libs/Cobbs.sol": "c03a095787e00a624074e850ed76e384ad9e915bebb04092436a2173c05e451d", + "contracts/governance/IManaged.sol": "2f4a25095e90d6b08f1789dde49cb87d46888090ccc4e6ab1459e316c746cd29", + "contracts/l2/discovery/IL2GNS.sol": "55df75583b94d01b4aaf826d09495d73c3eaa7de6651ca9091bced6a34b12eec", + "contracts/staking/libs/Stakes.sol": "d2954b67e9bf2681a3d958ab2d3eda8d8c7942e4fade92cd4670e33458f6d676", + "contracts/discovery/GNSStorage.sol": "3420c8831bc4238e445c51aedb4f9f5627df4a431ee1b6e42e52a0e032a8dfd8", + "contracts/epochs/IEpochManager.sol": "cb45047682865d6ed215af7d05d505b8fa7a1e66b1e54b45c1a47798281f47ab", + "contracts/staking/IStakingBase.sol": "e6e3c27f691c5517ba30f179f5bffc3e94428da1b7756352f4a4d1885d63da3e", + "contracts/staking/IStakingData.sol": "76dc93c787a58f0bf494194a880a219d8c788e525cb1288cd7063fe78ec59dc2", + "contracts/staking/libs/Rebates.sol": "db2181deffbf15c6a42edbf891b5b24ece3e5f366ced2989efe18f829614b161", + "contracts/upgrades/IGraphProxy.sol": "d7b89c0be1a05c93facc640a08d0b4f1e0b4557cb268d9143e4c42ce24bbd1a9", + "contracts/arbitrum/ITokenGateway.sol": "55bd317bf1ca12a2308cc51ad4ec6c370dcb6d1cdd1631ec2308e57528ad7600", + "contracts/discovery/ISubgraphNFT.sol": "e4e89a794ae1ee7487dc9636264f2d531c98cbc2842957b49af90380129afe5f", + "contracts/discovery/L1GNSStorage.sol": "792ce9c5e4efd88db6cba361bbe9ab8143ffbf089e345780fda70a8322d0e261", + "contracts/governance/IController.sol": "beb591bb70a3b94f025a5f64bbb3fc6788043fb8d5f1cdffb21acb8cbbce8f8d", + "contracts/staking/libs/MathUtils.sol": "721f4b3dbde3170e895a45c09905972db388321a2d7f61408ad4e2fd8d5a34a8", + "contracts/rewards/IRewardsManager.sol": "5cf8c3593f475719620e6a01b478fa3b5644d3848f82e7768bb36d43069478dd", + "contracts/gateway/ICallhookReceiver.sol": "36f674617a548524a637a2009674ac878e630a867bccfb0866403138f0a8554d", + "contracts/staking/IStakingExtension.sol": "1f39a3d4a7f482889a345fa4b31c1353f899f7f078b42f558f13960a7665f2ca", + "contracts/staking/libs/LibFixedMath.sol": "8a2760584a3bacd920e16cad7f90ff37e73e1795558574c8c6153b747e2ba498", + "contracts/upgrades/GraphUpgradeable.sol": "fdb340dbeb5de52ea8243fbfab9d2ef8fdfb916e909dc4e1ae6b314cde0ea083", + "@openzeppelin/contracts/math/SafeMath.sol": "4a04d0a20a19e3ef1dcabae9cad9ba006430a4e7eec4d9b519db87999722c98a", + "@openzeppelin/contracts/token/ERC20/IERC20.sol": "0573c2961569aa4906845d0cd428b5b7394956170054ceeaa8f8af96cd44875c", + "@openzeppelin/contracts/token/ERC721/IERC721.sol": "07abc5d9ae593f0dc7b854cb476fbee9e9f0df1c8f864e061f61e1532fb16357", + "@openzeppelin/contracts/introspection/IERC165.sol": "24d63fd063d0d9e954ce1a039404b4c01d2141f787143bbd3d5090a0220a2bcc", + "contracts/discovery/erc1056/IEthereumDIDRegistry.sol": "edc80d6f2efbbf7857bf1a148a04e45501c51038b2dbef562bc1d2c075e83b92", + "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol": "2c3a3edc2b1a4ac2c4a8645475b51f2668b1ad5ea22df074d0c0ebd3122ce2e7", + "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol": "dabaab4d3d3f03e6bfb86eec1d54f31edf0429f4bfc4dff717d5776d5231c145", + "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol": "877bc9cb396d0f50330bb9c0057c029407e159739b6fab0b110f19451c8681e4" + } +} diff --git a/packages/legacy/verified/L1GNS/sources/@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol b/packages/legacy/verified/L1GNS/sources/@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol new file mode 100644 index 000000000..c7b5c001e --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMathUpgradeable { + /** + * @dev Returns the addition of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + + /** + * @dev Returns the substraction of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b > a) return (false, 0); + return (true, a - b); + } + + /** + * @dev Returns the multiplication of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + + /** + * @dev Returns the division of two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b == 0) return (false, 0); + return (true, a / b); + } + + /** + * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b == 0) return (false, 0); + return (true, a % b); + } + + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + require(b <= a, "SafeMath: subtraction overflow"); + return a - b; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) return 0; + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + require(b > 0, "SafeMath: division by zero"); + return a / b; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + require(b > 0, "SafeMath: modulo by zero"); + return a % b; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {trySub}. + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + return a - b; + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting with custom message on + * division by zero. The result is rounded towards zero. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryDiv}. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + return a / b; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting with custom message when dividing by zero. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryMod}. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + return a % b; + } +} diff --git a/packages/legacy/verified/L1GNS/sources/@openzeppelin/contracts-upgradeable/proxy/Initializable.sol b/packages/legacy/verified/L1GNS/sources/@openzeppelin/contracts-upgradeable/proxy/Initializable.sol new file mode 100644 index 000000000..9fd6dc9fc --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/@openzeppelin/contracts-upgradeable/proxy/Initializable.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT + +// solhint-disable-next-line compiler-version +pragma solidity >=0.4.24 <0.8.0; + +import "../utils/AddressUpgradeable.sol"; + +/** + * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed + * behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an + * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer + * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. + * + * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as + * possible by providing the encoded function call as the `_data` argument to {UpgradeableProxy-constructor}. + * + * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure + * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. + */ +abstract contract Initializable { + + /** + * @dev Indicates that the contract has been initialized. + */ + bool private _initialized; + + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool private _initializing; + + /** + * @dev Modifier to protect an initializer function from being invoked twice. + */ + modifier initializer() { + require(_initializing || _isConstructor() || !_initialized, "Initializable: contract is already initialized"); + + bool isTopLevelCall = !_initializing; + if (isTopLevelCall) { + _initializing = true; + _initialized = true; + } + + _; + + if (isTopLevelCall) { + _initializing = false; + } + } + + /// @dev Returns true if and only if the function is running in the constructor + function _isConstructor() private view returns (bool) { + return !AddressUpgradeable.isContract(address(this)); + } +} diff --git a/packages/legacy/verified/L1GNS/sources/@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol b/packages/legacy/verified/L1GNS/sources/@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol new file mode 100644 index 000000000..08a59041b --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.2 <0.8.0; + +/** + * @dev Collection of functions related to the address type + */ +library AddressUpgradeable { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + uint256 size; + // solhint-disable-next-line no-inline-assembly + assembly { size := extcodesize(account) } + return size > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + // solhint-disable-next-line avoid-low-level-calls, avoid-call-value + (bool success, ) = recipient.call{ value: amount }(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain`call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.call{ value: value }(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.staticcall(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) { + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + + // solhint-disable-next-line no-inline-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} diff --git a/packages/legacy/verified/L1GNS/sources/@openzeppelin/contracts/introspection/IERC165.sol b/packages/legacy/verified/L1GNS/sources/@openzeppelin/contracts/introspection/IERC165.sol new file mode 100644 index 000000000..d47435525 --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/@openzeppelin/contracts/introspection/IERC165.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Interface of the ERC165 standard, as defined in the + * https://eips.ethereum.org/EIPS/eip-165[EIP]. + * + * Implementers can declare support of contract interfaces, which can then be + * queried by others ({ERC165Checker}). + * + * For an implementation, see {ERC165}. + */ +interface IERC165 { + /** + * @dev Returns true if this contract implements the interface defined by + * `interfaceId`. See the corresponding + * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] + * to learn more about how these ids are created. + * + * This function call must use less than 30 000 gas. + */ + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} diff --git a/packages/legacy/verified/L1GNS/sources/@openzeppelin/contracts/math/SafeMath.sol b/packages/legacy/verified/L1GNS/sources/@openzeppelin/contracts/math/SafeMath.sol new file mode 100644 index 000000000..3e81f295b --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/@openzeppelin/contracts/math/SafeMath.sol @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + + /** + * @dev Returns the substraction of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b > a) return (false, 0); + return (true, a - b); + } + + /** + * @dev Returns the multiplication of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + + /** + * @dev Returns the division of two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b == 0) return (false, 0); + return (true, a / b); + } + + /** + * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b == 0) return (false, 0); + return (true, a % b); + } + + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + require(b <= a, "SafeMath: subtraction overflow"); + return a - b; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) return 0; + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + require(b > 0, "SafeMath: division by zero"); + return a / b; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + require(b > 0, "SafeMath: modulo by zero"); + return a % b; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {trySub}. + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + return a - b; + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting with custom message on + * division by zero. The result is rounded towards zero. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryDiv}. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + return a / b; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting with custom message when dividing by zero. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryMod}. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + return a % b; + } +} diff --git a/packages/legacy/verified/L1GNS/sources/@openzeppelin/contracts/token/ERC20/IERC20.sol b/packages/legacy/verified/L1GNS/sources/@openzeppelin/contracts/token/ERC20/IERC20.sol new file mode 100644 index 000000000..9b877d119 --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/@openzeppelin/contracts/token/ERC20/IERC20.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} diff --git a/packages/legacy/verified/L1GNS/sources/@openzeppelin/contracts/token/ERC721/IERC721.sol b/packages/legacy/verified/L1GNS/sources/@openzeppelin/contracts/token/ERC721/IERC721.sol new file mode 100644 index 000000000..787afde6f --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/@openzeppelin/contracts/token/ERC721/IERC721.sol @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.2 <0.8.0; + +import "../../introspection/IERC165.sol"; + +/** + * @dev Required interface of an ERC721 compliant contract. + */ +interface IERC721 is IERC165 { + /** + * @dev Emitted when `tokenId` token is transferred from `from` to `to`. + */ + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + + /** + * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. + */ + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + + /** + * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. + */ + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + /** + * @dev Returns the number of tokens in ``owner``'s account. + */ + function balanceOf(address owner) external view returns (uint256 balance); + + /** + * @dev Returns the owner of the `tokenId` token. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function ownerOf(uint256 tokenId) external view returns (address owner); + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients + * are aware of the ERC721 protocol to prevent tokens from being forever locked. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function safeTransferFrom(address from, address to, uint256 tokenId) external; + + /** + * @dev Transfers `tokenId` token from `from` to `to`. + * + * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must be owned by `from`. + * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. + * + * Emits a {Transfer} event. + */ + function transferFrom(address from, address to, uint256 tokenId) external; + + /** + * @dev Gives permission to `to` to transfer `tokenId` token to another account. + * The approval is cleared when the token is transferred. + * + * Only a single account can be approved at a time, so approving the zero address clears previous approvals. + * + * Requirements: + * + * - The caller must own the token or be an approved operator. + * - `tokenId` must exist. + * + * Emits an {Approval} event. + */ + function approve(address to, uint256 tokenId) external; + + /** + * @dev Returns the account approved for `tokenId` token. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function getApproved(uint256 tokenId) external view returns (address operator); + + /** + * @dev Approve or remove `operator` as an operator for the caller. + * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. + * + * Requirements: + * + * - The `operator` cannot be the caller. + * + * Emits an {ApprovalForAll} event. + */ + function setApprovalForAll(address operator, bool _approved) external; + + /** + * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. + * + * See {setApprovalForAll} + */ + function isApprovedForAll(address owner, address operator) external view returns (bool); + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/arbitrum/ITokenGateway.sol b/packages/legacy/verified/L1GNS/sources/contracts/arbitrum/ITokenGateway.sol new file mode 100644 index 000000000..977fe07f2 --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/arbitrum/ITokenGateway.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2020, Offchain Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Originally copied from: + * https://github.com/OffchainLabs/arbitrum/tree/e3a6307ad8a2dc2cad35728a2a9908cfd8dd8ef9/packages/arb-bridge-peripherals + * + * MODIFIED from Offchain Labs' implementation: + * - Changed solidity version to 0.7.6 (pablo@edgeandnode.com) + * + */ + +pragma solidity ^0.7.6; + +interface ITokenGateway { + /// @notice event deprecated in favor of DepositInitiated and WithdrawalInitiated + // event OutboundTransferInitiated( + // address token, + // address indexed _from, + // address indexed _to, + // uint256 indexed _transferId, + // uint256 _amount, + // bytes _data + // ); + + /// @notice event deprecated in favor of DepositFinalized and WithdrawalFinalized + // event InboundTransferFinalized( + // address token, + // address indexed _from, + // address indexed _to, + // uint256 indexed _transferId, + // uint256 _amount, + // bytes _data + // ); + + function outboundTransfer( + address _token, + address _to, + uint256 _amount, + uint256 _maxGas, + uint256 _gasPriceBid, + bytes calldata _data + ) external payable returns (bytes memory); + + function finalizeInboundTransfer( + address _token, + address _from, + address _to, + uint256 _amount, + bytes calldata _data + ) external payable; + + /** + * @notice Calculate the address used when bridging an ERC20 token + * @dev the L1 and L2 address oracles may not always be in sync. + * For example, a custom token may have been registered but not deployed or the contract self destructed. + * @param l1ERC20 address of L1 token + * @return L2 address of a bridged ERC20 token + */ + function calculateL2TokenAddress(address l1ERC20) external view returns (address); +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/base/IMulticall.sol b/packages/legacy/verified/L1GNS/sources/contracts/base/IMulticall.sol new file mode 100644 index 000000000..3269f694b --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/base/IMulticall.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; +pragma abicoder v2; + +/** + * @title Multicall interface + * @notice Enables calling multiple methods in a single call to the contract + */ +interface IMulticall { + /** + * @notice Call multiple functions in the current contract and return the data from all of them if they all succeed + * @param data The encoded function data for each of the calls to make to this contract + * @return results The results from each of the calls passed in via data + */ + function multicall(bytes[] calldata data) external returns (bytes[] memory results); +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/base/Multicall.sol b/packages/legacy/verified/L1GNS/sources/contracts/base/Multicall.sol new file mode 100644 index 000000000..49111840d --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/base/Multicall.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; +pragma abicoder v2; + +import "./IMulticall.sol"; + +// Inspired by https://github.com/Uniswap/uniswap-v3-periphery/blob/main/contracts/base/Multicall.sol +// Note: Removed payable from the multicall + +/** + * @title Multicall + * @notice Enables calling multiple methods in a single call to the contract + */ +abstract contract Multicall is IMulticall { + /// @inheritdoc IMulticall + function multicall(bytes[] calldata data) external override returns (bytes[] memory results) { + results = new bytes[](data.length); + for (uint256 i = 0; i < data.length; i++) { + (bool success, bytes memory result) = address(this).delegatecall(data[i]); + + if (!success) { + // Next 5 lines from https://ethereum.stackexchange.com/a/83577 + if (result.length < 68) revert(); + assembly { + result := add(result, 0x04) + } + revert(abi.decode(result, (string))); + } + + results[i] = result; + } + } +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/curation/ICuration.sol b/packages/legacy/verified/L1GNS/sources/contracts/curation/ICuration.sol new file mode 100644 index 000000000..dffff46cd --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/curation/ICuration.sol @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +/** + * @title Curation Interface + * @dev Interface for the Curation contract (and L2Curation too) + */ +interface ICuration { + // -- Configuration -- + + /** + * @notice Update the default reserve ratio to `_defaultReserveRatio` + * @param _defaultReserveRatio Reserve ratio (in PPM) + */ + function setDefaultReserveRatio(uint32 _defaultReserveRatio) external; + + /** + * @notice Update the minimum deposit amount needed to intialize a new subgraph + * @param _minimumCurationDeposit Minimum amount of tokens required deposit + */ + function setMinimumCurationDeposit(uint256 _minimumCurationDeposit) external; + + /** + * @notice Set the curation tax percentage to charge when a curator deposits GRT tokens. + * @param _percentage Curation tax percentage charged when depositing GRT tokens + */ + function setCurationTaxPercentage(uint32 _percentage) external; + + /** + * @notice Set the master copy to use as clones for the curation token. + * @param _curationTokenMaster Address of implementation contract to use for curation tokens + */ + function setCurationTokenMaster(address _curationTokenMaster) external; + + // -- Curation -- + + /** + * @notice Deposit Graph Tokens in exchange for signal of a SubgraphDeployment curation pool. + * @param _subgraphDeploymentID Subgraph deployment pool from where to mint signal + * @param _tokensIn Amount of Graph Tokens to deposit + * @param _signalOutMin Expected minimum amount of signal to receive + * @return Amount of signal minted + * @return Amount of curation tax burned + */ + function mint( + bytes32 _subgraphDeploymentID, + uint256 _tokensIn, + uint256 _signalOutMin + ) external returns (uint256, uint256); + + /** + * @notice Burn _signal from the SubgraphDeployment curation pool + * @param _subgraphDeploymentID SubgraphDeployment the curator is returning signal + * @param _signalIn Amount of signal to return + * @param _tokensOutMin Expected minimum amount of tokens to receive + * @return Tokens returned + */ + function burn( + bytes32 _subgraphDeploymentID, + uint256 _signalIn, + uint256 _tokensOutMin + ) external returns (uint256); + + /** + * @notice Assign Graph Tokens collected as curation fees to the curation pool reserve. + * @param _subgraphDeploymentID SubgraphDeployment where funds should be allocated as reserves + * @param _tokens Amount of Graph Tokens to add to reserves + */ + function collect(bytes32 _subgraphDeploymentID, uint256 _tokens) external; + + // -- Getters -- + + /** + * @notice Check if any GRT tokens are deposited for a SubgraphDeployment. + * @param _subgraphDeploymentID SubgraphDeployment to check if curated + * @return True if curated, false otherwise + */ + function isCurated(bytes32 _subgraphDeploymentID) external view returns (bool); + + /** + * @notice Get the amount of signal a curator has in a curation pool. + * @param _curator Curator owning the signal tokens + * @param _subgraphDeploymentID Subgraph deployment curation pool + * @return Amount of signal owned by a curator for the subgraph deployment + */ + function getCuratorSignal(address _curator, bytes32 _subgraphDeploymentID) + external + view + returns (uint256); + + /** + * @notice Get the amount of signal in a curation pool. + * @param _subgraphDeploymentID Subgraph deployment curation poool + * @return Amount of signal minted for the subgraph deployment + */ + function getCurationPoolSignal(bytes32 _subgraphDeploymentID) external view returns (uint256); + + /** + * @notice Get the amount of token reserves in a curation pool. + * @param _subgraphDeploymentID Subgraph deployment curation poool + * @return Amount of token reserves in the curation pool + */ + function getCurationPoolTokens(bytes32 _subgraphDeploymentID) external view returns (uint256); + + /** + * @notice Calculate amount of signal that can be bought with tokens in a curation pool. + * This function considers and excludes the deposit tax. + * @param _subgraphDeploymentID Subgraph deployment to mint signal + * @param _tokensIn Amount of tokens used to mint signal + * @return Amount of signal that can be bought + * @return Amount of tokens that will be burned as curation tax + */ + function tokensToSignal(bytes32 _subgraphDeploymentID, uint256 _tokensIn) + external + view + returns (uint256, uint256); + + /** + * @notice Calculate number of tokens to get when burning signal from a curation pool. + * @param _subgraphDeploymentID Subgraph deployment to burn signal + * @param _signalIn Amount of signal to burn + * @return Amount of tokens to get for the specified amount of signal + */ + function signalToTokens(bytes32 _subgraphDeploymentID, uint256 _signalIn) + external + view + returns (uint256); + + /** + * @notice Tax charged when curators deposit funds. + * Parts per million. (Allows for 4 decimal points, 999,999 = 99.9999%) + * @return Curation tax percentage expressed in PPM + */ + function curationTaxPercentage() external view returns (uint32); +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/discovery/GNS.sol b/packages/legacy/verified/L1GNS/sources/contracts/discovery/GNS.sol new file mode 100644 index 000000000..9af60ba2f --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/discovery/GNS.sol @@ -0,0 +1,910 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; +pragma abicoder v2; + +import { SafeMathUpgradeable } from "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; +import { AddressUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; + +import { Multicall } from "../base/Multicall.sol"; +import { GraphUpgradeable } from "../upgrades/GraphUpgradeable.sol"; +import { TokenUtils } from "../utils/TokenUtils.sol"; +import { ICuration } from "../curation/ICuration.sol"; +import { Managed } from "../governance/Managed.sol"; +import { ISubgraphNFT } from "./ISubgraphNFT.sol"; + +import { IGNS } from "./IGNS.sol"; +import { GNSV3Storage } from "./GNSStorage.sol"; + +/** + * @title GNS + * @dev The Graph Name System contract provides a decentralized naming system for subgraphs + * used in the scope of the Graph Network. It translates Subgraphs into Subgraph Versions. + * Each version is associated with a Subgraph Deployment. The contract has no knowledge of + * human-readable names. All human readable names emitted in events. + * The contract implements a multicall behaviour to support batching multiple calls in a single + * transaction. + */ +abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { + using SafeMathUpgradeable for uint256; + + // -- Constants -- + + /// @dev 100% in parts per million + uint32 private constant MAX_PPM = 1000000; + + /// @dev Equates to Connector weight on bancor formula to be CW = 1 + uint32 internal immutable fixedReserveRatio = MAX_PPM; + + // -- Events -- + + /// @dev Emitted when the subgraph NFT contract is updated + event SubgraphNFTUpdated(address subgraphNFT); + + /** + * @dev Emitted when graph account sets its default name + */ + event SetDefaultName( + address indexed graphAccount, + uint256 nameSystem, // only ENS for now + bytes32 nameIdentifier, + string name + ); + + /** + * @dev Emitted when the subgraph metadata is updated. + */ + event SubgraphMetadataUpdated(uint256 indexed subgraphID, bytes32 subgraphMetadata); + + /** + * @dev Emitted when a subgraph version is updated. + */ + event SubgraphVersionUpdated( + uint256 indexed subgraphID, + bytes32 indexed subgraphDeploymentID, + bytes32 versionMetadata + ); + + /** + * @dev Emitted when a curator mints signal. + */ + event SignalMinted( + uint256 indexed subgraphID, + address indexed curator, + uint256 nSignalCreated, + uint256 vSignalCreated, + uint256 tokensDeposited + ); + + /** + * @dev Emitted when a curator burns signal. + */ + event SignalBurned( + uint256 indexed subgraphID, + address indexed curator, + uint256 nSignalBurnt, + uint256 vSignalBurnt, + uint256 tokensReceived + ); + + /** + * @dev Emitted when a curator transfers signal. + */ + event SignalTransferred( + uint256 indexed subgraphID, + address indexed from, + address indexed to, + uint256 nSignalTransferred + ); + + /** + * @dev Emitted when a subgraph is created. + */ + event SubgraphPublished( + uint256 indexed subgraphID, + bytes32 indexed subgraphDeploymentID, + uint32 reserveRatio + ); + + /** + * @dev Emitted when a subgraph is upgraded to point to a new + * subgraph deployment, burning all the old vSignal and depositing the GRT into the + * new vSignal curve. + */ + event SubgraphUpgraded( + uint256 indexed subgraphID, + uint256 vSignalCreated, + uint256 tokensSignalled, + bytes32 indexed subgraphDeploymentID + ); + + /** + * @dev Emitted when a subgraph is deprecated. + */ + event SubgraphDeprecated(uint256 indexed subgraphID, uint256 withdrawableGRT); + + /** + * @dev Emitted when a curator withdraws GRT from a deprecated subgraph + */ + event GRTWithdrawn( + uint256 indexed subgraphID, + address indexed curator, + uint256 nSignalBurnt, + uint256 withdrawnGRT + ); + + /** + * @dev Emitted when the counterpart (L1/L2) GNS address is updated + */ + event CounterpartGNSAddressUpdated(address _counterpart); + + // -- Modifiers -- + + /** + * @dev Emitted when a legacy subgraph is claimed + */ + event LegacySubgraphClaimed(address indexed graphAccount, uint256 subgraphNumber); + + /** + * @dev Modifier that allows only a subgraph operator to be the caller + */ + modifier onlySubgraphAuth(uint256 _subgraphID) { + require(ownerOf(_subgraphID) == msg.sender, "GNS: Must be authorized"); + _; + } + + // -- Functions -- + + /** + * @notice Initialize the GNS contract. + * @param _controller Address of the Controller contract that manages this contract + * @param _subgraphNFT Address of the Subgraph NFT contract + */ + function initialize(address _controller, address _subgraphNFT) external onlyImpl initializer { + Managed._initialize(_controller); + + // Settings + _setOwnerTaxPercentage(500000); + _setSubgraphNFT(_subgraphNFT); + } + + /** + * @notice Approve curation contract to pull funds. + */ + function approveAll() external override { + graphToken().approve(address(curation()), type(uint256).max); + } + + // -- Config -- + + /** + * @notice Set the owner fee percentage. This is used to prevent a subgraph owner to drain all + * the name curators tokens while upgrading or deprecating and is configurable in parts per million. + * @param _ownerTaxPercentage Owner tax percentage + */ + function setOwnerTaxPercentage(uint32 _ownerTaxPercentage) external override onlyGovernor { + _setOwnerTaxPercentage(_ownerTaxPercentage); + } + + /** + * @notice Set the NFT registry contract + * NOTE: Calling this function will break the ownership model unless + * it is replaced with a fully migrated version of the NFT contract state + * Use with care. + * @param _subgraphNFT Address of the ERC721 contract + */ + function setSubgraphNFT(address _subgraphNFT) external onlyGovernor { + _setSubgraphNFT(_subgraphNFT); + } + + /** + * @notice Set the counterpart (L1/L2) GNS address + * @param _counterpart Owner tax percentage + */ + function setCounterpartGNSAddress(address _counterpart) external onlyGovernor { + counterpartGNSAddress = _counterpart; + emit CounterpartGNSAddressUpdated(_counterpart); + } + + // -- Actions -- + + /** + * @notice Allows a graph account to set a default name + * @param _graphAccount Account that is setting its name + * @param _nameSystem Name system account already has ownership of a name in + * @param _nameIdentifier The unique identifier that is used to identify the name in the system + * @param _name The name being set as default + */ + function setDefaultName( + address _graphAccount, + uint8 _nameSystem, + bytes32 _nameIdentifier, + string calldata _name + ) external override { + require(_graphAccount == msg.sender, "GNS: Only you can set your name"); + emit SetDefaultName(_graphAccount, _nameSystem, _nameIdentifier, _name); + } + + /** + * @notice Allows a subgraph owner to update the metadata of a subgraph they have published + * @param _subgraphID Subgraph ID + * @param _subgraphMetadata IPFS hash for the subgraph metadata + */ + function updateSubgraphMetadata(uint256 _subgraphID, bytes32 _subgraphMetadata) + external + override + onlySubgraphAuth(_subgraphID) + { + _setSubgraphMetadata(_subgraphID, _subgraphMetadata); + } + + /** + * @notice Publish a new subgraph. + * @param _subgraphDeploymentID Subgraph deployment for the subgraph + * @param _versionMetadata IPFS hash for the subgraph version metadata + * @param _subgraphMetadata IPFS hash for the subgraph metadata + */ + function publishNewSubgraph( + bytes32 _subgraphDeploymentID, + bytes32 _versionMetadata, + bytes32 _subgraphMetadata + ) external override notPaused { + // Subgraph deployment must be non-empty + require(_subgraphDeploymentID != 0, "GNS: Cannot set deploymentID to 0 in publish"); + + // Init the subgraph + address subgraphOwner = msg.sender; + uint256 subgraphID = _nextSubgraphID(subgraphOwner); + SubgraphData storage subgraphData = _getSubgraphData(subgraphID); + subgraphData.subgraphDeploymentID = _subgraphDeploymentID; + subgraphData.reserveRatioDeprecated = fixedReserveRatio; + + // Mint the NFT. Use the subgraphID as tokenID. + // This function will check the if tokenID already exists. + _mintNFT(subgraphOwner, subgraphID); + emit SubgraphPublished(subgraphID, _subgraphDeploymentID, fixedReserveRatio); + + // Set the token metadata + _setSubgraphMetadata(subgraphID, _subgraphMetadata); + + emit SubgraphVersionUpdated(subgraphID, _subgraphDeploymentID, _versionMetadata); + } + + /** + * @notice Publish a new version of an existing subgraph. + * @param _subgraphID Subgraph ID + * @param _subgraphDeploymentID Subgraph deployment ID of the new version + * @param _versionMetadata IPFS hash for the subgraph version metadata + */ + function publishNewVersion( + uint256 _subgraphID, + bytes32 _subgraphDeploymentID, + bytes32 _versionMetadata + ) external virtual override notPaused onlySubgraphAuth(_subgraphID) { + // Perform the upgrade from the current subgraph deployment to the new one. + // This involves burning all signal from the old deployment and using the funds to buy + // from the new deployment. + // This will also make the change to target to the new deployment. + + // Subgraph check + SubgraphData storage subgraphData = _getSubgraphOrRevert(_subgraphID); + + // New subgraph deployment must be non-empty + require(_subgraphDeploymentID != 0, "GNS: Cannot set deploymentID to 0 in publish"); + + // New subgraph deployment must be different than current + require( + _subgraphDeploymentID != subgraphData.subgraphDeploymentID, + "GNS: Cannot publish a new version with the same subgraph deployment ID" + ); + + // This is to prevent the owner from front running its name curators signal by posting + // its own signal ahead, bringing the name curators in, and dumping on them + ICuration curation = curation(); + require( + !curation.isCurated(_subgraphDeploymentID), + "GNS: Owner cannot point to a subgraphID that has been pre-curated" + ); + + // Move all signal from previous version to new version + // NOTE: We will only do this as long as there is signal on the subgraph + if (subgraphData.nSignal != 0) { + // Burn all version signal in the name pool for tokens (w/no slippage protection) + // Sell all signal from the old deployment + uint256 tokens = curation.burn( + subgraphData.subgraphDeploymentID, + subgraphData.vSignal, + 0 + ); + + // Take the owner cut of the curation tax, add it to the total + // Upgrade is only callable by the owner, we assume then that msg.sender = owner + address subgraphOwner = msg.sender; + uint256 tokensWithTax = _chargeOwnerTax( + tokens, + subgraphOwner, + curation.curationTaxPercentage() + ); + + // Update pool: constant nSignal, vSignal can change (w/no slippage protection) + // Buy all signal from the new deployment + (subgraphData.vSignal, ) = curation.mint(_subgraphDeploymentID, tokensWithTax, 0); + + emit SubgraphUpgraded( + _subgraphID, + subgraphData.vSignal, + tokensWithTax, + _subgraphDeploymentID + ); + } + + // Update target deployment + subgraphData.subgraphDeploymentID = _subgraphDeploymentID; + + emit SubgraphVersionUpdated(_subgraphID, _subgraphDeploymentID, _versionMetadata); + } + + /** + * @notice Deprecate a subgraph. The bonding curve is destroyed, the vSignal is burned, and the GNS + * contract holds the GRT from burning the vSignal, which all curators can withdraw manually. + * Can only be done by the subgraph owner. + * @param _subgraphID Subgraph ID + */ + function deprecateSubgraph(uint256 _subgraphID) + external + override + notPaused + onlySubgraphAuth(_subgraphID) + { + // Subgraph check + SubgraphData storage subgraphData = _getSubgraphOrRevert(_subgraphID); + + // Burn signal only if it has any available + if (subgraphData.nSignal != 0) { + subgraphData.withdrawableGRT = curation().burn( + subgraphData.subgraphDeploymentID, + subgraphData.vSignal, + 0 + ); + } + + // Deprecate the subgraph and do cleanup + subgraphData.disabled = true; + subgraphData.vSignal = 0; + subgraphData.reserveRatioDeprecated = 0; + // NOTE: We don't reset the following variable as we use it to test if the Subgraph was ever created + // subgraphData.subgraphDeploymentID = 0; + + // Burn the NFT + _burnNFT(_subgraphID); + + emit SubgraphDeprecated(_subgraphID, subgraphData.withdrawableGRT); + } + + /** + * @notice Deposit GRT into a subgraph and mint signal. + * @param _subgraphID Subgraph ID + * @param _tokensIn The amount of tokens the nameCurator wants to deposit + * @param _nSignalOutMin Expected minimum amount of name signal to receive + */ + function mintSignal( + uint256 _subgraphID, + uint256 _tokensIn, + uint256 _nSignalOutMin + ) external override notPartialPaused { + // Subgraph checks + SubgraphData storage subgraphData = _getSubgraphOrRevert(_subgraphID); + + // Pull tokens from sender + address curator = msg.sender; + TokenUtils.pullTokens(graphToken(), curator, _tokensIn); + + // Get name signal to mint for tokens deposited + (uint256 vSignal, ) = curation().mint(subgraphData.subgraphDeploymentID, _tokensIn, 0); + uint256 nSignal = vSignalToNSignal(_subgraphID, vSignal); + + // Slippage protection + require(nSignal >= _nSignalOutMin, "GNS: Slippage protection"); + + // Update pools + subgraphData.vSignal = subgraphData.vSignal.add(vSignal); + subgraphData.nSignal = subgraphData.nSignal.add(nSignal); + subgraphData.curatorNSignal[curator] = subgraphData.curatorNSignal[curator].add(nSignal); + + emit SignalMinted(_subgraphID, curator, nSignal, vSignal, _tokensIn); + } + + /** + * @notice Burn signal for a subgraph and return the GRT. + * @param _subgraphID Subgraph ID + * @param _nSignal The amount of nSignal the nameCurator wants to burn + * @param _tokensOutMin Expected minimum amount of tokens to receive + */ + function burnSignal( + uint256 _subgraphID, + uint256 _nSignal, + uint256 _tokensOutMin + ) external override notPartialPaused { + // Subgraph checks + SubgraphData storage subgraphData = _getSubgraphOrRevert(_subgraphID); + + // Curator balance checks + address curator = msg.sender; + uint256 curatorNSignal = subgraphData.curatorNSignal[curator]; + require( + _nSignal <= curatorNSignal, + "GNS: Curator cannot withdraw more nSignal than they have" + ); + + // Get tokens for name signal amount to burn + uint256 vSignal = nSignalToVSignal(_subgraphID, _nSignal); + uint256 tokens = curation().burn(subgraphData.subgraphDeploymentID, vSignal, _tokensOutMin); + + // Update pools + subgraphData.vSignal = subgraphData.vSignal.sub(vSignal); + subgraphData.nSignal = subgraphData.nSignal.sub(_nSignal); + subgraphData.curatorNSignal[curator] = subgraphData.curatorNSignal[curator].sub(_nSignal); + + // Return the tokens to the nameCurator + require(graphToken().transfer(curator, tokens), "GNS: Error sending tokens"); + + emit SignalBurned(_subgraphID, curator, _nSignal, vSignal, tokens); + } + + /** + * @notice Move subgraph signal from sender to `_recipient` + * @param _subgraphID Subgraph ID + * @param _recipient Address to send the signal to + * @param _amount The amount of nSignal to transfer + */ + function transferSignal( + uint256 _subgraphID, + address _recipient, + uint256 _amount + ) external override notPartialPaused { + require(_recipient != address(0), "GNS: Curator cannot transfer to the zero address"); + + // Subgraph checks + SubgraphData storage subgraphData = _getSubgraphOrRevert(_subgraphID); + + // Balance checks + address curator = msg.sender; + uint256 curatorBalance = subgraphData.curatorNSignal[curator]; + require(curatorBalance >= _amount, "GNS: Curator transfer amount exceeds balance"); + + // Move the signal + subgraphData.curatorNSignal[curator] = subgraphData.curatorNSignal[curator].sub(_amount); + subgraphData.curatorNSignal[_recipient] = subgraphData.curatorNSignal[_recipient].add( + _amount + ); + + emit SignalTransferred(_subgraphID, curator, _recipient, _amount); + } + + /** + * @notice Withdraw tokens from a deprecated subgraph. + * When the subgraph is deprecated, any curator can call this function and + * withdraw the GRT they are entitled for its original deposit + * @param _subgraphID Subgraph ID + */ + function withdraw(uint256 _subgraphID) external override notPartialPaused { + // Subgraph validations + SubgraphData storage subgraphData = _getSubgraphData(_subgraphID); + require(subgraphData.disabled == true, "GNS: Must be disabled first"); + require(subgraphData.withdrawableGRT != 0, "GNS: No more GRT to withdraw"); + + // Curator validations + address curator = msg.sender; + uint256 curatorNSignal = subgraphData.curatorNSignal[curator]; + require(curatorNSignal != 0, "GNS: No signal to withdraw GRT"); + + // Get curator share of tokens to be withdrawn + uint256 tokensOut = curatorNSignal.mul(subgraphData.withdrawableGRT).div( + subgraphData.nSignal + ); + subgraphData.curatorNSignal[curator] = 0; + subgraphData.nSignal = subgraphData.nSignal.sub(curatorNSignal); + subgraphData.withdrawableGRT = subgraphData.withdrawableGRT.sub(tokensOut); + + // Return tokens to the curator + TokenUtils.pushTokens(graphToken(), curator, tokensOut); + + emit GRTWithdrawn(_subgraphID, curator, curatorNSignal, tokensOut); + } + + /** + * @notice Create subgraphID for legacy subgraph and mint ownership NFT. + * @param _graphAccount Account that created the subgraph + * @param _subgraphNumber The sequence number of the created subgraph + * @param _subgraphMetadata IPFS hash for the subgraph metadata + */ + function migrateLegacySubgraph( + address _graphAccount, + uint256 _subgraphNumber, + bytes32 _subgraphMetadata + ) external { + // Must be an existing legacy subgraph + bool legacySubgraphExists = legacySubgraphData[_graphAccount][_subgraphNumber] + .subgraphDeploymentID != 0; + require(legacySubgraphExists == true, "GNS: Subgraph does not exist"); + + // Must not be a claimed subgraph + uint256 subgraphID = _buildLegacySubgraphID(_graphAccount, _subgraphNumber); + require( + legacySubgraphKeys[subgraphID].account == address(0), + "GNS: Subgraph was already claimed" + ); + + // Store a reference for a legacy subgraph + legacySubgraphKeys[subgraphID] = IGNS.LegacySubgraphKey({ + account: _graphAccount, + accountSeqID: _subgraphNumber + }); + + // Delete state for legacy subgraph + legacySubgraphs[_graphAccount][_subgraphNumber] = 0; + + // Mint the NFT and send to owner + // The subgraph owner is the graph account that created it + _mintNFT(_graphAccount, subgraphID); + emit LegacySubgraphClaimed(_graphAccount, _subgraphNumber); + + // Set the token metadata + _setSubgraphMetadata(subgraphID, _subgraphMetadata); + } + + /** + * @notice Return the total signal on the subgraph. + * @param _subgraphID Subgraph ID + * @return Total signal on the subgraph + */ + function subgraphSignal(uint256 _subgraphID) external view override returns (uint256) { + return _getSubgraphData(_subgraphID).nSignal; + } + + /** + * @notice Return the total tokens on the subgraph at current value. + * @param _subgraphID Subgraph ID + * @return Total tokens on the subgraph + */ + function subgraphTokens(uint256 _subgraphID) external view override returns (uint256) { + uint256 signal = _getSubgraphData(_subgraphID).nSignal; + if (signal != 0) { + (, uint256 tokens) = nSignalToTokens(_subgraphID, signal); + return tokens; + } + return 0; + } + + /** + * @notice Return whether a subgraph is a legacy subgraph (created before subgraph NFTs). + * @param _subgraphID Subgraph ID + * @return Return true if subgraph is a legacy subgraph + */ + function isLegacySubgraph(uint256 _subgraphID) external view override returns (bool) { + (address account, ) = getLegacySubgraphKey(_subgraphID); + return account != address(0); + } + + /** + * @notice Calculate subgraph signal to be returned for an amount of tokens. + * @param _subgraphID Subgraph ID + * @param _tokensIn Tokens being exchanged for subgraph signal + * @return Amount of subgraph signal and curation tax + */ + function tokensToNSignal(uint256 _subgraphID, uint256 _tokensIn) + public + view + override + returns ( + uint256, + uint256, + uint256 + ) + { + SubgraphData storage subgraphData = _getSubgraphData(_subgraphID); + (uint256 vSignal, uint256 curationTax) = curation().tokensToSignal( + subgraphData.subgraphDeploymentID, + _tokensIn + ); + uint256 nSignal = vSignalToNSignal(_subgraphID, vSignal); + return (vSignal, nSignal, curationTax); + } + + /** + * @notice Calculate tokens returned for an amount of subgraph signal. + * @param _subgraphID Subgraph ID + * @param _nSignalIn Subgraph signal being exchanged for tokens + * @return Amount of tokens returned for an amount of subgraph signal + */ + function nSignalToTokens(uint256 _subgraphID, uint256 _nSignalIn) + public + view + override + returns (uint256, uint256) + { + // Get subgraph or revert if not published + // It does not make sense to convert signal from a disabled or non-existing one + SubgraphData storage subgraphData = _getSubgraphOrRevert(_subgraphID); + uint256 vSignal = nSignalToVSignal(_subgraphID, _nSignalIn); + uint256 tokensOut = curation().signalToTokens(subgraphData.subgraphDeploymentID, vSignal); + return (vSignal, tokensOut); + } + + /** + * @notice Calculate subgraph signal to be returned for an amount of subgraph deployment signal. + * @param _subgraphID Subgraph ID + * @param _vSignalIn Amount of subgraph deployment signal to exchange for subgraph signal + * @return Amount of subgraph signal that can be bought + */ + function vSignalToNSignal(uint256 _subgraphID, uint256 _vSignalIn) + public + view + override + returns (uint256) + { + SubgraphData storage subgraphData = _getSubgraphData(_subgraphID); + + // Handle initialization by using 1:1 version to name signal + if (subgraphData.vSignal == 0) { + return _vSignalIn; + } + + return subgraphData.nSignal.mul(_vSignalIn).div(subgraphData.vSignal); + } + + /** + * @notice Calculate subgraph deployment signal to be returned for an amount of subgraph signal. + * @param _subgraphID Subgraph ID + * @param _nSignalIn Subgraph signal being exchanged for subgraph deployment signal + * @return Amount of subgraph deployment signal that can be returned + */ + function nSignalToVSignal(uint256 _subgraphID, uint256 _nSignalIn) + public + view + override + returns (uint256) + { + SubgraphData storage subgraphData = _getSubgraphData(_subgraphID); + return subgraphData.vSignal.mul(_nSignalIn).div(subgraphData.nSignal); + } + + /** + * @notice Get the amount of subgraph signal a curator has. + * @param _subgraphID Subgraph ID + * @param _curator Curator address + * @return Amount of subgraph signal owned by a curator + */ + function getCuratorSignal(uint256 _subgraphID, address _curator) + public + view + override + returns (uint256) + { + return _getSubgraphData(_subgraphID).curatorNSignal[_curator]; + } + + /** + * @notice Return whether a subgraph is published. + * @param _subgraphID Subgraph ID + * @return Return true if subgraph is currently published + */ + function isPublished(uint256 _subgraphID) public view override returns (bool) { + return _isPublished(_getSubgraphData(_subgraphID)); + } + + /** + * @notice Returns account and sequence ID for a legacy subgraph (created before subgraph NFTs). + * @param _subgraphID Subgraph ID + * @return account Account that created the subgraph (or 0 if it's not a legacy subgraph) + * @return seqID Sequence number for the subgraph + */ + function getLegacySubgraphKey(uint256 _subgraphID) + public + view + override + returns (address account, uint256 seqID) + { + LegacySubgraphKey storage legacySubgraphKey = legacySubgraphKeys[_subgraphID]; + account = legacySubgraphKey.account; + seqID = legacySubgraphKey.accountSeqID; + } + + /** + * @notice Return the owner of a subgraph. + * @param _tokenID Subgraph ID + * @return Owner address + */ + function ownerOf(uint256 _tokenID) public view override returns (address) { + return subgraphNFT.ownerOf(_tokenID); + } + + /** + * @dev Calculate tax that owner will have to cover for upgrading or deprecating. + * @param _tokens Tokens that were received from deprecating the old subgraph + * @param _owner Subgraph owner + * @param _curationTaxPercentage Tax percentage on curation deposits from Curation contract + * @return Total tokens that will be sent to curation, _tokens + ownerTax + */ + function _chargeOwnerTax( + uint256 _tokens, + address _owner, + uint32 _curationTaxPercentage + ) internal returns (uint256) { + if (_curationTaxPercentage == 0 || ownerTaxPercentage == 0) { + return 0; + } + + // Tax on the total bonding curve funds + uint256 taxOnOriginal = _tokens.mul(_curationTaxPercentage).div(MAX_PPM); + // Total after the tax + uint256 totalWithoutOwnerTax = _tokens.sub(taxOnOriginal); + // The portion of tax that the owner will pay + uint256 ownerTax = taxOnOriginal.mul(ownerTaxPercentage).div(MAX_PPM); + + uint256 totalWithOwnerTax = totalWithoutOwnerTax.add(ownerTax); + + // The total after tax, plus owner partial repay, divided by + // the tax, to adjust it slightly upwards. ex: + // 100 GRT, 5 GRT Tax, owner pays 100% --> 5 GRT + // To get 100 in the protocol after tax, Owner deposits + // ~5.26, as ~105.26 * .95 = 100 + uint256 totalAdjustedUp = totalWithOwnerTax.mul(MAX_PPM).div( + uint256(MAX_PPM).sub(uint256(_curationTaxPercentage)) + ); + + uint256 ownerTaxAdjustedUp = totalAdjustedUp.sub(_tokens); + + // Get the owner of the subgraph to reimburse the curation tax + TokenUtils.pullTokens(graphToken(), _owner, ownerTaxAdjustedUp); + + return totalAdjustedUp; + } + + /** + * @dev Return the next subgraphID given the account that is creating the subgraph. + * NOTE: This function updates the sequence ID for the account + * @return Sequence ID for the account + */ + function _nextSubgraphID(address _account) internal returns (uint256) { + return _buildSubgraphID(_account, _nextAccountSeqID(_account)); + } + + /** + * @dev Return a new consecutive sequence ID for an account and update to the next value. + * NOTE: This function updates the sequence ID for the account + * @return Sequence ID for the account + */ + function _nextAccountSeqID(address _account) internal returns (uint256) { + uint256 seqID = nextAccountSeqID[_account]; + nextAccountSeqID[_account] = nextAccountSeqID[_account].add(1); + return seqID; + } + + /** + * @dev Mint the NFT for the subgraph. + * @param _owner Owner address + * @param _tokenID Subgraph ID + */ + function _mintNFT(address _owner, uint256 _tokenID) internal { + subgraphNFT.mint(_owner, _tokenID); + } + + /** + * @dev Burn the NFT for the subgraph. + * @param _tokenID Subgraph ID + */ + function _burnNFT(uint256 _tokenID) internal { + subgraphNFT.burn(_tokenID); + } + + /** + * @dev Set the subgraph metadata. + * @param _tokenID Subgraph ID + * @param _subgraphMetadata IPFS hash of the subgraph metadata + */ + function _setSubgraphMetadata(uint256 _tokenID, bytes32 _subgraphMetadata) internal { + subgraphNFT.setSubgraphMetadata(_tokenID, _subgraphMetadata); + + // Even if the following event is emitted in the NFT we emit it here to facilitate + // subgraph indexing + emit SubgraphMetadataUpdated(_tokenID, _subgraphMetadata); + } + + /** + * @dev Get subgraph data. + * This function will first look for a v1 subgraph and return it if found. + * @param _subgraphID Subgraph ID + * @return Subgraph Data + */ + function _getSubgraphData(uint256 _subgraphID) + internal + view + virtual + returns (SubgraphData storage) + { + // If there is a legacy subgraph created return it + LegacySubgraphKey storage legacySubgraphKey = legacySubgraphKeys[_subgraphID]; + if (legacySubgraphKey.account != address(0)) { + return legacySubgraphData[legacySubgraphKey.account][legacySubgraphKey.accountSeqID]; + } + // Return new subgraph type + return subgraphs[_subgraphID]; + } + + /** + * @dev Return whether a subgraph is published. + * @param _subgraphData Subgraph Data + * @return Return true if subgraph is currently published + */ + function _isPublished(SubgraphData storage _subgraphData) internal view returns (bool) { + return _subgraphData.subgraphDeploymentID != 0 && _subgraphData.disabled == false; + } + + /** + * @dev Return the subgraph data or revert if not published or deprecated. + * @param _subgraphID Subgraph ID + * @return Subgraph Data + */ + function _getSubgraphOrRevert(uint256 _subgraphID) + internal + view + returns (SubgraphData storage) + { + SubgraphData storage subgraphData = _getSubgraphData(_subgraphID); + require(_isPublished(subgraphData) == true, "GNS: Must be active"); + return subgraphData; + } + + /** + * @dev Build a subgraph ID based on the account creating it and a sequence number for that account. + * Only used for legacy subgraphs being migrated, as new ones will also use the chainid. + * Subgraph ID is the keccak hash of account+seqID + * @return Subgraph ID + */ + function _buildLegacySubgraphID(address _account, uint256 _seqID) + internal + pure + returns (uint256) + { + return uint256(keccak256(abi.encodePacked(_account, _seqID))); + } + + /** + * @dev Build a subgraph ID based on the account creating it and a sequence number for that account. + * Subgraph ID is the keccak hash of account+seqID + * @return Subgraph ID + */ + function _buildSubgraphID(address _account, uint256 _seqID) internal pure returns (uint256) { + uint256 chainId; + // Too bad solidity 0.7.6 still doesn't have block.chainid + // solhint-disable-next-line no-inline-assembly + assembly { + chainId := chainid() + } + return uint256(keccak256(abi.encodePacked(_account, _seqID, chainId))); + } + + /** + * @dev Internal: Set the owner tax percentage. This is used to prevent a subgraph owner to drain all + * the name curators tokens while upgrading or deprecating and is configurable in parts per million. + * @param _ownerTaxPercentage Owner tax percentage + */ + function _setOwnerTaxPercentage(uint32 _ownerTaxPercentage) private { + require(_ownerTaxPercentage <= MAX_PPM, "Owner tax must be MAX_PPM or less"); + ownerTaxPercentage = _ownerTaxPercentage; + emit ParameterUpdated("ownerTaxPercentage"); + } + + /** + * @dev Internal: Set the NFT registry contract + * @param _subgraphNFT Address of the ERC721 contract + */ + function _setSubgraphNFT(address _subgraphNFT) private { + require(_subgraphNFT != address(0), "NFT address cant be zero"); + require(AddressUpgradeable.isContract(_subgraphNFT), "NFT must be valid"); + + subgraphNFT = ISubgraphNFT(_subgraphNFT); + emit SubgraphNFTUpdated(_subgraphNFT); + } +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/discovery/GNSStorage.sol b/packages/legacy/verified/L1GNS/sources/contracts/discovery/GNSStorage.sol new file mode 100644 index 000000000..b8480c8e6 --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/discovery/GNSStorage.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; +pragma abicoder v2; + +import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol"; +import { Managed } from "../governance/Managed.sol"; + +import { IEthereumDIDRegistry } from "./erc1056/IEthereumDIDRegistry.sol"; +import { IGNS } from "./IGNS.sol"; +import { ISubgraphNFT } from "./ISubgraphNFT.sol"; + +/** + * @title GNSV1Storage + * @notice This contract holds all the storage variables for the GNS contract, version 1 + */ +abstract contract GNSV1Storage is Managed { + // -- State -- + + /// Percentage of curation tax that must be paid by the owner, in parts per million. + uint32 public ownerTaxPercentage; + + /// [DEPRECATED] Bonding curve formula. + address public __DEPRECATED_bondingCurve; // solhint-disable-line var-name-mixedcase + + /// @dev Stores what subgraph deployment a particular legacy subgraph targets. + /// A subgraph is defined by (graphAccountID, subgraphNumber). + /// A subgraph can target one subgraph deployment (bytes32 hash). + /// (graphAccountID, subgraphNumber) => subgraphDeploymentID + mapping(address => mapping(uint256 => bytes32)) internal legacySubgraphs; + + /// Every time an account creates a subgraph it increases a per-account sequence ID. + /// account => seqID + mapping(address => uint256) public nextAccountSeqID; + + /// Stores all the signal deposited on a legacy subgraph. + /// (graphAccountID, subgraphNumber) => SubgraphData + mapping(address => mapping(uint256 => IGNS.SubgraphData)) public legacySubgraphData; + + /// @dev [DEPRECATED] ERC-1056 contract reference. + /// This contract was used for managing identities. + IEthereumDIDRegistry private __DEPRECATED_erc1056Registry; // solhint-disable-line var-name-mixedcase +} + +/** + * @title GNSV2Storage + * @notice This contract holds all the storage variables for the GNS contract, version 2 + */ +abstract contract GNSV2Storage is GNSV1Storage { + /// Stores the account and seqID for a legacy subgraph that has been migrated. + /// Use it whenever a legacy (v1) subgraph NFT was claimed to maintain compatibility. + /// Keep a reference from subgraphID => (graphAccount, subgraphNumber) + mapping(uint256 => IGNS.LegacySubgraphKey) public legacySubgraphKeys; + + /// Store data for all NFT-based (v2) subgraphs. + /// subgraphID => SubgraphData + mapping(uint256 => IGNS.SubgraphData) public subgraphs; + + /// Contract that represents subgraph ownership through an NFT + ISubgraphNFT public subgraphNFT; +} + +/** + * @title GNSV3Storage + * @notice This contract holds all the storage variables for the base GNS contract, version 3. + * @dev Note that this is the first version that includes a storage gap - if adding + * future versions, make sure to move the gap to the new version and + * reduce the size of the gap accordingly. + */ +abstract contract GNSV3Storage is GNSV2Storage, Initializable { + /// Address of the counterpart GNS contract (L1GNS/L2GNS) + address public counterpartGNSAddress; + /// @dev Gap to allow adding variables in future upgrades (since L1GNS and L2GNS have their own storage as well) + uint256[50] private __gap; +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/discovery/IGNS.sol b/packages/legacy/verified/L1GNS/sources/contracts/discovery/IGNS.sol new file mode 100644 index 000000000..80cc99820 --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/discovery/IGNS.sol @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +/** + * @title Interface for GNS + */ +interface IGNS { + // -- Pool -- + + /** + * @dev The SubgraphData struct holds information about subgraphs + * and their signal; both nSignal (i.e. name signal at the GNS level) + * and vSignal (i.e. version signal at the Curation contract level) + */ + struct SubgraphData { + uint256 vSignal; // The token of the subgraph-deployment bonding curve + uint256 nSignal; // The token of the subgraph bonding curve + mapping(address => uint256) curatorNSignal; + bytes32 subgraphDeploymentID; + uint32 reserveRatioDeprecated; // Ratio for the bonding curve, always 1 in PPM, deprecated. + bool disabled; + uint256 withdrawableGRT; + } + + /** + * @dev The LegacySubgraphKey struct holds the account and sequence ID + * used to generate subgraph IDs in legacy subgraphs. + */ + struct LegacySubgraphKey { + address account; + uint256 accountSeqID; + } + + // -- Configuration -- + + /** + * @notice Approve curation contract to pull funds. + */ + function approveAll() external; + + /** + * @notice Set the owner fee percentage. This is used to prevent a subgraph owner to drain all + * the name curators tokens while upgrading or deprecating and is configurable in parts per million. + * @param _ownerTaxPercentage Owner tax percentage + */ + function setOwnerTaxPercentage(uint32 _ownerTaxPercentage) external; + + // -- Publishing -- + + /** + * @notice Allows a graph account to set a default name + * @param _graphAccount Account that is setting its name + * @param _nameSystem Name system account already has ownership of a name in + * @param _nameIdentifier The unique identifier that is used to identify the name in the system + * @param _name The name being set as default + */ + function setDefaultName( + address _graphAccount, + uint8 _nameSystem, + bytes32 _nameIdentifier, + string calldata _name + ) external; + + /** + * @notice Allows a subgraph owner to update the metadata of a subgraph they have published + * @param _subgraphID Subgraph ID + * @param _subgraphMetadata IPFS hash for the subgraph metadata + */ + function updateSubgraphMetadata(uint256 _subgraphID, bytes32 _subgraphMetadata) external; + + /** + * @notice Publish a new subgraph. + * @param _subgraphDeploymentID Subgraph deployment for the subgraph + * @param _versionMetadata IPFS hash for the subgraph version metadata + * @param _subgraphMetadata IPFS hash for the subgraph metadata + */ + function publishNewSubgraph( + bytes32 _subgraphDeploymentID, + bytes32 _versionMetadata, + bytes32 _subgraphMetadata + ) external; + + /** + * @notice Publish a new version of an existing subgraph. + * @param _subgraphID Subgraph ID + * @param _subgraphDeploymentID Subgraph deployment ID of the new version + * @param _versionMetadata IPFS hash for the subgraph version metadata + */ + function publishNewVersion( + uint256 _subgraphID, + bytes32 _subgraphDeploymentID, + bytes32 _versionMetadata + ) external; + + /** + * @notice Deprecate a subgraph. The bonding curve is destroyed, the vSignal is burned, and the GNS + * contract holds the GRT from burning the vSignal, which all curators can withdraw manually. + * Can only be done by the subgraph owner. + * @param _subgraphID Subgraph ID + */ + function deprecateSubgraph(uint256 _subgraphID) external; + + // -- Curation -- + + /** + * @notice Deposit GRT into a subgraph and mint signal. + * @param _subgraphID Subgraph ID + * @param _tokensIn The amount of tokens the nameCurator wants to deposit + * @param _nSignalOutMin Expected minimum amount of name signal to receive + */ + function mintSignal( + uint256 _subgraphID, + uint256 _tokensIn, + uint256 _nSignalOutMin + ) external; + + /** + * @notice Burn signal for a subgraph and return the GRT. + * @param _subgraphID Subgraph ID + * @param _nSignal The amount of nSignal the nameCurator wants to burn + * @param _tokensOutMin Expected minimum amount of tokens to receive + */ + function burnSignal( + uint256 _subgraphID, + uint256 _nSignal, + uint256 _tokensOutMin + ) external; + + /** + * @notice Move subgraph signal from sender to `_recipient` + * @param _subgraphID Subgraph ID + * @param _recipient Address to send the signal to + * @param _amount The amount of nSignal to transfer + */ + function transferSignal( + uint256 _subgraphID, + address _recipient, + uint256 _amount + ) external; + + /** + * @notice Withdraw tokens from a deprecated subgraph. + * When the subgraph is deprecated, any curator can call this function and + * withdraw the GRT they are entitled for its original deposit + * @param _subgraphID Subgraph ID + */ + function withdraw(uint256 _subgraphID) external; + + // -- Getters -- + + /** + * @notice Return the owner of a subgraph. + * @param _tokenID Subgraph ID + * @return Owner address + */ + function ownerOf(uint256 _tokenID) external view returns (address); + + /** + * @notice Return the total signal on the subgraph. + * @param _subgraphID Subgraph ID + * @return Total signal on the subgraph + */ + function subgraphSignal(uint256 _subgraphID) external view returns (uint256); + + /** + * @notice Return the total tokens on the subgraph at current value. + * @param _subgraphID Subgraph ID + * @return Total tokens on the subgraph + */ + function subgraphTokens(uint256 _subgraphID) external view returns (uint256); + + /** + * @notice Calculate subgraph signal to be returned for an amount of tokens. + * @param _subgraphID Subgraph ID + * @param _tokensIn Tokens being exchanged for subgraph signal + * @return Amount of subgraph signal and curation tax + */ + function tokensToNSignal(uint256 _subgraphID, uint256 _tokensIn) + external + view + returns ( + uint256, + uint256, + uint256 + ); + + /** + * @notice Calculate tokens returned for an amount of subgraph signal. + * @param _subgraphID Subgraph ID + * @param _nSignalIn Subgraph signal being exchanged for tokens + * @return Amount of tokens returned for an amount of subgraph signal + */ + function nSignalToTokens(uint256 _subgraphID, uint256 _nSignalIn) + external + view + returns (uint256, uint256); + + /** + * @notice Calculate subgraph signal to be returned for an amount of subgraph deployment signal. + * @param _subgraphID Subgraph ID + * @param _vSignalIn Amount of subgraph deployment signal to exchange for subgraph signal + * @return Amount of subgraph signal that can be bought + */ + function vSignalToNSignal(uint256 _subgraphID, uint256 _vSignalIn) + external + view + returns (uint256); + + /** + * @notice Calculate subgraph deployment signal to be returned for an amount of subgraph signal. + * @param _subgraphID Subgraph ID + * @param _nSignalIn Subgraph signal being exchanged for subgraph deployment signal + * @return Amount of subgraph deployment signal that can be returned + */ + function nSignalToVSignal(uint256 _subgraphID, uint256 _nSignalIn) + external + view + returns (uint256); + + /** + * @notice Get the amount of subgraph signal a curator has. + * @param _subgraphID Subgraph ID + * @param _curator Curator address + * @return Amount of subgraph signal owned by a curator + */ + function getCuratorSignal(uint256 _subgraphID, address _curator) + external + view + returns (uint256); + + /** + * @notice Return whether a subgraph is published. + * @param _subgraphID Subgraph ID + * @return Return true if subgraph is currently published + */ + function isPublished(uint256 _subgraphID) external view returns (bool); + + /** + * @notice Return whether a subgraph is a legacy subgraph (created before subgraph NFTs). + * @param _subgraphID Subgraph ID + * @return Return true if subgraph is a legacy subgraph + */ + function isLegacySubgraph(uint256 _subgraphID) external view returns (bool); + + /** + * @notice Returns account and sequence ID for a legacy subgraph (created before subgraph NFTs). + * @param _subgraphID Subgraph ID + * @return account Account that created the subgraph (or 0 if it's not a legacy subgraph) + * @return seqID Sequence number for the subgraph + */ + function getLegacySubgraphKey(uint256 _subgraphID) + external + view + returns (address account, uint256 seqID); +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/discovery/ISubgraphNFT.sol b/packages/legacy/verified/L1GNS/sources/contracts/discovery/ISubgraphNFT.sol new file mode 100644 index 000000000..4b0495a28 --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/discovery/ISubgraphNFT.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; + +interface ISubgraphNFT is IERC721 { + // -- Config -- + + function setMinter(address _minter) external; + + function setTokenDescriptor(address _tokenDescriptor) external; + + function setBaseURI(string memory _baseURI) external; + + // -- Actions -- + + function mint(address _to, uint256 _tokenId) external; + + function burn(uint256 _tokenId) external; + + function setSubgraphMetadata(uint256 _tokenId, bytes32 _subgraphMetadata) external; + + function tokenURI(uint256 _tokenId) external view returns (string memory); +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/discovery/L1GNS.sol b/packages/legacy/verified/L1GNS/sources/contracts/discovery/L1GNS.sol new file mode 100644 index 000000000..01dce4967 --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/discovery/L1GNS.sol @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; +pragma abicoder v2; + +import { SafeMathUpgradeable } from "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; + +import { GNS } from "./GNS.sol"; + +import { ITokenGateway } from "../arbitrum/ITokenGateway.sol"; +import { IL2GNS } from "../l2/discovery/IL2GNS.sol"; +import { IGraphToken } from "../token/IGraphToken.sol"; +import { L1GNSV1Storage } from "./L1GNSStorage.sol"; + +/** + * @title L1GNS + * @dev The Graph Name System contract provides a decentralized naming system for subgraphs + * used in the scope of the Graph Network. It translates Subgraphs into Subgraph Versions. + * Each version is associated with a Subgraph Deployment. The contract has no knowledge of + * human-readable names. All human readable names emitted in events. + * The contract implements a multicall behaviour to support batching multiple calls in a single + * transaction. + * This L1GNS variant includes some functions to allow transferring subgraphs to L2. + */ +contract L1GNS is GNS, L1GNSV1Storage { + using SafeMathUpgradeable for uint256; + + /// @dev Emitted when a subgraph was sent to L2 through the bridge + event SubgraphSentToL2( + uint256 indexed _subgraphID, + address indexed _l1Owner, + address indexed _l2Owner, + uint256 _tokens + ); + + /// @dev Emitted when a curator's balance for a subgraph was sent to L2 + event CuratorBalanceSentToL2( + uint256 indexed _subgraphID, + address indexed _l1Curator, + address indexed _l2Beneficiary, + uint256 _tokens + ); + + /** + * @notice Send a subgraph's data and tokens to L2. + * Use the Arbitrum SDK to estimate the L2 retryable ticket parameters. + * Note that any L2 gas/fee refunds will be lost, so the function only accepts + * the exact amount of ETH to cover _maxSubmissionCost + _maxGas * _gasPriceBid. + * @param _subgraphID Subgraph ID + * @param _l2Owner Address that will own the subgraph in L2 (could be the L1 owner, but could be different if the L1 owner is an L1 contract) + * @param _maxGas Max gas to use for the L2 retryable ticket + * @param _gasPriceBid Gas price bid for the L2 retryable ticket + * @param _maxSubmissionCost Max submission cost for the L2 retryable ticket + */ + function sendSubgraphToL2( + uint256 _subgraphID, + address _l2Owner, + uint256 _maxGas, + uint256 _gasPriceBid, + uint256 _maxSubmissionCost + ) external payable notPartialPaused { + require(!subgraphTransferredToL2[_subgraphID], "ALREADY_DONE"); + require( + msg.value == _maxSubmissionCost.add(_maxGas.mul(_gasPriceBid)), + "INVALID_ETH_VALUE" + ); + + SubgraphData storage subgraphData = _getSubgraphOrRevert(_subgraphID); + // This is just like onlySubgraphAuth, but we want it to run after the subgraphTransferredToL2 check + // to revert with a nicer message in that case: + require(ownerOf(_subgraphID) == msg.sender, "GNS: Must be authorized"); + subgraphTransferredToL2[_subgraphID] = true; + + uint256 curationTokens = curation().burn( + subgraphData.subgraphDeploymentID, + subgraphData.vSignal, + 0 + ); + subgraphData.disabled = true; + subgraphData.vSignal = 0; + + // We send only the subgraph owner's tokens and nsignal to L2, + // and for everyone else we set the withdrawableGRT so that they can choose + // to withdraw or transfer their signal. + uint256 ownerNSignal = subgraphData.curatorNSignal[msg.sender]; + uint256 totalSignal = subgraphData.nSignal; + + // Get owner share of tokens to be sent to L2 + uint256 tokensForL2 = ownerNSignal.mul(curationTokens).div(totalSignal); + // This leaves the subgraph as if it was deprecated, + // so other curators can withdraw: + subgraphData.curatorNSignal[msg.sender] = 0; + subgraphData.nSignal = totalSignal.sub(ownerNSignal); + subgraphData.withdrawableGRT = curationTokens.sub(tokensForL2); + + bytes memory extraData = abi.encode( + uint8(IL2GNS.L1MessageCodes.RECEIVE_SUBGRAPH_CODE), + _subgraphID, + _l2Owner + ); + + _sendTokensAndMessageToL2GNS( + tokensForL2, + _maxGas, + _gasPriceBid, + _maxSubmissionCost, + extraData + ); + + subgraphData.reserveRatioDeprecated = 0; + _burnNFT(_subgraphID); + emit SubgraphSentToL2(_subgraphID, msg.sender, _l2Owner, tokensForL2); + } + + /** + * @notice Send the balance for a curator's signal in a subgraph that was + * transferred to L2, using the L1GraphTokenGateway. + * The balance will be claimed for a beneficiary address, as this method can be + * used by curators that use a contract address in L1 that may not exist in L2. + * This will set the curator's signal on L1 to zero, so the caller must ensure + * that the retryable ticket is redeemed before expiration, or the signal will be lost. + * It is up to the caller to verify that the subgraph transfer was finished in L2, + * but if it wasn't, the tokens will be sent to the beneficiary in L2. + * Note that any L2 gas/fee refunds will be lost, so the function only accepts + * the exact amount of ETH to cover _maxSubmissionCost + _maxGas * _gasPriceBid. + * @dev Use the Arbitrum SDK to estimate the L2 retryable ticket parameters. + * @param _subgraphID Subgraph ID + * @param _beneficiary Address that will receive the tokens in L2 + * @param _maxGas Max gas to use for the L2 retryable ticket + * @param _gasPriceBid Gas price bid for the L2 retryable ticket + * @param _maxSubmissionCost Max submission cost for the L2 retryable ticket + */ + function sendCuratorBalanceToBeneficiaryOnL2( + uint256 _subgraphID, + address _beneficiary, + uint256 _maxGas, + uint256 _gasPriceBid, + uint256 _maxSubmissionCost + ) external payable notPartialPaused { + require(subgraphTransferredToL2[_subgraphID], "!TRANSFERRED"); + require( + msg.value == _maxSubmissionCost.add(_maxGas.mul(_gasPriceBid)), + "INVALID_ETH_VALUE" + ); + // The Arbitrum bridge will check this too, we just check here for an early exit + require(_maxSubmissionCost != 0, "NO_SUBMISSION_COST"); + + SubgraphData storage subgraphData = _getSubgraphData(_subgraphID); + uint256 curatorNSignal = subgraphData.curatorNSignal[msg.sender]; + require(curatorNSignal != 0, "NO_SIGNAL"); + uint256 subgraphNSignal = subgraphData.nSignal; + require(subgraphNSignal != 0, "NO_SUBGRAPH_SIGNAL"); + + uint256 withdrawableGRT = subgraphData.withdrawableGRT; + uint256 tokensForL2 = curatorNSignal.mul(withdrawableGRT).div(subgraphNSignal); + bytes memory extraData = abi.encode( + uint8(IL2GNS.L1MessageCodes.RECEIVE_CURATOR_BALANCE_CODE), + _subgraphID, + _beneficiary + ); + + // Set the subgraph as if the curator had withdrawn their tokens + subgraphData.curatorNSignal[msg.sender] = 0; + subgraphData.nSignal = subgraphNSignal.sub(curatorNSignal); + subgraphData.withdrawableGRT = withdrawableGRT.sub(tokensForL2); + + // Send the tokens and data to L2 using the L1GraphTokenGateway + _sendTokensAndMessageToL2GNS( + tokensForL2, + _maxGas, + _gasPriceBid, + _maxSubmissionCost, + extraData + ); + emit CuratorBalanceSentToL2(_subgraphID, msg.sender, _beneficiary, tokensForL2); + } + + /** + * @notice Sends a message to the L2GNS with some extra data, + * also sending some tokens, using the L1GraphTokenGateway. + * @param _tokens Amount of tokens to send to L2 + * @param _maxGas Max gas to use for the L2 retryable ticket + * @param _gasPriceBid Gas price bid for the L2 retryable ticket + * @param _maxSubmissionCost Max submission cost for the L2 retryable ticket + * @param _extraData Extra data for the callhook on L2GNS + */ + function _sendTokensAndMessageToL2GNS( + uint256 _tokens, + uint256 _maxGas, + uint256 _gasPriceBid, + uint256 _maxSubmissionCost, + bytes memory _extraData + ) internal { + bytes memory data = abi.encode(_maxSubmissionCost, _extraData); + IGraphToken grt = graphToken(); + ITokenGateway gateway = graphTokenGateway(); + grt.approve(address(gateway), _tokens); + gateway.outboundTransfer{ value: msg.value }( + address(grt), + counterpartGNSAddress, + _tokens, + _maxGas, + _gasPriceBid, + data + ); + } +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/discovery/L1GNSStorage.sol b/packages/legacy/verified/L1GNS/sources/contracts/discovery/L1GNSStorage.sol new file mode 100644 index 000000000..557814513 --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/discovery/L1GNSStorage.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; +pragma abicoder v2; + +/** + * @title L1GNSV1Storage + * @notice This contract holds all the L1-specific storage variables for the L1GNS contract, version 1 + * @dev When adding new versions, make sure to move the gap to the new version and + * reduce the size of the gap accordingly. + */ +abstract contract L1GNSV1Storage { + /// True for subgraph IDs that have been transferred to L2 + mapping(uint256 => bool) public subgraphTransferredToL2; + /// @dev Storage gap to keep storage slots fixed in future versions + uint256[50] private __gap; +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/discovery/erc1056/IEthereumDIDRegistry.sol b/packages/legacy/verified/L1GNS/sources/contracts/discovery/erc1056/IEthereumDIDRegistry.sol new file mode 100644 index 000000000..10a383f85 --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/discovery/erc1056/IEthereumDIDRegistry.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.7.6; + +interface IEthereumDIDRegistry { + function identityOwner(address identity) external view returns (address); + + function setAttribute( + address identity, + bytes32 name, + bytes calldata value, + uint256 validity + ) external; +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/epochs/IEpochManager.sol b/packages/legacy/verified/L1GNS/sources/contracts/epochs/IEpochManager.sol new file mode 100644 index 000000000..36b1f47a3 --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/epochs/IEpochManager.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +interface IEpochManager { + // -- Configuration -- + + function setEpochLength(uint256 _epochLength) external; + + // -- Epochs + + function runEpoch() external; + + // -- Getters -- + + function isCurrentEpochRun() external view returns (bool); + + function blockNum() external view returns (uint256); + + function blockHash(uint256 _block) external view returns (bytes32); + + function currentEpoch() external view returns (uint256); + + function currentEpochBlock() external view returns (uint256); + + function currentEpochBlockSinceStart() external view returns (uint256); + + function epochsSince(uint256 _epoch) external view returns (uint256); + + function epochsSinceUpdate() external view returns (uint256); +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/gateway/ICallhookReceiver.sol b/packages/legacy/verified/L1GNS/sources/contracts/gateway/ICallhookReceiver.sol new file mode 100644 index 000000000..ff0fbfab1 --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/gateway/ICallhookReceiver.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/** + * @title Interface for contracts that can receive callhooks through the Arbitrum GRT bridge + * @dev Any contract that can receive a callhook on L2, sent through the bridge from L1, must + * be allowlisted by the governor, but also implement this interface that contains + * the function that will actually be called by the L2GraphTokenGateway. + */ +pragma solidity ^0.7.6; + +interface ICallhookReceiver { + /** + * @notice Receive tokens with a callhook from the bridge + * @param _from Token sender in L1 + * @param _amount Amount of tokens that were transferred + * @param _data ABI-encoded callhook data + */ + function onTokenTransfer( + address _from, + uint256 _amount, + bytes calldata _data + ) external; +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/governance/IController.sol b/packages/legacy/verified/L1GNS/sources/contracts/governance/IController.sol new file mode 100644 index 000000000..7df3d94ee --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/governance/IController.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; + +interface IController { + function getGovernor() external view returns (address); + + // -- Registry -- + + function setContractProxy(bytes32 _id, address _contractAddress) external; + + function unsetContractProxy(bytes32 _id) external; + + function updateController(bytes32 _id, address _controller) external; + + function getContractProxy(bytes32 _id) external view returns (address); + + // -- Pausing -- + + function setPartialPaused(bool _partialPaused) external; + + function setPaused(bool _paused) external; + + function setPauseGuardian(address _newPauseGuardian) external; + + function paused() external view returns (bool); + + function partialPaused() external view returns (bool); +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/governance/IManaged.sol b/packages/legacy/verified/L1GNS/sources/contracts/governance/IManaged.sol new file mode 100644 index 000000000..76f05e0fb --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/governance/IManaged.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import { IController } from "./IController.sol"; + +/** + * @title Managed Interface + * @dev Interface for contracts that can be managed by a controller. + */ +interface IManaged { + /** + * @notice Set the controller that manages this contract + * @dev Only the current controller can set a new controller + * @param _controller Address of the new controller + */ + function setController(address _controller) external; + + /** + * @notice Sync protocol contract addresses from the Controller registry + * @dev This function will cache all the contracts using the latest addresses. + * Anyone can call the function whenever a Proxy contract change in the + * controller to ensure the protocol is using the latest version. + */ + function syncAllContracts() external; + + /** + * @notice Get the Controller that manages this contract + * @return The Controller as an IController interface + */ + function controller() external view returns (IController); +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/governance/Managed.sol b/packages/legacy/verified/L1GNS/sources/contracts/governance/Managed.sol new file mode 100644 index 000000000..9b0ea29c8 --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/governance/Managed.sol @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import { IController } from "./IController.sol"; + +import { ICuration } from "../curation/ICuration.sol"; +import { IEpochManager } from "../epochs/IEpochManager.sol"; +import { IRewardsManager } from "../rewards/IRewardsManager.sol"; +import { IStaking } from "../staking/IStaking.sol"; +import { IGraphToken } from "../token/IGraphToken.sol"; +import { ITokenGateway } from "../arbitrum/ITokenGateway.sol"; +import { IGNS } from "../discovery/IGNS.sol"; + +import { IManaged } from "./IManaged.sol"; + +/** + * @title Graph Managed contract + * @dev The Managed contract provides an interface to interact with the Controller. + * It also provides local caching for contract addresses. This mechanism relies on calling the + * public `syncAllContracts()` function whenever a contract changes in the controller. + * + * Inspired by Livepeer: + * https://github.com/livepeer/protocol/blob/streamflow/contracts/Controller.sol + */ +abstract contract Managed is IManaged { + // -- State -- + + /// Controller that manages this contract + IController public override controller; + /// @dev Cache for the addresses of the contracts retrieved from the controller + mapping(bytes32 => address) private _addressCache; + /// @dev Gap for future storage variables + uint256[10] private __gap; + + // Immutables + bytes32 private immutable CURATION = keccak256("Curation"); + bytes32 private immutable EPOCH_MANAGER = keccak256("EpochManager"); + bytes32 private immutable REWARDS_MANAGER = keccak256("RewardsManager"); + bytes32 private immutable STAKING = keccak256("Staking"); + bytes32 private immutable GRAPH_TOKEN = keccak256("GraphToken"); + bytes32 private immutable GRAPH_TOKEN_GATEWAY = keccak256("GraphTokenGateway"); + bytes32 private immutable GNS = keccak256("GNS"); + + // -- Events -- + + /// Emitted when a contract parameter has been updated + event ParameterUpdated(string param); + /// Emitted when the controller address has been set + event SetController(address controller); + + /// Emitted when contract with `nameHash` is synced to `contractAddress`. + event ContractSynced(bytes32 indexed nameHash, address contractAddress); + + // -- Modifiers -- + + /** + * @dev Revert if the controller is paused or partially paused + */ + function _notPartialPaused() internal view { + require(!controller.paused(), "Paused"); + require(!controller.partialPaused(), "Partial-paused"); + } + + /** + * @dev Revert if the controller is paused + */ + function _notPaused() internal view virtual { + require(!controller.paused(), "Paused"); + } + + /** + * @dev Revert if the caller is not the governor + */ + function _onlyGovernor() internal view { + require(msg.sender == controller.getGovernor(), "Only Controller governor"); + } + + /** + * @dev Revert if the caller is not the Controller + */ + function _onlyController() internal view { + require(msg.sender == address(controller), "Caller must be Controller"); + } + + /** + * @dev Revert if the controller is paused or partially paused + */ + modifier notPartialPaused() { + _notPartialPaused(); + _; + } + + /** + * @dev Revert if the controller is paused + */ + modifier notPaused() { + _notPaused(); + _; + } + + /** + * @dev Revert if the caller is not the Controller + */ + modifier onlyController() { + _onlyController(); + _; + } + + /** + * @dev Revert if the caller is not the governor + */ + modifier onlyGovernor() { + _onlyGovernor(); + _; + } + + // -- Functions -- + + /** + * @dev Initialize a Managed contract + * @param _controller Address for the Controller that manages this contract + */ + function _initialize(address _controller) internal { + _setController(_controller); + } + + /** + * @notice Set Controller. Only callable by current controller. + * @param _controller Controller contract address + */ + function setController(address _controller) external override onlyController { + _setController(_controller); + } + + /** + * @dev Set controller. + * @param _controller Controller contract address + */ + function _setController(address _controller) internal { + require(_controller != address(0), "Controller must be set"); + controller = IController(_controller); + emit SetController(_controller); + } + + /** + * @dev Return Curation interface + * @return Curation contract registered with Controller + */ + function curation() internal view returns (ICuration) { + return ICuration(_resolveContract(CURATION)); + } + + /** + * @dev Return EpochManager interface + * @return Epoch manager contract registered with Controller + */ + function epochManager() internal view returns (IEpochManager) { + return IEpochManager(_resolveContract(EPOCH_MANAGER)); + } + + /** + * @dev Return RewardsManager interface + * @return Rewards manager contract registered with Controller + */ + function rewardsManager() internal view returns (IRewardsManager) { + return IRewardsManager(_resolveContract(REWARDS_MANAGER)); + } + + /** + * @dev Return Staking interface + * @return Staking contract registered with Controller + */ + function staking() internal view returns (IStaking) { + return IStaking(_resolveContract(STAKING)); + } + + /** + * @dev Return GraphToken interface + * @return Graph token contract registered with Controller + */ + function graphToken() internal view returns (IGraphToken) { + return IGraphToken(_resolveContract(GRAPH_TOKEN)); + } + + /** + * @dev Return GraphTokenGateway (L1 or L2) interface + * @return Graph token gateway contract registered with Controller + */ + function graphTokenGateway() internal view returns (ITokenGateway) { + return ITokenGateway(_resolveContract(GRAPH_TOKEN_GATEWAY)); + } + + /** + * @dev Return GNS (L1 or L2) interface. + * @return Address of the GNS contract registered with Controller, as an IGNS interface. + */ + function gns() internal view returns (IGNS) { + return IGNS(_resolveContract(GNS)); + } + + /** + * @dev Resolve a contract address from the cache or the Controller if not found. + * @param _nameHash keccak256 hash of the contract name + * @return Address of the contract + */ + function _resolveContract(bytes32 _nameHash) internal view returns (address) { + address contractAddress = _addressCache[_nameHash]; + if (contractAddress == address(0)) { + contractAddress = controller.getContractProxy(_nameHash); + } + return contractAddress; + } + + /** + * @dev Cache a contract address from the Controller registry. + * @param _nameHash keccak256 hash of the name of the contract to sync into the cache + */ + function _syncContract(bytes32 _nameHash) internal { + address contractAddress = controller.getContractProxy(_nameHash); + if (_addressCache[_nameHash] != contractAddress) { + _addressCache[_nameHash] = contractAddress; + emit ContractSynced(_nameHash, contractAddress); + } + } + + /** + * @notice Sync protocol contract addresses from the Controller registry + * @dev This function will cache all the contracts using the latest addresses + * Anyone can call the function whenever a Proxy contract change in the + * controller to ensure the protocol is using the latest version + */ + function syncAllContracts() external override { + _syncContract(CURATION); + _syncContract(EPOCH_MANAGER); + _syncContract(REWARDS_MANAGER); + _syncContract(STAKING); + _syncContract(GRAPH_TOKEN); + _syncContract(GRAPH_TOKEN_GATEWAY); + _syncContract(GNS); + } +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/l2/discovery/IL2GNS.sol b/packages/legacy/verified/L1GNS/sources/contracts/l2/discovery/IL2GNS.sol new file mode 100644 index 000000000..3a8763274 --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/l2/discovery/IL2GNS.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import { ICallhookReceiver } from "../../gateway/ICallhookReceiver.sol"; + +/** + * @title Interface for the L2GNS contract. + */ +interface IL2GNS is ICallhookReceiver { + enum L1MessageCodes { + RECEIVE_SUBGRAPH_CODE, + RECEIVE_CURATOR_BALANCE_CODE + } + + /** + * @dev The SubgraphL2TransferData struct holds information + * about a subgraph related to its transfer from L1 to L2. + */ + struct SubgraphL2TransferData { + uint256 tokens; // GRT that will be sent to L2 to mint signal + mapping(address => bool) curatorBalanceClaimed; // True for curators whose balance has been claimed in L2 + bool l2Done; // Transfer finished on L2 side + uint256 subgraphReceivedOnL2BlockNumber; // Block number when the subgraph was received on L2 + } + + /** + * @notice Finish a subgraph transfer from L1. + * The subgraph must have been previously sent through the bridge + * using the sendSubgraphToL2 function on L1GNS. + * @param _l2SubgraphID Subgraph ID in L2 (aliased from the L1 subgraph ID) + * @param _subgraphDeploymentID Latest subgraph deployment to assign to the subgraph + * @param _subgraphMetadata IPFS hash of the subgraph metadata + * @param _versionMetadata IPFS hash of the version metadata + */ + function finishSubgraphTransferFromL1( + uint256 _l2SubgraphID, + bytes32 _subgraphDeploymentID, + bytes32 _subgraphMetadata, + bytes32 _versionMetadata + ) external; + + /** + * @notice Return the aliased L2 subgraph ID from a transferred L1 subgraph ID + * @param _l1SubgraphID L1 subgraph ID + * @return L2 subgraph ID + */ + function getAliasedL2SubgraphID(uint256 _l1SubgraphID) external pure returns (uint256); +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/rewards/IRewardsManager.sol b/packages/legacy/verified/L1GNS/sources/contracts/rewards/IRewardsManager.sol new file mode 100644 index 000000000..87d3c2455 --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/rewards/IRewardsManager.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +interface IRewardsManager { + /** + * @dev Stores accumulated rewards and snapshots related to a particular SubgraphDeployment. + */ + struct Subgraph { + uint256 accRewardsForSubgraph; + uint256 accRewardsForSubgraphSnapshot; + uint256 accRewardsPerSignalSnapshot; + uint256 accRewardsPerAllocatedToken; + } + + // -- Config -- + + function setIssuancePerBlock(uint256 _issuancePerBlock) external; + + function setMinimumSubgraphSignal(uint256 _minimumSubgraphSignal) external; + + // -- Denylist -- + + function setSubgraphAvailabilityOracle(address _subgraphAvailabilityOracle) external; + + function setDenied(bytes32 _subgraphDeploymentID, bool _deny) external; + + function setDeniedMany(bytes32[] calldata _subgraphDeploymentID, bool[] calldata _deny) + external; + + function isDenied(bytes32 _subgraphDeploymentID) external view returns (bool); + + // -- Getters -- + + function getNewRewardsPerSignal() external view returns (uint256); + + function getAccRewardsPerSignal() external view returns (uint256); + + function getAccRewardsForSubgraph(bytes32 _subgraphDeploymentID) + external + view + returns (uint256); + + function getAccRewardsPerAllocatedToken(bytes32 _subgraphDeploymentID) + external + view + returns (uint256, uint256); + + function getRewards(address _allocationID) external view returns (uint256); + + // -- Updates -- + + function updateAccRewardsPerSignal() external returns (uint256); + + function takeRewards(address _allocationID) external returns (uint256); + + // -- Hooks -- + + function onSubgraphSignalUpdate(bytes32 _subgraphDeploymentID) external returns (uint256); + + function onSubgraphAllocationUpdate(bytes32 _subgraphDeploymentID) external returns (uint256); +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/staking/IStaking.sol b/packages/legacy/verified/L1GNS/sources/contracts/staking/IStaking.sol new file mode 100644 index 000000000..53ec76646 --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/staking/IStaking.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; +pragma abicoder v2; + +import { IStakingBase } from "./IStakingBase.sol"; +import { IStakingExtension } from "./IStakingExtension.sol"; +import { Stakes } from "./libs/Stakes.sol"; +import { IStakingData } from "./IStakingData.sol"; +import { Rebates } from "./libs/Rebates.sol"; +import { IMulticall } from "../base/IMulticall.sol"; +import { IManaged } from "../governance/IManaged.sol"; + +/** + * @title Interface for the Staking contract + * @notice This is the interface that should be used when interacting with the Staking contract. + * @dev Note that Staking doesn't actually inherit this interface. This is because of + * the custom setup of the Staking contract where part of the functionality is implemented + * in a separate contract (StakingExtension) to which calls are delegated through the fallback function. + */ +interface IStaking is IStakingBase, IStakingExtension, IMulticall, IManaged { + // Nothing to see here +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/staking/IStakingBase.sol b/packages/legacy/verified/L1GNS/sources/contracts/staking/IStakingBase.sol new file mode 100644 index 000000000..b76cc488d --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/staking/IStakingBase.sol @@ -0,0 +1,437 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; +pragma abicoder v2; + +import { IStakingData } from "./IStakingData.sol"; + +/** + * @title Base interface for the Staking contract. + * @dev This interface includes only what's implemented in the base Staking contract. + * It does not include the L1 and L2 specific functionality. It also does not include + * several functions that are implemented in the StakingExtension contract, and are called + * via delegatecall through the fallback function. See IStaking.sol for an interface + * that includes the full functionality. + */ +interface IStakingBase is IStakingData { + /** + * @dev Emitted when `indexer` stakes `tokens` amount. + */ + event StakeDeposited(address indexed indexer, uint256 tokens); + + /** + * @dev Emitted when `indexer` unstaked and locked `tokens` amount until `until` block. + */ + event StakeLocked(address indexed indexer, uint256 tokens, uint256 until); + + /** + * @dev Emitted when `indexer` withdrew `tokens` staked. + */ + event StakeWithdrawn(address indexed indexer, uint256 tokens); + + /** + * @dev Emitted when `indexer` allocated `tokens` amount to `subgraphDeploymentID` + * during `epoch`. + * `allocationID` indexer derived address used to identify the allocation. + * `metadata` additional information related to the allocation. + */ + event AllocationCreated( + address indexed indexer, + bytes32 indexed subgraphDeploymentID, + uint256 epoch, + uint256 tokens, + address indexed allocationID, + bytes32 metadata + ); + + /** + * @dev Emitted when `indexer` collected `tokens` amount in `epoch` for `allocationID`. + * These funds are related to `subgraphDeploymentID`. + * The `from` value is the sender of the collected funds. + */ + event AllocationCollected( + address indexed indexer, + bytes32 indexed subgraphDeploymentID, + uint256 epoch, + uint256 tokens, + address indexed allocationID, + address from, + uint256 curationFees, + uint256 rebateFees + ); + + /** + * @dev Emitted when `indexer` close an allocation in `epoch` for `allocationID`. + * An amount of `tokens` get unallocated from `subgraphDeploymentID`. + * The `effectiveAllocation` are the tokens allocated from creation to closing. + * This event also emits the POI (proof of indexing) submitted by the indexer. + * `isPublic` is true if the sender was someone other than the indexer. + */ + event AllocationClosed( + address indexed indexer, + bytes32 indexed subgraphDeploymentID, + uint256 epoch, + uint256 tokens, + address indexed allocationID, + uint256 effectiveAllocation, + address sender, + bytes32 poi, + bool isPublic + ); + + /** + * @dev Emitted when `indexer` claimed a rebate on `subgraphDeploymentID` during `epoch` + * related to the `forEpoch` rebate pool. + * The rebate is for `tokens` amount and `unclaimedAllocationsCount` are left for claim + * in the rebate pool. `delegationFees` collected and sent to delegation pool. + */ + event RebateClaimed( + address indexed indexer, + bytes32 indexed subgraphDeploymentID, + address indexed allocationID, + uint256 epoch, + uint256 forEpoch, + uint256 tokens, + uint256 unclaimedAllocationsCount, + uint256 delegationFees + ); + + /** + * @dev Emitted when `indexer` update the delegation parameters for its delegation pool. + */ + event DelegationParametersUpdated( + address indexed indexer, + uint32 indexingRewardCut, + uint32 queryFeeCut, + uint32 cooldownBlocks + ); + + /** + * @dev Emitted when `caller` set `assetHolder` address as `allowed` to send funds + * to staking contract. + */ + event AssetHolderUpdate(address indexed caller, address indexed assetHolder, bool allowed); + + /** + * @dev Emitted when `indexer` set `operator` access. + */ + event SetOperator(address indexed indexer, address indexed operator, bool allowed); + + /** + * @dev Emitted when `indexer` set an address to receive rewards. + */ + event SetRewardsDestination(address indexed indexer, address indexed destination); + + /** + * @dev Emitted when `extensionImpl` was set as the address of the StakingExtension contract + * to which extended functionality is delegated. + */ + event ExtensionImplementationSet(address extensionImpl); + + /** + * @dev Possible states an allocation can be. + * States: + * - Null = indexer == address(0) + * - Active = not Null && tokens > 0 + * - Closed = Active && closedAtEpoch != 0 + * - Finalized = Closed && closedAtEpoch + channelDisputeEpochs > now() + * - Claimed = not Null && tokens == 0 + */ + enum AllocationState { + Null, + Active, + Closed, + Finalized, + Claimed + } + + /** + * @notice Initialize this contract. + * @param _controller Address of the controller that manages this contract + * @param _minimumIndexerStake Minimum amount of tokens that an indexer must stake + * @param _thawingPeriod Number of blocks that tokens get locked after unstaking + * @param _protocolPercentage Percentage of query fees that are burned as protocol fee (in PPM) + * @param _curationPercentage Percentage of query fees that are given to curators (in PPM) + * @param _channelDisputeEpochs The period in epochs that needs to pass before fees in rebate pool can be claimed + * @param _maxAllocationEpochs The maximum number of epochs that an allocation can be active + * @param _delegationUnbondingPeriod The period in epochs that tokens get locked after undelegating + * @param _delegationRatio The ratio between an indexer's own stake and the delegation they can use + * @param _rebateAlphaNumerator The numerator of the alpha factor used to calculate the rebate + * @param _rebateAlphaDenominator The denominator of the alpha factor used to calculate the rebate + * @param _extensionImpl Address of the StakingExtension implementation + */ + function initialize( + address _controller, + uint256 _minimumIndexerStake, + uint32 _thawingPeriod, + uint32 _protocolPercentage, + uint32 _curationPercentage, + uint32 _channelDisputeEpochs, + uint32 _maxAllocationEpochs, + uint32 _delegationUnbondingPeriod, + uint32 _delegationRatio, + uint32 _rebateAlphaNumerator, + uint32 _rebateAlphaDenominator, + address _extensionImpl + ) external; + + /** + * @notice Set the address of the StakingExtension implementation. + * @dev This function can only be called by the governor. + * @param _extensionImpl Address of the StakingExtension implementation + */ + function setExtensionImpl(address _extensionImpl) external; + + /** + * @notice Set the address of the counterpart (L1 or L2) staking contract. + * @dev This function can only be called by the governor. + * @param _counterpart Address of the counterpart staking contract in the other chain, without any aliasing. + */ + function setCounterpartStakingAddress(address _counterpart) external; + + /** + * @notice Set the minimum stake needed to be an Indexer + * @dev This function can only be called by the governor. + * @param _minimumIndexerStake Minimum amount of tokens that an indexer must stake + */ + function setMinimumIndexerStake(uint256 _minimumIndexerStake) external; + + /** + * @notice Set the number of blocks that tokens get locked after unstaking + * @dev This function can only be called by the governor. + * @param _thawingPeriod Number of blocks that tokens get locked after unstaking + */ + function setThawingPeriod(uint32 _thawingPeriod) external; + + /** + * @notice Set the curation percentage of query fees sent to curators. + * @dev This function can only be called by the governor. + * @param _percentage Percentage of query fees sent to curators + */ + function setCurationPercentage(uint32 _percentage) external; + + /** + * @notice Set a protocol percentage to burn when collecting query fees. + * @dev This function can only be called by the governor. + * @param _percentage Percentage of query fees to burn as protocol fee + */ + function setProtocolPercentage(uint32 _percentage) external; + + /** + * @notice Set the period in epochs that need to pass before fees in rebate pool can be claimed. + * @dev This function can only be called by the governor. + * @param _channelDisputeEpochs Period in epochs + */ + function setChannelDisputeEpochs(uint32 _channelDisputeEpochs) external; + + /** + * @notice Set the max time allowed for indexers to allocate on a subgraph + * before others are allowed to close the allocation. + * @dev This function can only be called by the governor. + * @param _maxAllocationEpochs Allocation duration limit in epochs + */ + function setMaxAllocationEpochs(uint32 _maxAllocationEpochs) external; + + /** + * @notice Set the rebate ratio (fees to allocated stake). + * @dev This function can only be called by the governor. + * @param _alphaNumerator Numerator of `alpha` in the cobb-douglas function + * @param _alphaDenominator Denominator of `alpha` in the cobb-douglas function + */ + function setRebateRatio(uint32 _alphaNumerator, uint32 _alphaDenominator) external; + + /** + * @notice Set an address as allowed asset holder. + * @dev This function can only be called by the governor. + * @param _assetHolder Address of allowed source for state channel funds + * @param _allowed True if asset holder is allowed + */ + function setAssetHolder(address _assetHolder, bool _allowed) external; + + /** + * @notice Authorize or unauthorize an address to be an operator for the caller. + * @param _operator Address to authorize or unauthorize + * @param _allowed Whether the operator is authorized or not + */ + function setOperator(address _operator, bool _allowed) external; + + /** + * @notice Deposit tokens on the indexer's stake. + * The amount staked must be over the minimumIndexerStake. + * @param _tokens Amount of tokens to stake + */ + function stake(uint256 _tokens) external; + + /** + * @notice Deposit tokens on the Indexer stake, on behalf of the Indexer. + * The amount staked must be over the minimumIndexerStake. + * @param _indexer Address of the indexer + * @param _tokens Amount of tokens to stake + */ + function stakeTo(address _indexer, uint256 _tokens) external; + + /** + * @notice Unstake tokens from the indexer stake, lock them until the thawing period expires. + * @dev NOTE: The function accepts an amount greater than the currently staked tokens. + * If that happens, it will try to unstake the max amount of tokens it can. + * The reason for this behaviour is to avoid time conditions while the transaction + * is in flight. + * @param _tokens Amount of tokens to unstake + */ + function unstake(uint256 _tokens) external; + + /** + * @notice Withdraw indexer tokens once the thawing period has passed. + */ + function withdraw() external; + + /** + * @notice Set the destination where to send rewards for an indexer. + * @param _destination Rewards destination address. If set to zero, rewards will be restaked + */ + function setRewardsDestination(address _destination) external; + + /** + * @notice Set the delegation parameters for the caller. + * @param _indexingRewardCut Percentage of indexing rewards left for the indexer + * @param _queryFeeCut Percentage of query fees left for the indexer + * @param _cooldownBlocks Period that need to pass to update delegation parameters + */ + function setDelegationParameters( + uint32 _indexingRewardCut, + uint32 _queryFeeCut, + uint32 _cooldownBlocks + ) external; + + /** + * @notice Allocate available tokens to a subgraph deployment. + * @param _subgraphDeploymentID ID of the SubgraphDeployment where tokens will be allocated + * @param _tokens Amount of tokens to allocate + * @param _allocationID The allocation identifier + * @param _metadata IPFS hash for additional information about the allocation + * @param _proof A 65-bytes Ethereum signed message of `keccak256(indexerAddress,allocationID)` + */ + function allocate( + bytes32 _subgraphDeploymentID, + uint256 _tokens, + address _allocationID, + bytes32 _metadata, + bytes calldata _proof + ) external; + + /** + * @notice Allocate available tokens to a subgraph deployment from and indexer's stake. + * The caller must be the indexer or the indexer's operator. + * @param _indexer Indexer address to allocate funds from. + * @param _subgraphDeploymentID ID of the SubgraphDeployment where tokens will be allocated + * @param _tokens Amount of tokens to allocate + * @param _allocationID The allocation identifier + * @param _metadata IPFS hash for additional information about the allocation + * @param _proof A 65-bytes Ethereum signed message of `keccak256(indexerAddress,allocationID)` + */ + function allocateFrom( + address _indexer, + bytes32 _subgraphDeploymentID, + uint256 _tokens, + address _allocationID, + bytes32 _metadata, + bytes calldata _proof + ) external; + + /** + * @notice Close an allocation and free the staked tokens. + * To be eligible for rewards a proof of indexing must be presented. + * Presenting a bad proof is subject to slashable condition. + * To opt out of rewards set _poi to 0x0 + * @param _allocationID The allocation identifier + * @param _poi Proof of indexing submitted for the allocated period + */ + function closeAllocation(address _allocationID, bytes32 _poi) external; + + /** + * @notice Collect query fees from state channels and assign them to an allocation. + * Funds received are only accepted from a valid sender. + * @dev To avoid reverting on the withdrawal from channel flow this function will: + * 1) Accept calls with zero tokens. + * 2) Accept calls after an allocation passed the dispute period, in that case, all + * the received tokens are burned. + * @param _tokens Amount of tokens to collect + * @param _allocationID Allocation where the tokens will be assigned + */ + function collect(uint256 _tokens, address _allocationID) external; + + /** + * @notice Claim tokens from the rebate pool. + * @param _allocationID Allocation from where we are claiming tokens + * @param _restake True if restake fees instead of transfer to indexer + */ + function claim(address _allocationID, bool _restake) external; + + /** + * @dev Claim tokens from the rebate pool for many allocations. + * @param _allocationID Array of allocations from where we are claiming tokens + * @param _restake True if restake fees instead of transfer to indexer + */ + function claimMany(address[] calldata _allocationID, bool _restake) external; + + /** + * @notice Return true if operator is allowed for indexer. + * @param _operator Address of the operator + * @param _indexer Address of the indexer + * @return True if operator is allowed for indexer, false otherwise + */ + function isOperator(address _operator, address _indexer) external view returns (bool); + + /** + * @notice Getter that returns if an indexer has any stake. + * @param _indexer Address of the indexer + * @return True if indexer has staked tokens + */ + function hasStake(address _indexer) external view returns (bool); + + /** + * @notice Get the total amount of tokens staked by the indexer. + * @param _indexer Address of the indexer + * @return Amount of tokens staked by the indexer + */ + function getIndexerStakedTokens(address _indexer) external view returns (uint256); + + /** + * @notice Get the total amount of tokens available to use in allocations. + * This considers the indexer stake and delegated tokens according to delegation ratio + * @param _indexer Address of the indexer + * @return Amount of tokens available to allocate including delegation + */ + function getIndexerCapacity(address _indexer) external view returns (uint256); + + /** + * @notice Return the allocation by ID. + * @param _allocationID Address used as allocation identifier + * @return Allocation data + */ + function getAllocation(address _allocationID) external view returns (Allocation memory); + + /** + * @notice Return the current state of an allocation + * @param _allocationID Allocation identifier + * @return AllocationState enum with the state of the allocation + */ + function getAllocationState(address _allocationID) external view returns (AllocationState); + + /** + * @notice Return if allocationID is used. + * @param _allocationID Address used as signer by the indexer for an allocation + * @return True if allocationID already used + */ + function isAllocation(address _allocationID) external view returns (bool); + + /** + * @notice Return the total amount of tokens allocated to subgraph. + * @param _subgraphDeploymentID Deployment ID for the subgraph + * @return Total tokens allocated to subgraph + */ + function getSubgraphAllocatedTokens(bytes32 _subgraphDeploymentID) + external + view + returns (uint256); +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/staking/IStakingData.sol b/packages/legacy/verified/L1GNS/sources/contracts/staking/IStakingData.sol new file mode 100644 index 000000000..4446077b5 --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/staking/IStakingData.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; + +/** + * @title Staking Data interface + * @dev This interface defines some structures used by the Staking contract. + */ +interface IStakingData { + /** + * @dev Allocate GRT tokens for the purpose of serving queries of a subgraph deployment + * An allocation is created in the allocate() function and consumed in claim() + */ + struct Allocation { + address indexer; + bytes32 subgraphDeploymentID; + uint256 tokens; // Tokens allocated to a SubgraphDeployment + uint256 createdAtEpoch; // Epoch when it was created + uint256 closedAtEpoch; // Epoch when it was closed + uint256 collectedFees; // Collected fees for the allocation + uint256 effectiveAllocation; // Effective allocation when closed + uint256 accRewardsPerAllocatedToken; // Snapshot used for reward calc + } + + // -- Delegation Data -- + + /** + * @dev Delegation pool information. One per indexer. + */ + struct DelegationPool { + uint32 cooldownBlocks; // Blocks to wait before updating parameters + uint32 indexingRewardCut; // in PPM + uint32 queryFeeCut; // in PPM + uint256 updatedAtBlock; // Block when the pool was last updated + uint256 tokens; // Total tokens as pool reserves + uint256 shares; // Total shares minted in the pool + mapping(address => Delegation) delegators; // Mapping of delegator => Delegation + } + + /** + * @dev Individual delegation data of a delegator in a pool. + */ + struct Delegation { + uint256 shares; // Shares owned by a delegator in the pool + uint256 tokensLocked; // Tokens locked for undelegation + uint256 tokensLockedUntil; // Block when locked tokens can be withdrawn + } +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/staking/IStakingExtension.sol b/packages/legacy/verified/L1GNS/sources/contracts/staking/IStakingExtension.sol new file mode 100644 index 000000000..e63f5c035 --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/staking/IStakingExtension.sol @@ -0,0 +1,351 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; +pragma abicoder v2; + +import { IStakingData } from "./IStakingData.sol"; +import { Rebates } from "./libs/Rebates.sol"; +import { Stakes } from "./libs/Stakes.sol"; + +/** + * @title Interface for the StakingExtension contract + * @dev This interface defines the events and functions implemented + * in the StakingExtension contract, which is used to extend the functionality + * of the Staking contract while keeping it within the 24kB mainnet size limit. + * In particular, this interface includes delegation functions and various storage + * getters. + */ +interface IStakingExtension is IStakingData { + /** + * @dev DelegationPool struct as returned by delegationPools(), since + * the original DelegationPool in IStakingData.sol contains a nested mapping. + */ + struct DelegationPoolReturn { + uint32 cooldownBlocks; // Blocks to wait before updating parameters + uint32 indexingRewardCut; // in PPM + uint32 queryFeeCut; // in PPM + uint256 updatedAtBlock; // Block when the pool was last updated + uint256 tokens; // Total tokens as pool reserves + uint256 shares; // Total shares minted in the pool + } + + /** + * @dev Emitted when `delegator` delegated `tokens` to the `indexer`, the delegator + * gets `shares` for the delegation pool proportionally to the tokens staked. + */ + event StakeDelegated( + address indexed indexer, + address indexed delegator, + uint256 tokens, + uint256 shares + ); + + /** + * @dev Emitted when `delegator` undelegated `tokens` from `indexer`. + * Tokens get locked for withdrawal after a period of time. + */ + event StakeDelegatedLocked( + address indexed indexer, + address indexed delegator, + uint256 tokens, + uint256 shares, + uint256 until + ); + + /** + * @dev Emitted when `delegator` withdrew delegated `tokens` from `indexer`. + */ + event StakeDelegatedWithdrawn( + address indexed indexer, + address indexed delegator, + uint256 tokens + ); + + /** + * @dev Emitted when `indexer` was slashed for a total of `tokens` amount. + * Tracks `reward` amount of tokens given to `beneficiary`. + */ + event StakeSlashed( + address indexed indexer, + uint256 tokens, + uint256 reward, + address beneficiary + ); + + /** + * @dev Emitted when `caller` set `slasher` address as `allowed` to slash stakes. + */ + event SlasherUpdate(address indexed caller, address indexed slasher, bool allowed); + + /** + * @notice Set the delegation ratio. + * If set to 10 it means the indexer can use up to 10x the indexer staked amount + * from their delegated tokens + * @dev This function is only callable by the governor + * @param _delegationRatio Delegation capacity multiplier + */ + function setDelegationRatio(uint32 _delegationRatio) external; + + /** + * @notice Set the minimum time in blocks an indexer needs to wait to change delegation parameters. + * Indexers can set a custom amount time for their own cooldown, but it must be greater than this. + * @dev This function is only callable by the governor + * @param _blocks Number of blocks to set the delegation parameters cooldown period + */ + function setDelegationParametersCooldown(uint32 _blocks) external; + + /** + * @notice Set the time, in epochs, a Delegator needs to wait to withdraw tokens after undelegating. + * @dev This function is only callable by the governor + * @param _delegationUnbondingPeriod Period in epochs to wait for token withdrawals after undelegating + */ + function setDelegationUnbondingPeriod(uint32 _delegationUnbondingPeriod) external; + + /** + * @notice Set a delegation tax percentage to burn when delegated funds are deposited. + * @dev This function is only callable by the governor + * @param _percentage Percentage of delegated tokens to burn as delegation tax, expressed in parts per million + */ + function setDelegationTaxPercentage(uint32 _percentage) external; + + /** + * @notice Set or unset an address as allowed slasher. + * @dev This function can only be called by the governor. + * @param _slasher Address of the party allowed to slash indexers + * @param _allowed True if slasher is allowed + */ + function setSlasher(address _slasher, bool _allowed) external; + + /** + * @notice Delegate tokens to an indexer. + * @param _indexer Address of the indexer to which tokens are delegated + * @param _tokens Amount of tokens to delegate + * @return Amount of shares issued from the delegation pool + */ + function delegate(address _indexer, uint256 _tokens) external returns (uint256); + + /** + * @notice Undelegate tokens from an indexer. Tokens will be locked for the unbonding period. + * @param _indexer Address of the indexer to which tokens had been delegated + * @param _shares Amount of shares to return and undelegate tokens + * @return Amount of tokens returned for the shares of the delegation pool + */ + function undelegate(address _indexer, uint256 _shares) external returns (uint256); + + /** + * @notice Withdraw undelegated tokens once the unbonding period has passed, and optionally + * re-delegate to a new indexer. + * @param _indexer Withdraw available tokens delegated to indexer + * @param _newIndexer Re-delegate to indexer address if non-zero, withdraw if zero address + */ + function withdrawDelegated(address _indexer, address _newIndexer) external returns (uint256); + + /** + * @notice Slash the indexer stake. Delegated tokens are not subject to slashing. + * @dev Can only be called by the slasher role. + * @param _indexer Address of indexer to slash + * @param _tokens Amount of tokens to slash from the indexer stake + * @param _reward Amount of reward tokens to send to a beneficiary + * @param _beneficiary Address of a beneficiary to receive a reward for the slashing + */ + function slash( + address _indexer, + uint256 _tokens, + uint256 _reward, + address _beneficiary + ) external; + + /** + * @notice Return the delegation from a delegator to an indexer. + * @param _indexer Address of the indexer where funds have been delegated + * @param _delegator Address of the delegator + * @return Delegation data + */ + function getDelegation(address _indexer, address _delegator) + external + view + returns (Delegation memory); + + /** + * @notice Return whether the delegator has delegated to the indexer. + * @param _indexer Address of the indexer where funds have been delegated + * @param _delegator Address of the delegator + * @return True if delegator has tokens delegated to the indexer + */ + function isDelegator(address _indexer, address _delegator) external view returns (bool); + + /** + * @notice Returns amount of delegated tokens ready to be withdrawn after unbonding period. + * @param _delegation Delegation of tokens from delegator to indexer + * @return Amount of tokens to withdraw + */ + function getWithdraweableDelegatedTokens(Delegation memory _delegation) + external + view + returns (uint256); + + /** + * @notice Getter for the delegationRatio, i.e. the delegation capacity multiplier: + * If delegation ratio is 100, and an Indexer has staked 5 GRT, + * then they can use up to 500 GRT from the delegated stake + * @return Delegation ratio + */ + function delegationRatio() external view returns (uint32); + + /** + * @notice Getter for delegationParametersCooldown: + * Minimum time in blocks an indexer needs to wait to change delegation parameters + * @return Delegation parameters cooldown in blocks + */ + function delegationParametersCooldown() external view returns (uint32); + + /** + * @notice Getter for delegationUnbondingPeriod: + * Time in epochs a delegator needs to wait to withdraw delegated stake + * @return Delegation unbonding period in epochs + */ + function delegationUnbondingPeriod() external view returns (uint32); + + /** + * @notice Getter for delegationTaxPercentage: + * Percentage of tokens to tax a delegation deposit, expressed in parts per million + * @return Delegation tax percentage in parts per million + */ + function delegationTaxPercentage() external view returns (uint32); + + /** + * @notice Getter for delegationPools[_indexer]: + * gets the delegation pool structure for a particular indexer. + * @param _indexer Address of the indexer for which to query the delegation pool + * @return Delegation pool as a DelegationPoolReturn struct + */ + function delegationPools(address _indexer) external view returns (DelegationPoolReturn memory); + + /** + * @notice Getter for operatorAuth[_indexer][_maybeOperator]: + * returns true if the operator is authorized to operate on behalf of the indexer. + * @param _indexer The indexer address for which to query authorization + * @param _maybeOperator The address that may or may not be an operator + * @return True if the operator is authorized to operate on behalf of the indexer + */ + function operatorAuth(address _indexer, address _maybeOperator) external view returns (bool); + + /** + * @notice Getter for rewardsDestination[_indexer]: + * returns the address where the indexer's rewards are sent. + * @param _indexer The indexer address for which to query the rewards destination + * @return The address where the indexer's rewards are sent, zero if none is set in which case rewards are re-staked + */ + function rewardsDestination(address _indexer) external view returns (address); + + /** + * @notice Getter for assetHolders[_maybeAssetHolder]: + * returns true if the address is an asset holder, i.e. an entity that can collect + * query fees into the Staking contract. + * @param _maybeAssetHolder The address that may or may not be an asset holder + * @return True if the address is an asset holder + */ + function assetHolders(address _maybeAssetHolder) external view returns (bool); + + /** + * @notice Getter for subgraphAllocations[_subgraphDeploymentId]: + * returns the amount of tokens allocated to a subgraph deployment. + * @param _subgraphDeploymentId The subgraph deployment for which to query the allocations + * @return The amount of tokens allocated to the subgraph deployment + */ + function subgraphAllocations(bytes32 _subgraphDeploymentId) external view returns (uint256); + + /** + * @notice Getter for rebates[_epoch]: + * gets the rebate pool for a particular epoch. + * @param _epoch Epoch for which to query the rebate pool + * @return Rebate pool for the specified epoch, as a Rebates.Pool struct + */ + function rebates(uint256 _epoch) external view returns (Rebates.Pool memory); + + /** + * @notice Getter for slashers[_maybeSlasher]: + * returns true if the address is a slasher, i.e. an entity that can slash indexers + * @param _maybeSlasher Address for which to check the slasher role + * @return True if the address is a slasher + */ + function slashers(address _maybeSlasher) external view returns (bool); + + /** + * @notice Getter for minimumIndexerStake: the minimum + * amount of GRT that an indexer needs to stake. + * @return Minimum indexer stake in GRT + */ + function minimumIndexerStake() external view returns (uint256); + + /** + * @notice Getter for thawingPeriod: the time in blocks an + * indexer needs to wait to unstake tokens. + * @return Thawing period in blocks + */ + function thawingPeriod() external view returns (uint32); + + /** + * @notice Getter for curationPercentage: the percentage of + * query fees that are distributed to curators. + * @return Curation percentage in parts per million + */ + function curationPercentage() external view returns (uint32); + + /** + * @notice Getter for protocolPercentage: the percentage of + * query fees that are burned as protocol fees. + * @return Protocol percentage in parts per million + */ + function protocolPercentage() external view returns (uint32); + + /** + * @notice Getter for channelDisputeEpochs: the time in epochs + * between closing an allocation and the moment it becomes finalized so + * query fees can be claimed. + * @return Channel dispute period in epochs + */ + function channelDisputeEpochs() external view returns (uint32); + + /** + * @notice Getter for maxAllocationEpochs: the maximum time in epochs + * that an allocation can be open before anyone is allowed to close it. This + * also caps the effective allocation when sending the allocation's query fees + * to the rebate pool. + * @return Maximum allocation period in epochs + */ + function maxAllocationEpochs() external view returns (uint32); + + /** + * @notice Getter for alphaNumerator: the numerator of the Cobb-Douglas + * rebate ratio. + * @return Rebate ratio numerator + */ + function alphaNumerator() external view returns (uint32); + + /** + * @notice Getter for alphaDenominator: the denominator of the Cobb-Douglas + * rebate ratio. + * @return Rebate ratio denominator + */ + function alphaDenominator() external view returns (uint32); + + /** + * @notice Getter for stakes[_indexer]: + * gets the stake information for an indexer as a Stakes.Indexer struct. + * @param _indexer Indexer address for which to query the stake information + * @return Stake information for the specified indexer, as a Stakes.Indexer struct + */ + function stakes(address _indexer) external view returns (Stakes.Indexer memory); + + /** + * @notice Getter for allocations[_allocationID]: + * gets an allocation's information as an IStakingData.Allocation struct. + * @param _allocationID Allocation ID for which to query the allocation information + * @return The specified allocation, as an IStakingData.Allocation struct + */ + function allocations(address _allocationID) + external + view + returns (IStakingData.Allocation memory); +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/staking/libs/Cobbs.sol b/packages/legacy/verified/L1GNS/sources/contracts/staking/libs/Cobbs.sol new file mode 100644 index 000000000..f8effed02 --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/staking/libs/Cobbs.sol @@ -0,0 +1,92 @@ +/* + + Copyright 2019 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.7.6; +pragma abicoder v2; + +import "./LibFixedMath.sol"; + +library LibCobbDouglas { + /// @dev The cobb-douglas function used to compute fee-based rewards for + /// staking pools in a given epoch. This function does not perform + /// bounds checking on the inputs, but the following conditions + /// need to be true: + /// 0 <= fees / totalFees <= 1 + /// 0 <= stake / totalStake <= 1 + /// 0 <= alphaNumerator / alphaDenominator <= 1 + /// @param totalRewards collected over an epoch. + /// @param fees Fees attributed to the the staking pool. + /// @param totalFees Total fees collected across all pools that earned rewards. + /// @param stake Stake attributed to the staking pool. + /// @param totalStake Total stake across all pools that earned rewards. + /// @param alphaNumerator Numerator of `alpha` in the cobb-douglas function. + /// @param alphaDenominator Denominator of `alpha` in the cobb-douglas + /// function. + /// @return rewards Rewards owed to the staking pool. + function cobbDouglas( + uint256 totalRewards, + uint256 fees, + uint256 totalFees, + uint256 stake, + uint256 totalStake, + uint32 alphaNumerator, + uint32 alphaDenominator + ) public pure returns (uint256 rewards) { + int256 feeRatio = LibFixedMath.toFixed(fees, totalFees); + int256 stakeRatio = LibFixedMath.toFixed(stake, totalStake); + if (feeRatio == 0 || stakeRatio == 0) { + return rewards = 0; + } + // The cobb-doublas function has the form: + // `totalRewards * feeRatio ^ alpha * stakeRatio ^ (1-alpha)` + // This is equivalent to: + // `totalRewards * stakeRatio * e^(alpha * (ln(feeRatio / stakeRatio)))` + // However, because `ln(x)` has the domain of `0 < x < 1` + // and `exp(x)` has the domain of `x < 0`, + // and fixed-point math easily overflows with multiplication, + // we will choose the following if `stakeRatio > feeRatio`: + // `totalRewards * stakeRatio / e^(alpha * (ln(stakeRatio / feeRatio)))` + + // Compute + // `e^(alpha * ln(feeRatio/stakeRatio))` if feeRatio <= stakeRatio + // or + // `e^(alpa * ln(stakeRatio/feeRatio))` if feeRatio > stakeRatio + int256 n = feeRatio <= stakeRatio + ? LibFixedMath.div(feeRatio, stakeRatio) + : LibFixedMath.div(stakeRatio, feeRatio); + n = LibFixedMath.exp( + LibFixedMath.mulDiv( + LibFixedMath.ln(n), + int256(alphaNumerator), + int256(alphaDenominator) + ) + ); + // Compute + // `totalRewards * n` if feeRatio <= stakeRatio + // or + // `totalRewards / n` if stakeRatio > feeRatio + // depending on the choice we made earlier. + n = feeRatio <= stakeRatio + ? LibFixedMath.mul(stakeRatio, n) + : LibFixedMath.div(stakeRatio, n); + // Multiply the above with totalRewards. + rewards = LibFixedMath.uintMul(n, totalRewards); + } +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/staking/libs/LibFixedMath.sol b/packages/legacy/verified/L1GNS/sources/contracts/staking/libs/LibFixedMath.sol new file mode 100644 index 000000000..0a830848a --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/staking/libs/LibFixedMath.sol @@ -0,0 +1,408 @@ +/* + + Copyright 2017 Bprotocol Foundation, 2019 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.7.6; + +// solhint-disable indent +/// @dev Signed, fixed-point, 127-bit precision math library. +library LibFixedMath { + // 1 + int256 private constant FIXED_1 = + int256(0x0000000000000000000000000000000080000000000000000000000000000000); + // 2**255 + int256 private constant MIN_FIXED_VAL = + int256(0x8000000000000000000000000000000000000000000000000000000000000000); + // 1^2 (in fixed-point) + int256 private constant FIXED_1_SQUARED = + int256(0x4000000000000000000000000000000000000000000000000000000000000000); + // 1 + int256 private constant LN_MAX_VAL = FIXED_1; + // e ^ -63.875 + int256 private constant LN_MIN_VAL = + int256(0x0000000000000000000000000000000000000000000000000000000733048c5a); + // 0 + int256 private constant EXP_MAX_VAL = 0; + // -63.875 + int256 private constant EXP_MIN_VAL = + -int256(0x0000000000000000000000000000001ff0000000000000000000000000000000); + + /// @dev Get one as a fixed-point number. + function one() internal pure returns (int256 f) { + f = FIXED_1; + } + + /// @dev Returns the addition of two fixed point numbers, reverting on overflow. + function add(int256 a, int256 b) internal pure returns (int256 c) { + c = _add(a, b); + } + + /// @dev Returns the addition of two fixed point numbers, reverting on overflow. + function sub(int256 a, int256 b) internal pure returns (int256 c) { + if (b == MIN_FIXED_VAL) { + revert("out-of-bounds"); + } + c = _add(a, -b); + } + + /// @dev Returns the multiplication of two fixed point numbers, reverting on overflow. + function mul(int256 a, int256 b) internal pure returns (int256 c) { + c = _mul(a, b) / FIXED_1; + } + + /// @dev Returns the division of two fixed point numbers. + function div(int256 a, int256 b) internal pure returns (int256 c) { + c = _div(_mul(a, FIXED_1), b); + } + + /// @dev Performs (a * n) / d, without scaling for precision. + function mulDiv( + int256 a, + int256 n, + int256 d + ) internal pure returns (int256 c) { + c = _div(_mul(a, n), d); + } + + /// @dev Returns the unsigned integer result of multiplying a fixed-point + /// number with an integer, reverting if the multiplication overflows. + /// Negative results are clamped to zero. + function uintMul(int256 f, uint256 u) internal pure returns (uint256) { + if (int256(u) < int256(0)) { + revert("out-of-bounds"); + } + int256 c = _mul(f, int256(u)); + if (c <= 0) { + return 0; + } + return uint256(uint256(c) >> 127); + } + + /// @dev Returns the absolute value of a fixed point number. + function abs(int256 f) internal pure returns (int256 c) { + if (f == MIN_FIXED_VAL) { + revert("out-of-bounds"); + } + if (f >= 0) { + c = f; + } else { + c = -f; + } + } + + /// @dev Returns 1 / `x`, where `x` is a fixed-point number. + function invert(int256 f) internal pure returns (int256 c) { + c = _div(FIXED_1_SQUARED, f); + } + + /// @dev Convert signed `n` / 1 to a fixed-point number. + function toFixed(int256 n) internal pure returns (int256 f) { + f = _mul(n, FIXED_1); + } + + /// @dev Convert signed `n` / `d` to a fixed-point number. + function toFixed(int256 n, int256 d) internal pure returns (int256 f) { + f = _div(_mul(n, FIXED_1), d); + } + + /// @dev Convert unsigned `n` / 1 to a fixed-point number. + /// Reverts if `n` is too large to fit in a fixed-point number. + function toFixed(uint256 n) internal pure returns (int256 f) { + if (int256(n) < int256(0)) { + revert("out-of-bounds"); + } + f = _mul(int256(n), FIXED_1); + } + + /// @dev Convert unsigned `n` / `d` to a fixed-point number. + /// Reverts if `n` / `d` is too large to fit in a fixed-point number. + function toFixed(uint256 n, uint256 d) internal pure returns (int256 f) { + if (int256(n) < int256(0)) { + revert("out-of-bounds"); + } + if (int256(d) < int256(0)) { + revert("out-of-bounds"); + } + f = _div(_mul(int256(n), FIXED_1), int256(d)); + } + + /// @dev Convert a fixed-point number to an integer. + function toInteger(int256 f) internal pure returns (int256 n) { + return f / FIXED_1; + } + + /// @dev Get the natural logarithm of a fixed-point number 0 < `x` <= LN_MAX_VAL + function ln(int256 x) internal pure returns (int256 r) { + if (x > LN_MAX_VAL) { + revert("out-of-bounds"); + } + if (x <= 0) { + revert("too-small"); + } + if (x == FIXED_1) { + return 0; + } + if (x <= LN_MIN_VAL) { + return EXP_MIN_VAL; + } + + int256 y; + int256 z; + int256 w; + + // Rewrite the input as a quotient of negative natural exponents and a single residual q, such that 1 < q < 2 + // For example: log(0.3) = log(e^-1 * e^-0.25 * 1.0471028872385522) + // = 1 - 0.25 - log(1 + 0.0471028872385522) + // e ^ -32 + if (x <= int256(0x00000000000000000000000000000000000000000001c8464f76164760000000)) { + r -= int256(0x0000000000000000000000000000001000000000000000000000000000000000); // - 32 + x = + (x * FIXED_1) / + int256(0x00000000000000000000000000000000000000000001c8464f76164760000000); // / e ^ -32 + } + // e ^ -16 + if (x <= int256(0x00000000000000000000000000000000000000f1aaddd7742e90000000000000)) { + r -= int256(0x0000000000000000000000000000000800000000000000000000000000000000); // - 16 + x = + (x * FIXED_1) / + int256(0x00000000000000000000000000000000000000f1aaddd7742e90000000000000); // / e ^ -16 + } + // e ^ -8 + if (x <= int256(0x00000000000000000000000000000000000afe10820813d78000000000000000)) { + r -= int256(0x0000000000000000000000000000000400000000000000000000000000000000); // - 8 + x = + (x * FIXED_1) / + int256(0x00000000000000000000000000000000000afe10820813d78000000000000000); // / e ^ -8 + } + // e ^ -4 + if (x <= int256(0x0000000000000000000000000000000002582ab704279ec00000000000000000)) { + r -= int256(0x0000000000000000000000000000000200000000000000000000000000000000); // - 4 + x = + (x * FIXED_1) / + int256(0x0000000000000000000000000000000002582ab704279ec00000000000000000); // / e ^ -4 + } + // e ^ -2 + if (x <= int256(0x000000000000000000000000000000001152aaa3bf81cc000000000000000000)) { + r -= int256(0x0000000000000000000000000000000100000000000000000000000000000000); // - 2 + x = + (x * FIXED_1) / + int256(0x000000000000000000000000000000001152aaa3bf81cc000000000000000000); // / e ^ -2 + } + // e ^ -1 + if (x <= int256(0x000000000000000000000000000000002f16ac6c59de70000000000000000000)) { + r -= int256(0x0000000000000000000000000000000080000000000000000000000000000000); // - 1 + x = + (x * FIXED_1) / + int256(0x000000000000000000000000000000002f16ac6c59de70000000000000000000); // / e ^ -1 + } + // e ^ -0.5 + if (x <= int256(0x000000000000000000000000000000004da2cbf1be5828000000000000000000)) { + r -= int256(0x0000000000000000000000000000000040000000000000000000000000000000); // - 0.5 + x = + (x * FIXED_1) / + int256(0x000000000000000000000000000000004da2cbf1be5828000000000000000000); // / e ^ -0.5 + } + // e ^ -0.25 + if (x <= int256(0x0000000000000000000000000000000063afbe7ab2082c000000000000000000)) { + r -= int256(0x0000000000000000000000000000000020000000000000000000000000000000); // - 0.25 + x = + (x * FIXED_1) / + int256(0x0000000000000000000000000000000063afbe7ab2082c000000000000000000); // / e ^ -0.25 + } + // e ^ -0.125 + if (x <= int256(0x0000000000000000000000000000000070f5a893b608861e1f58934f97aea57d)) { + r -= int256(0x0000000000000000000000000000000010000000000000000000000000000000); // - 0.125 + x = + (x * FIXED_1) / + int256(0x0000000000000000000000000000000070f5a893b608861e1f58934f97aea57d); // / e ^ -0.125 + } + // `x` is now our residual in the range of 1 <= x <= 2 (or close enough). + + // Add the taylor series for log(1 + z), where z = x - 1 + z = y = x - FIXED_1; + w = (y * y) / FIXED_1; + r += (z * (0x100000000000000000000000000000000 - y)) / 0x100000000000000000000000000000000; + z = (z * w) / FIXED_1; // add y^01 / 01 - y^02 / 02 + r += (z * (0x0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - y)) / 0x200000000000000000000000000000000; + z = (z * w) / FIXED_1; // add y^03 / 03 - y^04 / 04 + r += (z * (0x099999999999999999999999999999999 - y)) / 0x300000000000000000000000000000000; + z = (z * w) / FIXED_1; // add y^05 / 05 - y^06 / 06 + r += (z * (0x092492492492492492492492492492492 - y)) / 0x400000000000000000000000000000000; + z = (z * w) / FIXED_1; // add y^07 / 07 - y^08 / 08 + r += (z * (0x08e38e38e38e38e38e38e38e38e38e38e - y)) / 0x500000000000000000000000000000000; + z = (z * w) / FIXED_1; // add y^09 / 09 - y^10 / 10 + r += (z * (0x08ba2e8ba2e8ba2e8ba2e8ba2e8ba2e8b - y)) / 0x600000000000000000000000000000000; + z = (z * w) / FIXED_1; // add y^11 / 11 - y^12 / 12 + r += (z * (0x089d89d89d89d89d89d89d89d89d89d89 - y)) / 0x700000000000000000000000000000000; + z = (z * w) / FIXED_1; // add y^13 / 13 - y^14 / 14 + r += (z * (0x088888888888888888888888888888888 - y)) / 0x800000000000000000000000000000000; // add y^15 / 15 - y^16 / 16 + } + + /// @dev Compute the natural exponent for a fixed-point number EXP_MIN_VAL <= `x` <= 1 + function exp(int256 x) internal pure returns (int256 r) { + if (x < EXP_MIN_VAL) { + // Saturate to zero below EXP_MIN_VAL. + return 0; + } + if (x == 0) { + return FIXED_1; + } + if (x > EXP_MAX_VAL) { + revert("out-of-bounds"); + } + + // Rewrite the input as a product of natural exponents and a + // single residual q, where q is a number of small magnitude. + // For example: e^-34.419 = e^(-32 - 2 - 0.25 - 0.125 - 0.044) + // = e^-32 * e^-2 * e^-0.25 * e^-0.125 * e^-0.044 + // -> q = -0.044 + + // Multiply with the taylor series for e^q + int256 y; + int256 z; + // q = x % 0.125 (the residual) + z = y = x % 0x0000000000000000000000000000000010000000000000000000000000000000; + z = (z * y) / FIXED_1; + r += z * 0x10e1b3be415a0000; // add y^02 * (20! / 02!) + z = (z * y) / FIXED_1; + r += z * 0x05a0913f6b1e0000; // add y^03 * (20! / 03!) + z = (z * y) / FIXED_1; + r += z * 0x0168244fdac78000; // add y^04 * (20! / 04!) + z = (z * y) / FIXED_1; + r += z * 0x004807432bc18000; // add y^05 * (20! / 05!) + z = (z * y) / FIXED_1; + r += z * 0x000c0135dca04000; // add y^06 * (20! / 06!) + z = (z * y) / FIXED_1; + r += z * 0x0001b707b1cdc000; // add y^07 * (20! / 07!) + z = (z * y) / FIXED_1; + r += z * 0x000036e0f639b800; // add y^08 * (20! / 08!) + z = (z * y) / FIXED_1; + r += z * 0x00000618fee9f800; // add y^09 * (20! / 09!) + z = (z * y) / FIXED_1; + r += z * 0x0000009c197dcc00; // add y^10 * (20! / 10!) + z = (z * y) / FIXED_1; + r += z * 0x0000000e30dce400; // add y^11 * (20! / 11!) + z = (z * y) / FIXED_1; + r += z * 0x000000012ebd1300; // add y^12 * (20! / 12!) + z = (z * y) / FIXED_1; + r += z * 0x0000000017499f00; // add y^13 * (20! / 13!) + z = (z * y) / FIXED_1; + r += z * 0x0000000001a9d480; // add y^14 * (20! / 14!) + z = (z * y) / FIXED_1; + r += z * 0x00000000001c6380; // add y^15 * (20! / 15!) + z = (z * y) / FIXED_1; + r += z * 0x000000000001c638; // add y^16 * (20! / 16!) + z = (z * y) / FIXED_1; + r += z * 0x0000000000001ab8; // add y^17 * (20! / 17!) + z = (z * y) / FIXED_1; + r += z * 0x000000000000017c; // add y^18 * (20! / 18!) + z = (z * y) / FIXED_1; + r += z * 0x0000000000000014; // add y^19 * (20! / 19!) + z = (z * y) / FIXED_1; + r += z * 0x0000000000000001; // add y^20 * (20! / 20!) + r = r / 0x21c3677c82b40000 + y + FIXED_1; // divide by 20! and then add y^1 / 1! + y^0 / 0! + + // Multiply with the non-residual terms. + x = -x; + // e ^ -32 + if ((x & int256(0x0000000000000000000000000000001000000000000000000000000000000000)) != 0) { + r = + (r * int256(0x00000000000000000000000000000000000000f1aaddd7742e56d32fb9f99744)) / + int256(0x0000000000000000000000000043cbaf42a000812488fc5c220ad7b97bf6e99e); // * e ^ -32 + } + // e ^ -16 + if ((x & int256(0x0000000000000000000000000000000800000000000000000000000000000000)) != 0) { + r = + (r * int256(0x00000000000000000000000000000000000afe10820813d65dfe6a33c07f738f)) / + int256(0x000000000000000000000000000005d27a9f51c31b7c2f8038212a0574779991); // * e ^ -16 + } + // e ^ -8 + if ((x & int256(0x0000000000000000000000000000000400000000000000000000000000000000)) != 0) { + r = + (r * int256(0x0000000000000000000000000000000002582ab704279e8efd15e0265855c47a)) / + int256(0x0000000000000000000000000000001b4c902e273a58678d6d3bfdb93db96d02); // * e ^ -8 + } + // e ^ -4 + if ((x & int256(0x0000000000000000000000000000000200000000000000000000000000000000)) != 0) { + r = + (r * int256(0x000000000000000000000000000000001152aaa3bf81cb9fdb76eae12d029571)) / + int256(0x00000000000000000000000000000003b1cc971a9bb5b9867477440d6d157750); // * e ^ -4 + } + // e ^ -2 + if ((x & int256(0x0000000000000000000000000000000100000000000000000000000000000000)) != 0) { + r = + (r * int256(0x000000000000000000000000000000002f16ac6c59de6f8d5d6f63c1482a7c86)) / + int256(0x000000000000000000000000000000015bf0a8b1457695355fb8ac404e7a79e3); // * e ^ -2 + } + // e ^ -1 + if ((x & int256(0x0000000000000000000000000000000080000000000000000000000000000000)) != 0) { + r = + (r * int256(0x000000000000000000000000000000004da2cbf1be5827f9eb3ad1aa9866ebb3)) / + int256(0x00000000000000000000000000000000d3094c70f034de4b96ff7d5b6f99fcd8); // * e ^ -1 + } + // e ^ -0.5 + if ((x & int256(0x0000000000000000000000000000000040000000000000000000000000000000)) != 0) { + r = + (r * int256(0x0000000000000000000000000000000063afbe7ab2082ba1a0ae5e4eb1b479dc)) / + int256(0x00000000000000000000000000000000a45af1e1f40c333b3de1db4dd55f29a7); // * e ^ -0.5 + } + // e ^ -0.25 + if ((x & int256(0x0000000000000000000000000000000020000000000000000000000000000000)) != 0) { + r = + (r * int256(0x0000000000000000000000000000000070f5a893b608861e1f58934f97aea57d)) / + int256(0x00000000000000000000000000000000910b022db7ae67ce76b441c27035c6a1); // * e ^ -0.25 + } + // e ^ -0.125 + if ((x & int256(0x0000000000000000000000000000000010000000000000000000000000000000)) != 0) { + r = + (r * int256(0x00000000000000000000000000000000783eafef1c0a8f3978c7f81824d62ebf)) / + int256(0x0000000000000000000000000000000088415abbe9a76bead8d00cf112e4d4a8); // * e ^ -0.125 + } + } + + /// @dev Returns the multiplication two numbers, reverting on overflow. + function _mul(int256 a, int256 b) private pure returns (int256 c) { + if (a == 0) { + return 0; + } + c = a * b; + if (c / a != b || c / b != a) { + revert("overflow"); + } + } + + /// @dev Returns the division of two numbers, reverting on division by zero. + function _div(int256 a, int256 b) private pure returns (int256 c) { + if (b == 0) { + revert("overflow"); + } + if (a == MIN_FIXED_VAL && b == -1) { + revert("overflow"); + } + c = a / b; + } + + /// @dev Adds two numbers, reverting on overflow. + function _add(int256 a, int256 b) private pure returns (int256 c) { + c = a + b; + if ((a < 0 && b < 0 && c > a) || (a > 0 && b > 0 && c < a)) { + revert("overflow"); + } + } +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/staking/libs/MathUtils.sol b/packages/legacy/verified/L1GNS/sources/contracts/staking/libs/MathUtils.sol new file mode 100644 index 000000000..4194b6da8 --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/staking/libs/MathUtils.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import "@openzeppelin/contracts/math/SafeMath.sol"; + +/** + * @title MathUtils Library + * @notice A collection of functions to perform math operations + */ +library MathUtils { + using SafeMath for uint256; + + /** + * @dev Calculates the weighted average of two values pondering each of these + * values based on configured weights. The contribution of each value N is + * weightN/(weightA + weightB). + * @param valueA The amount for value A + * @param weightA The weight to use for value A + * @param valueB The amount for value B + * @param weightB The weight to use for value B + */ + function weightedAverage( + uint256 valueA, + uint256 weightA, + uint256 valueB, + uint256 weightB + ) internal pure returns (uint256) { + return valueA.mul(weightA).add(valueB.mul(weightB)).div(weightA.add(weightB)); + } + + /** + * @dev Returns the minimum of two numbers. + */ + function min(uint256 x, uint256 y) internal pure returns (uint256) { + return x <= y ? x : y; + } + + /** + * @dev Returns the difference between two numbers or zero if negative. + */ + function diffOrZero(uint256 x, uint256 y) internal pure returns (uint256) { + return (x > y) ? x.sub(y) : 0; + } +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/staking/libs/Rebates.sol b/packages/legacy/verified/L1GNS/sources/contracts/staking/libs/Rebates.sol new file mode 100644 index 000000000..9323a9747 --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/staking/libs/Rebates.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; +pragma abicoder v2; + +import "@openzeppelin/contracts/math/SafeMath.sol"; + +import "./Cobbs.sol"; + +/** + * @title A collection of data structures and functions to manage Rebates + * Used for low-level state changes, require() conditions should be evaluated + * at the caller function scope. + */ +library Rebates { + using SafeMath for uint256; + using Rebates for Rebates.Pool; + + // Tracks stats for allocations closed on a particular epoch for claiming + // The pool also keeps tracks of total query fees collected and stake used + // Only one rebate pool exists per epoch + struct Pool { + uint256 fees; // total query fees in the rebate pool + uint256 effectiveAllocatedStake; // total effective allocation of stake + uint256 claimedRewards; // total claimed rewards from the rebate pool + uint32 unclaimedAllocationsCount; // amount of unclaimed allocations + uint32 alphaNumerator; // numerator of `alpha` in the cobb-douglas function + uint32 alphaDenominator; // denominator of `alpha` in the cobb-douglas function + } + + /** + * @dev Init the rebate pool with the rebate ratio. + * @param _alphaNumerator Numerator of `alpha` in the cobb-douglas function + * @param _alphaDenominator Denominator of `alpha` in the cobb-douglas function + */ + function init( + Rebates.Pool storage pool, + uint32 _alphaNumerator, + uint32 _alphaDenominator + ) internal { + pool.alphaNumerator = _alphaNumerator; + pool.alphaDenominator = _alphaDenominator; + } + + /** + * @dev Return true if the rebate pool was already initialized. + */ + function exists(Rebates.Pool storage pool) internal view returns (bool) { + return pool.effectiveAllocatedStake > 0; + } + + /** + * @dev Return the amount of unclaimed fees. + */ + function unclaimedFees(Rebates.Pool storage pool) internal view returns (uint256) { + return pool.fees.sub(pool.claimedRewards); + } + + /** + * @dev Deposit tokens into the rebate pool. + * @param _indexerFees Amount of fees collected in tokens + * @param _indexerEffectiveAllocatedStake Effective stake allocated by indexer for a period of epochs + */ + function addToPool( + Rebates.Pool storage pool, + uint256 _indexerFees, + uint256 _indexerEffectiveAllocatedStake + ) internal { + pool.fees = pool.fees.add(_indexerFees); + pool.effectiveAllocatedStake = pool.effectiveAllocatedStake.add( + _indexerEffectiveAllocatedStake + ); + pool.unclaimedAllocationsCount += 1; + } + + /** + * @dev Redeem tokens from the rebate pool. + * @param _indexerFees Amount of fees collected in tokens + * @param _indexerEffectiveAllocatedStake Effective stake allocated by indexer for a period of epochs + * @return Amount of reward tokens according to Cobb-Douglas rebate formula + */ + function redeem( + Rebates.Pool storage pool, + uint256 _indexerFees, + uint256 _indexerEffectiveAllocatedStake + ) internal returns (uint256) { + uint256 rebateReward = 0; + + // Calculate the rebate rewards for the indexer + if (pool.fees > 0 && pool.effectiveAllocatedStake > 0) { + rebateReward = LibCobbDouglas.cobbDouglas( + pool.fees, // totalRewards + _indexerFees, + pool.fees, + _indexerEffectiveAllocatedStake, + pool.effectiveAllocatedStake, + pool.alphaNumerator, + pool.alphaDenominator + ); + + // Under NO circumstance we will reward more than total fees in the pool + uint256 _unclaimedFees = pool.unclaimedFees(); + if (rebateReward > _unclaimedFees) { + rebateReward = _unclaimedFees; + } + } + + // Update pool state + pool.unclaimedAllocationsCount -= 1; + pool.claimedRewards = pool.claimedRewards.add(rebateReward); + + return rebateReward; + } +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/staking/libs/Stakes.sol b/packages/legacy/verified/L1GNS/sources/contracts/staking/libs/Stakes.sol new file mode 100644 index 000000000..5882944d8 --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/staking/libs/Stakes.sol @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; +pragma abicoder v2; + +import "@openzeppelin/contracts/math/SafeMath.sol"; + +import "./MathUtils.sol"; + +/** + * @title A collection of data structures and functions to manage the Indexer Stake state. + * Used for low-level state changes, require() conditions should be evaluated + * at the caller function scope. + */ +library Stakes { + using SafeMath for uint256; + using Stakes for Stakes.Indexer; + + struct Indexer { + uint256 tokensStaked; // Tokens on the indexer stake (staked by the indexer) + uint256 tokensAllocated; // Tokens used in allocations + uint256 tokensLocked; // Tokens locked for withdrawal subject to thawing period + uint256 tokensLockedUntil; // Block when locked tokens can be withdrawn + } + + /** + * @dev Deposit tokens to the indexer stake. + * @param stake Stake data + * @param _tokens Amount of tokens to deposit + */ + function deposit(Stakes.Indexer storage stake, uint256 _tokens) internal { + stake.tokensStaked = stake.tokensStaked.add(_tokens); + } + + /** + * @dev Release tokens from the indexer stake. + * @param stake Stake data + * @param _tokens Amount of tokens to release + */ + function release(Stakes.Indexer storage stake, uint256 _tokens) internal { + stake.tokensStaked = stake.tokensStaked.sub(_tokens); + } + + /** + * @dev Allocate tokens from the main stack to a SubgraphDeployment. + * @param stake Stake data + * @param _tokens Amount of tokens to allocate + */ + function allocate(Stakes.Indexer storage stake, uint256 _tokens) internal { + stake.tokensAllocated = stake.tokensAllocated.add(_tokens); + } + + /** + * @dev Unallocate tokens from a SubgraphDeployment back to the main stack. + * @param stake Stake data + * @param _tokens Amount of tokens to unallocate + */ + function unallocate(Stakes.Indexer storage stake, uint256 _tokens) internal { + stake.tokensAllocated = stake.tokensAllocated.sub(_tokens); + } + + /** + * @dev Lock tokens until a thawing period pass. + * @param stake Stake data + * @param _tokens Amount of tokens to unstake + * @param _period Period in blocks that need to pass before withdrawal + */ + function lockTokens( + Stakes.Indexer storage stake, + uint256 _tokens, + uint256 _period + ) internal { + // Take into account period averaging for multiple unstake requests + uint256 lockingPeriod = _period; + if (stake.tokensLocked > 0) { + lockingPeriod = MathUtils.weightedAverage( + MathUtils.diffOrZero(stake.tokensLockedUntil, block.number), // Remaining thawing period + stake.tokensLocked, // Weighted by remaining unstaked tokens + _period, // Thawing period + _tokens // Weighted by new tokens to unstake + ); + } + + // Update balances + stake.tokensLocked = stake.tokensLocked.add(_tokens); + stake.tokensLockedUntil = block.number.add(lockingPeriod); + } + + /** + * @dev Unlock tokens. + * @param stake Stake data + * @param _tokens Amount of tokens to unkock + */ + function unlockTokens(Stakes.Indexer storage stake, uint256 _tokens) internal { + stake.tokensLocked = stake.tokensLocked.sub(_tokens); + if (stake.tokensLocked == 0) { + stake.tokensLockedUntil = 0; + } + } + + /** + * @dev Take all tokens out from the locked stake for withdrawal. + * @param stake Stake data + * @return Amount of tokens being withdrawn + */ + function withdrawTokens(Stakes.Indexer storage stake) internal returns (uint256) { + // Calculate tokens that can be released + uint256 tokensToWithdraw = stake.tokensWithdrawable(); + + if (tokensToWithdraw > 0) { + // Reset locked tokens + stake.unlockTokens(tokensToWithdraw); + + // Decrease indexer stake + stake.release(tokensToWithdraw); + } + + return tokensToWithdraw; + } + + /** + * @dev Return the amount of tokens used in allocations and locked for withdrawal. + * @param stake Stake data + * @return Token amount + */ + function tokensUsed(Stakes.Indexer memory stake) internal pure returns (uint256) { + return stake.tokensAllocated.add(stake.tokensLocked); + } + + /** + * @dev Return the amount of tokens staked not considering the ones that are already going + * through the thawing period or are ready for withdrawal. We call it secure stake because + * it is not subject to change by a withdraw call from the indexer. + * @param stake Stake data + * @return Token amount + */ + function tokensSecureStake(Stakes.Indexer memory stake) internal pure returns (uint256) { + return stake.tokensStaked.sub(stake.tokensLocked); + } + + /** + * @dev Tokens free balance on the indexer stake that can be used for any purpose. + * Any token that is allocated cannot be used as well as tokens that are going through the + * thawing period or are withdrawable + * Calc: tokensStaked - tokensAllocated - tokensLocked + * @param stake Stake data + * @return Token amount + */ + function tokensAvailable(Stakes.Indexer memory stake) internal pure returns (uint256) { + return stake.tokensAvailableWithDelegation(0); + } + + /** + * @dev Tokens free balance on the indexer stake that can be used for allocations. + * This function accepts a parameter for extra delegated capacity that takes into + * account delegated tokens + * @param stake Stake data + * @param _delegatedCapacity Amount of tokens used from delegators to calculate availability + * @return Token amount + */ + function tokensAvailableWithDelegation(Stakes.Indexer memory stake, uint256 _delegatedCapacity) + internal + pure + returns (uint256) + { + uint256 tokensCapacity = stake.tokensStaked.add(_delegatedCapacity); + uint256 _tokensUsed = stake.tokensUsed(); + // If more tokens are used than the current capacity, the indexer is overallocated. + // This means the indexer doesn't have available capacity to create new allocations. + // We can reach this state when the indexer has funds allocated and then any + // of these conditions happen: + // - The delegationCapacity ratio is reduced. + // - The indexer stake is slashed. + // - A delegator removes enough stake. + if (_tokensUsed > tokensCapacity) { + // Indexer stake is over allocated: return 0 to avoid stake to be used until + // the overallocation is restored by staking more tokens, unallocating tokens + // or using more delegated funds + return 0; + } + return tokensCapacity.sub(_tokensUsed); + } + + /** + * @dev Tokens available for withdrawal after thawing period. + * @param stake Stake data + * @return Token amount + */ + function tokensWithdrawable(Stakes.Indexer memory stake) internal view returns (uint256) { + // No tokens to withdraw before locking period + if (stake.tokensLockedUntil == 0 || block.number < stake.tokensLockedUntil) { + return 0; + } + return stake.tokensLocked; + } +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/token/IGraphToken.sol b/packages/legacy/verified/L1GNS/sources/contracts/token/IGraphToken.sol new file mode 100644 index 000000000..8255e18d5 --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/token/IGraphToken.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface IGraphToken is IERC20 { + // -- Mint and Burn -- + + function burn(uint256 amount) external; + + function burnFrom(address _from, uint256 amount) external; + + function mint(address _to, uint256 _amount) external; + + // -- Mint Admin -- + + function addMinter(address _account) external; + + function removeMinter(address _account) external; + + function renounceMinter() external; + + function isMinter(address _account) external view returns (bool); + + // -- Permit -- + + function permit( + address _owner, + address _spender, + uint256 _value, + uint256 _deadline, + uint8 _v, + bytes32 _r, + bytes32 _s + ) external; + + // -- Allowance -- + + function increaseAllowance(address spender, uint256 addedValue) external returns (bool); + + function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool); +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/upgrades/GraphUpgradeable.sol b/packages/legacy/verified/L1GNS/sources/contracts/upgrades/GraphUpgradeable.sol new file mode 100644 index 000000000..92ce80ad7 --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/upgrades/GraphUpgradeable.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import { IGraphProxy } from "./IGraphProxy.sol"; + +/** + * @title Graph Upgradeable + * @dev This contract is intended to be inherited from upgradeable contracts. + */ +abstract contract GraphUpgradeable { + /** + * @dev Storage slot with the address of the current implementation. + * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is + * validated in the constructor. + */ + bytes32 internal constant IMPLEMENTATION_SLOT = + 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + /** + * @dev Check if the caller is the proxy admin. + */ + modifier onlyProxyAdmin(IGraphProxy _proxy) { + require(msg.sender == _proxy.admin(), "Caller must be the proxy admin"); + _; + } + + /** + * @dev Check if the caller is the implementation. + */ + modifier onlyImpl() { + require(msg.sender == _implementation(), "Only implementation"); + _; + } + + /** + * @dev Returns the current implementation. + * @return impl Address of the current implementation + */ + function _implementation() internal view returns (address impl) { + bytes32 slot = IMPLEMENTATION_SLOT; + // solhint-disable-next-line no-inline-assembly + assembly { + impl := sload(slot) + } + } + + /** + * @notice Accept to be an implementation of proxy. + * @param _proxy Proxy to accept + */ + function acceptProxy(IGraphProxy _proxy) external onlyProxyAdmin(_proxy) { + _proxy.acceptUpgrade(); + } + + /** + * @notice Accept to be an implementation of proxy and then call a function from the new + * implementation as specified by `_data`, which should be an encoded function call. This is + * useful to initialize new storage variables in the proxied contract. + * @param _proxy Proxy to accept + * @param _data Calldata for the initialization function call (including selector) + */ + function acceptProxyAndCall(IGraphProxy _proxy, bytes calldata _data) + external + onlyProxyAdmin(_proxy) + { + _proxy.acceptUpgradeAndCall(_data); + } +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/upgrades/IGraphProxy.sol b/packages/legacy/verified/L1GNS/sources/contracts/upgrades/IGraphProxy.sol new file mode 100644 index 000000000..61946e948 --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/upgrades/IGraphProxy.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +interface IGraphProxy { + function admin() external returns (address); + + function setAdmin(address _newAdmin) external; + + function implementation() external returns (address); + + function pendingImplementation() external returns (address); + + function upgradeTo(address _newImplementation) external; + + function acceptUpgrade() external; + + function acceptUpgradeAndCall(bytes calldata data) external; +} diff --git a/packages/legacy/verified/L1GNS/sources/contracts/utils/TokenUtils.sol b/packages/legacy/verified/L1GNS/sources/contracts/utils/TokenUtils.sol new file mode 100644 index 000000000..cbb23ce97 --- /dev/null +++ b/packages/legacy/verified/L1GNS/sources/contracts/utils/TokenUtils.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import "../token/IGraphToken.sol"; + +library TokenUtils { + /** + * @dev Pull tokens from an address to this contract. + * @param _graphToken Token to transfer + * @param _from Address sending the tokens + * @param _amount Amount of tokens to transfer + */ + function pullTokens( + IGraphToken _graphToken, + address _from, + uint256 _amount + ) internal { + if (_amount > 0) { + require(_graphToken.transferFrom(_from, address(this), _amount), "!transfer"); + } + } + + /** + * @dev Push tokens from this contract to a receiving address. + * @param _graphToken Token to transfer + * @param _to Address receiving the tokens + * @param _amount Amount of tokens to transfer + */ + function pushTokens( + IGraphToken _graphToken, + address _to, + uint256 _amount + ) internal { + if (_amount > 0) { + require(_graphToken.transfer(_to, _amount), "!transfer"); + } + } + + /** + * @dev Burn tokens held by this contract. + * @param _graphToken Token to burn + * @param _amount Amount of tokens to burn + */ + function burnTokens(IGraphToken _graphToken, uint256 _amount) internal { + if (_amount > 0) { + _graphToken.burn(_amount); + } + } +} diff --git a/packages/legacy/verified/L1GraphTokenGateway/compiler.json b/packages/legacy/verified/L1GraphTokenGateway/compiler.json new file mode 100644 index 000000000..d0b01dc08 --- /dev/null +++ b/packages/legacy/verified/L1GraphTokenGateway/compiler.json @@ -0,0 +1,49 @@ +{ + "version": "0.7.6+commit.7338295f", + "settings": { + "metadata": { + "bytecodeHash": "ipfs", + "useLiteralContent": true + }, + "libraries": {}, + "optimizer": { + "runs": 200, + "enabled": true + }, + "evmVersion": "istanbul", + "remappings": [] + }, + "target": { + "source": "contracts/gateway/L1GraphTokenGateway.sol", + "name": "L1GraphTokenGateway" + }, + "sourcifyMatch": "exact_match", + "sourceHashes": { + "contracts/arbitrum/IInbox.sol": "9512ab41cf60ec90fcaf54d7b1e8d4a02d73974e9e6b02a2c6fd5be2cfb92671", + "contracts/arbitrum/IBridge.sol": "6198806819f2e7f9709a5dc5e7e6dde2d02e1af62d4e75aaf9d1c9df77a64f56", + "contracts/arbitrum/IOutbox.sol": "ba7dfdd4ad21301a4b4214977216eafa8f1cc9f48aa33410078d2fcf45e312e2", + "contracts/staking/IStaking.sol": "17f3a421e91e745b50ca53227a4197b3992d1133946c91a307ed073993c267c9", + "contracts/token/IGraphToken.sol": "244ca084fd409c9ad3969e7ac5548346495466442ff7f802288b112d687705d2", + "contracts/curation/ICuration.sol": "5535bfac7790f46af6b64b4c0db2348d9158992a2fd7b9d7af056a6e63a8b4ba", + "contracts/governance/Managed.sol": "a0e4ec30705ab1d9f7fd896a957fb096168885c92e62c44069932176fd3cb8f3", + "contracts/governance/IManaged.sol": "bd1f47d7a16eb1a2ddbf547511166fee8e99c73d7cb871908837d5db65037bb8", + "contracts/governance/Pausable.sol": "99aba03d9b61a91544287ce780e02fab90f34c542ecd8fa014302d8f99d98a2a", + "contracts/epochs/IEpochManager.sol": "cb45047682865d6ed215af7d05d505b8fa7a1e66b1e54b45c1a47798281f47ab", + "contracts/staking/IStakingData.sol": "b44cad426f81eb34c59b2c276080ba8d674f98ebe685529fb24acc61c217b1bc", + "contracts/upgrades/IGraphProxy.sol": "d7b89c0be1a05c93facc640a08d0b4f1e0b4557cb268d9143e4c42ce24bbd1a9", + "contracts/arbitrum/ITokenGateway.sol": "55bd317bf1ca12a2308cc51ad4ec6c370dcb6d1cdd1631ec2308e57528ad7600", + "contracts/governance/IController.sol": "beb591bb70a3b94f025a5f64bbb3fc6788043fb8d5f1cdffb21acb8cbbce8f8d", + "contracts/rewards/IRewardsManager.sol": "5cf8c3593f475719620e6a01b478fa3b5644d3848f82e7768bb36d43069478dd", + "contracts/arbitrum/IMessageProvider.sol": "8b15cd25f7d816e38a127e48eea1c6e85976f111dae57b8f47861479f96a2a9b", + "contracts/gateway/GraphTokenGateway.sol": "0a4f95b0e1773dfd4a36967c425395146e1f4af4cca404762958ea481993cbc8", + "contracts/upgrades/GraphUpgradeable.sol": "fdb340dbeb5de52ea8243fbfab9d2ef8fdfb916e909dc4e1ae6b314cde0ea083", + "contracts/gateway/L1GraphTokenGateway.sol": "0e75ff631719d02cca4184ecf760b6e116b2bae511421e778f2ddd20ab086362", + "contracts/arbitrum/L1ArbitrumMessenger.sol": "3733fddf4bcac8afbdb95bd10709b68d09abd3233134fc583f87134104c9dfac", + "contracts/curation/IGraphCurationToken.sol": "4f905a43b8e7146f0cfa000657de76aa27a84a042522b2a98af7552d2eecb767", + "@openzeppelin/contracts/token/ERC20/IERC20.sol": "0573c2961569aa4906845d0cd428b5b7394956170054ceeaa8f8af96cd44875c", + "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol": "2c3a3edc2b1a4ac2c4a8645475b51f2668b1ad5ea22df074d0c0ebd3122ce2e7", + "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol": "dabaab4d3d3f03e6bfb86eec1d54f31edf0429f4bfc4dff717d5776d5231c145", + "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol": "877bc9cb396d0f50330bb9c0057c029407e159739b6fab0b110f19451c8681e4", + "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol": "57cf669fc8977ccd1738280e0a39c0156ecbde0af6142d6dd845ab1c055ca61d" + } +} diff --git a/packages/legacy/verified/L1GraphTokenGateway/sources/@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol b/packages/legacy/verified/L1GraphTokenGateway/sources/@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol new file mode 100644 index 000000000..c7b5c001e --- /dev/null +++ b/packages/legacy/verified/L1GraphTokenGateway/sources/@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMathUpgradeable { + /** + * @dev Returns the addition of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + + /** + * @dev Returns the substraction of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b > a) return (false, 0); + return (true, a - b); + } + + /** + * @dev Returns the multiplication of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + + /** + * @dev Returns the division of two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b == 0) return (false, 0); + return (true, a / b); + } + + /** + * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b == 0) return (false, 0); + return (true, a % b); + } + + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + require(b <= a, "SafeMath: subtraction overflow"); + return a - b; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) return 0; + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + require(b > 0, "SafeMath: division by zero"); + return a / b; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + require(b > 0, "SafeMath: modulo by zero"); + return a % b; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {trySub}. + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + return a - b; + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting with custom message on + * division by zero. The result is rounded towards zero. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryDiv}. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + return a / b; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting with custom message when dividing by zero. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryMod}. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + return a % b; + } +} diff --git a/packages/legacy/verified/L1GraphTokenGateway/sources/@openzeppelin/contracts-upgradeable/proxy/Initializable.sol b/packages/legacy/verified/L1GraphTokenGateway/sources/@openzeppelin/contracts-upgradeable/proxy/Initializable.sol new file mode 100644 index 000000000..9fd6dc9fc --- /dev/null +++ b/packages/legacy/verified/L1GraphTokenGateway/sources/@openzeppelin/contracts-upgradeable/proxy/Initializable.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT + +// solhint-disable-next-line compiler-version +pragma solidity >=0.4.24 <0.8.0; + +import "../utils/AddressUpgradeable.sol"; + +/** + * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed + * behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an + * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer + * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. + * + * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as + * possible by providing the encoded function call as the `_data` argument to {UpgradeableProxy-constructor}. + * + * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure + * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. + */ +abstract contract Initializable { + + /** + * @dev Indicates that the contract has been initialized. + */ + bool private _initialized; + + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool private _initializing; + + /** + * @dev Modifier to protect an initializer function from being invoked twice. + */ + modifier initializer() { + require(_initializing || _isConstructor() || !_initialized, "Initializable: contract is already initialized"); + + bool isTopLevelCall = !_initializing; + if (isTopLevelCall) { + _initializing = true; + _initialized = true; + } + + _; + + if (isTopLevelCall) { + _initializing = false; + } + } + + /// @dev Returns true if and only if the function is running in the constructor + function _isConstructor() private view returns (bool) { + return !AddressUpgradeable.isContract(address(this)); + } +} diff --git a/packages/legacy/verified/L1GraphTokenGateway/sources/@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol b/packages/legacy/verified/L1GraphTokenGateway/sources/@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol new file mode 100644 index 000000000..e4c8c06ec --- /dev/null +++ b/packages/legacy/verified/L1GraphTokenGateway/sources/@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20Upgradeable { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} diff --git a/packages/legacy/verified/L1GraphTokenGateway/sources/@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol b/packages/legacy/verified/L1GraphTokenGateway/sources/@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol new file mode 100644 index 000000000..08a59041b --- /dev/null +++ b/packages/legacy/verified/L1GraphTokenGateway/sources/@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.2 <0.8.0; + +/** + * @dev Collection of functions related to the address type + */ +library AddressUpgradeable { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + uint256 size; + // solhint-disable-next-line no-inline-assembly + assembly { size := extcodesize(account) } + return size > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + // solhint-disable-next-line avoid-low-level-calls, avoid-call-value + (bool success, ) = recipient.call{ value: amount }(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain`call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.call{ value: value }(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.staticcall(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) { + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + + // solhint-disable-next-line no-inline-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} diff --git a/packages/legacy/verified/L1GraphTokenGateway/sources/@openzeppelin/contracts/token/ERC20/IERC20.sol b/packages/legacy/verified/L1GraphTokenGateway/sources/@openzeppelin/contracts/token/ERC20/IERC20.sol new file mode 100644 index 000000000..9b877d119 --- /dev/null +++ b/packages/legacy/verified/L1GraphTokenGateway/sources/@openzeppelin/contracts/token/ERC20/IERC20.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} diff --git a/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/arbitrum/IBridge.sol b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/arbitrum/IBridge.sol new file mode 100644 index 000000000..ff78253fc --- /dev/null +++ b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/arbitrum/IBridge.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2021, Offchain Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Originally copied from: + * https://github.com/OffchainLabs/arbitrum/tree/e3a6307ad8a2dc2cad35728a2a9908cfd8dd8ef9/packages/arb-bridge-eth + * + * MODIFIED from Offchain Labs' implementation: + * - Changed solidity version to 0.7.6 (pablo@edgeandnode.com) + * + */ + +pragma solidity ^0.7.6; + +interface IBridge { + event MessageDelivered( + uint256 indexed messageIndex, + bytes32 indexed beforeInboxAcc, + address inbox, + uint8 kind, + address sender, + bytes32 messageDataHash + ); + + event BridgeCallTriggered( + address indexed outbox, + address indexed destAddr, + uint256 amount, + bytes data + ); + + event InboxToggle(address indexed inbox, bool enabled); + + event OutboxToggle(address indexed outbox, bool enabled); + + function deliverMessageToInbox( + uint8 kind, + address sender, + bytes32 messageDataHash + ) external payable returns (uint256); + + function executeCall( + address destAddr, + uint256 amount, + bytes calldata data + ) external returns (bool success, bytes memory returnData); + + // These are only callable by the admin + function setInbox(address inbox, bool enabled) external; + + function setOutbox(address inbox, bool enabled) external; + + // View functions + + function activeOutbox() external view returns (address); + + function allowedInboxes(address inbox) external view returns (bool); + + function allowedOutboxes(address outbox) external view returns (bool); + + function inboxAccs(uint256 index) external view returns (bytes32); + + function messageCount() external view returns (uint256); +} diff --git a/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/arbitrum/IInbox.sol b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/arbitrum/IInbox.sol new file mode 100644 index 000000000..a9315bbf8 --- /dev/null +++ b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/arbitrum/IInbox.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2021, Offchain Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Originally copied from: + * https://github.com/OffchainLabs/arbitrum/tree/e3a6307ad8a2dc2cad35728a2a9908cfd8dd8ef9/packages/arb-bridge-eth + * + * MODIFIED from Offchain Labs' implementation: + * - Changed solidity version to 0.7.6 (pablo@edgeandnode.com) + * + */ + +pragma solidity ^0.7.6; + +import "./IBridge.sol"; +import "./IMessageProvider.sol"; + +interface IInbox is IMessageProvider { + function sendL2Message(bytes calldata messageData) external returns (uint256); + + function sendUnsignedTransaction( + uint256 maxGas, + uint256 gasPriceBid, + uint256 nonce, + address destAddr, + uint256 amount, + bytes calldata data + ) external returns (uint256); + + function sendContractTransaction( + uint256 maxGas, + uint256 gasPriceBid, + address destAddr, + uint256 amount, + bytes calldata data + ) external returns (uint256); + + function sendL1FundedUnsignedTransaction( + uint256 maxGas, + uint256 gasPriceBid, + uint256 nonce, + address destAddr, + bytes calldata data + ) external payable returns (uint256); + + function sendL1FundedContractTransaction( + uint256 maxGas, + uint256 gasPriceBid, + address destAddr, + bytes calldata data + ) external payable returns (uint256); + + function createRetryableTicket( + address destAddr, + uint256 arbTxCallValue, + uint256 maxSubmissionCost, + address submissionRefundAddress, + address valueRefundAddress, + uint256 maxGas, + uint256 gasPriceBid, + bytes calldata data + ) external payable returns (uint256); + + function depositEth(uint256 maxSubmissionCost) external payable returns (uint256); + + function bridge() external view returns (IBridge); + + function pauseCreateRetryables() external; + + function unpauseCreateRetryables() external; + + function startRewriteAddress() external; + + function stopRewriteAddress() external; +} diff --git a/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/arbitrum/IMessageProvider.sol b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/arbitrum/IMessageProvider.sol new file mode 100644 index 000000000..8fbfdb171 --- /dev/null +++ b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/arbitrum/IMessageProvider.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2021, Offchain Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Originally copied from: + * https://github.com/OffchainLabs/arbitrum/tree/e3a6307ad8a2dc2cad35728a2a9908cfd8dd8ef9/packages/arb-bridge-eth + * + * MODIFIED from Offchain Labs' implementation: + * - Changed solidity version to 0.7.6 (pablo@edgeandnode.com) + * + */ + +pragma solidity ^0.7.6; + +interface IMessageProvider { + event InboxMessageDelivered(uint256 indexed messageNum, bytes data); + + event InboxMessageDeliveredFromOrigin(uint256 indexed messageNum); +} diff --git a/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/arbitrum/IOutbox.sol b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/arbitrum/IOutbox.sol new file mode 100644 index 000000000..687c86abf --- /dev/null +++ b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/arbitrum/IOutbox.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2021, Offchain Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Originally copied from: + * https://github.com/OffchainLabs/arbitrum/tree/e3a6307ad8a2dc2cad35728a2a9908cfd8dd8ef9/packages/arb-bridge-eth + * + * MODIFIED from Offchain Labs' implementation: + * - Changed solidity version to 0.7.6 (pablo@edgeandnode.com) + * + */ + +pragma solidity ^0.7.6; + +interface IOutbox { + event OutboxEntryCreated( + uint256 indexed batchNum, + uint256 outboxEntryIndex, + bytes32 outputRoot, + uint256 numInBatch + ); + event OutBoxTransactionExecuted( + address indexed destAddr, + address indexed l2Sender, + uint256 indexed outboxEntryIndex, + uint256 transactionIndex + ); + + function l2ToL1Sender() external view returns (address); + + function l2ToL1Block() external view returns (uint256); + + function l2ToL1EthBlock() external view returns (uint256); + + function l2ToL1Timestamp() external view returns (uint256); + + function l2ToL1BatchNum() external view returns (uint256); + + function l2ToL1OutputId() external view returns (bytes32); + + function processOutgoingMessages(bytes calldata sendsData, uint256[] calldata sendLengths) + external; + + function outboxEntryExists(uint256 batchNum) external view returns (bool); +} diff --git a/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/arbitrum/ITokenGateway.sol b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/arbitrum/ITokenGateway.sol new file mode 100644 index 000000000..977fe07f2 --- /dev/null +++ b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/arbitrum/ITokenGateway.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2020, Offchain Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Originally copied from: + * https://github.com/OffchainLabs/arbitrum/tree/e3a6307ad8a2dc2cad35728a2a9908cfd8dd8ef9/packages/arb-bridge-peripherals + * + * MODIFIED from Offchain Labs' implementation: + * - Changed solidity version to 0.7.6 (pablo@edgeandnode.com) + * + */ + +pragma solidity ^0.7.6; + +interface ITokenGateway { + /// @notice event deprecated in favor of DepositInitiated and WithdrawalInitiated + // event OutboundTransferInitiated( + // address token, + // address indexed _from, + // address indexed _to, + // uint256 indexed _transferId, + // uint256 _amount, + // bytes _data + // ); + + /// @notice event deprecated in favor of DepositFinalized and WithdrawalFinalized + // event InboundTransferFinalized( + // address token, + // address indexed _from, + // address indexed _to, + // uint256 indexed _transferId, + // uint256 _amount, + // bytes _data + // ); + + function outboundTransfer( + address _token, + address _to, + uint256 _amount, + uint256 _maxGas, + uint256 _gasPriceBid, + bytes calldata _data + ) external payable returns (bytes memory); + + function finalizeInboundTransfer( + address _token, + address _from, + address _to, + uint256 _amount, + bytes calldata _data + ) external payable; + + /** + * @notice Calculate the address used when bridging an ERC20 token + * @dev the L1 and L2 address oracles may not always be in sync. + * For example, a custom token may have been registered but not deployed or the contract self destructed. + * @param l1ERC20 address of L1 token + * @return L2 address of a bridged ERC20 token + */ + function calculateL2TokenAddress(address l1ERC20) external view returns (address); +} diff --git a/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/arbitrum/L1ArbitrumMessenger.sol b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/arbitrum/L1ArbitrumMessenger.sol new file mode 100644 index 000000000..b893fa262 --- /dev/null +++ b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/arbitrum/L1ArbitrumMessenger.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2020, Offchain Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Originally copied from: + * https://github.com/OffchainLabs/arbitrum/tree/e3a6307ad8a2dc2cad35728a2a9908cfd8dd8ef9/packages/arb-bridge-peripherals + * + * MODIFIED from Offchain Labs' implementation: + * - Changed solidity version to 0.7.6 (pablo@edgeandnode.com) + * + */ + +pragma solidity ^0.7.6; + +import "./IInbox.sol"; +import "./IOutbox.sol"; + +/// @notice L1 utility contract to assist with L1 <=> L2 interactions +/// @dev this is an abstract contract instead of library so the functions can be easily overriden when testing +abstract contract L1ArbitrumMessenger { + event TxToL2(address indexed _from, address indexed _to, uint256 indexed _seqNum, bytes _data); + + struct L2GasParams { + uint256 _maxSubmissionCost; + uint256 _maxGas; + uint256 _gasPriceBid; + } + + function sendTxToL2( + address _inbox, + address _to, + address _user, + uint256 _l1CallValue, + uint256 _l2CallValue, + L2GasParams memory _l2GasParams, + bytes memory _data + ) internal virtual returns (uint256) { + // alternative function entry point when struggling with the stack size + return + sendTxToL2( + _inbox, + _to, + _user, + _l1CallValue, + _l2CallValue, + _l2GasParams._maxSubmissionCost, + _l2GasParams._maxGas, + _l2GasParams._gasPriceBid, + _data + ); + } + + function sendTxToL2( + address _inbox, + address _to, + address _user, + uint256 _l1CallValue, + uint256 _l2CallValue, + uint256 _maxSubmissionCost, + uint256 _maxGas, + uint256 _gasPriceBid, + bytes memory _data + ) internal virtual returns (uint256) { + uint256 seqNum = IInbox(_inbox).createRetryableTicket{ value: _l1CallValue }( + _to, + _l2CallValue, + _maxSubmissionCost, + _user, + _user, + _maxGas, + _gasPriceBid, + _data + ); + emit TxToL2(_user, _to, seqNum, _data); + return seqNum; + } + + function getBridge(address _inbox) internal view virtual returns (IBridge) { + return IInbox(_inbox).bridge(); + } + + /// @dev the l2ToL1Sender behaves as the tx.origin, the msg.sender should be validated to protect against reentrancies + function getL2ToL1Sender(address _inbox) internal view virtual returns (address) { + IOutbox outbox = IOutbox(getBridge(_inbox).activeOutbox()); + address l2ToL1Sender = outbox.l2ToL1Sender(); + + require(l2ToL1Sender != address(0), "NO_SENDER"); + return l2ToL1Sender; + } +} diff --git a/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/curation/ICuration.sol b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/curation/ICuration.sol new file mode 100644 index 000000000..9e1701aaf --- /dev/null +++ b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/curation/ICuration.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import "./IGraphCurationToken.sol"; + +interface ICuration { + // -- Configuration -- + + function setDefaultReserveRatio(uint32 _defaultReserveRatio) external; + + function setMinimumCurationDeposit(uint256 _minimumCurationDeposit) external; + + function setCurationTaxPercentage(uint32 _percentage) external; + + function setCurationTokenMaster(address _curationTokenMaster) external; + + // -- Curation -- + + function mint( + bytes32 _subgraphDeploymentID, + uint256 _tokensIn, + uint256 _signalOutMin + ) external returns (uint256, uint256); + + function burn( + bytes32 _subgraphDeploymentID, + uint256 _signalIn, + uint256 _tokensOutMin + ) external returns (uint256); + + function collect(bytes32 _subgraphDeploymentID, uint256 _tokens) external; + + // -- Getters -- + + function isCurated(bytes32 _subgraphDeploymentID) external view returns (bool); + + function getCuratorSignal(address _curator, bytes32 _subgraphDeploymentID) + external + view + returns (uint256); + + function getCurationPoolSignal(bytes32 _subgraphDeploymentID) external view returns (uint256); + + function getCurationPoolTokens(bytes32 _subgraphDeploymentID) external view returns (uint256); + + function tokensToSignal(bytes32 _subgraphDeploymentID, uint256 _tokensIn) + external + view + returns (uint256, uint256); + + function signalToTokens(bytes32 _subgraphDeploymentID, uint256 _signalIn) + external + view + returns (uint256); + + function curationTaxPercentage() external view returns (uint32); +} diff --git a/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/curation/IGraphCurationToken.sol b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/curation/IGraphCurationToken.sol new file mode 100644 index 000000000..43679aba6 --- /dev/null +++ b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/curation/IGraphCurationToken.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; + +interface IGraphCurationToken is IERC20Upgradeable { + function initialize(address _owner) external; + + function burnFrom(address _account, uint256 _amount) external; + + function mint(address _to, uint256 _amount) external; +} diff --git a/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/epochs/IEpochManager.sol b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/epochs/IEpochManager.sol new file mode 100644 index 000000000..36b1f47a3 --- /dev/null +++ b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/epochs/IEpochManager.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +interface IEpochManager { + // -- Configuration -- + + function setEpochLength(uint256 _epochLength) external; + + // -- Epochs + + function runEpoch() external; + + // -- Getters -- + + function isCurrentEpochRun() external view returns (bool); + + function blockNum() external view returns (uint256); + + function blockHash(uint256 _block) external view returns (bytes32); + + function currentEpoch() external view returns (uint256); + + function currentEpochBlock() external view returns (uint256); + + function currentEpochBlockSinceStart() external view returns (uint256); + + function epochsSince(uint256 _epoch) external view returns (uint256); + + function epochsSinceUpdate() external view returns (uint256); +} diff --git a/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/gateway/GraphTokenGateway.sol b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/gateway/GraphTokenGateway.sol new file mode 100644 index 000000000..ca2ad4c95 --- /dev/null +++ b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/gateway/GraphTokenGateway.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import { GraphUpgradeable } from "../upgrades/GraphUpgradeable.sol"; +import { ITokenGateway } from "../arbitrum/ITokenGateway.sol"; +import { Pausable } from "../governance/Pausable.sol"; +import { Managed } from "../governance/Managed.sol"; + +/** + * @title L1/L2 Graph Token Gateway + * @dev This includes everything that's shared between the L1 and L2 sides of the bridge. + */ +abstract contract GraphTokenGateway is GraphUpgradeable, Pausable, Managed, ITokenGateway { + /// @dev Storage gap added in case we need to add state variables to this contract + uint256[50] private __gap; + + /** + * @dev Check if the caller is the Controller's governor or this contract's pause guardian. + */ + modifier onlyGovernorOrGuardian() { + require( + msg.sender == controller.getGovernor() || msg.sender == pauseGuardian, + "Only Governor or Guardian" + ); + _; + } + + /** + * @notice Change the Pause Guardian for this contract + * @param _newPauseGuardian The address of the new Pause Guardian + */ + function setPauseGuardian(address _newPauseGuardian) external onlyGovernor { + require(_newPauseGuardian != address(0), "PauseGuardian must be set"); + _setPauseGuardian(_newPauseGuardian); + } + + /** + * @notice Change the paused state of the contract + * @param _newPaused New value for the pause state (true means the transfers will be paused) + */ + function setPaused(bool _newPaused) external onlyGovernorOrGuardian { + if (!_newPaused) { + _checksBeforeUnpause(); + } + _setPaused(_newPaused); + } + + /** + * @notice Getter to access paused state of this contract + * @return True if the contract is paused, false otherwise + */ + function paused() external view returns (bool) { + return _paused; + } + + /** + * @dev Override the default pausing from Managed to allow pausing this + * particular contract instead of pausing from the Controller. + */ + function _notPaused() internal view override { + require(!_paused, "Paused (contract)"); + } + + /** + * @dev Runs state validation before unpausing, reverts if + * something is not set properly + */ + function _checksBeforeUnpause() internal view virtual; +} diff --git a/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/gateway/L1GraphTokenGateway.sol b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/gateway/L1GraphTokenGateway.sol new file mode 100644 index 000000000..d946b78a7 --- /dev/null +++ b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/gateway/L1GraphTokenGateway.sol @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; +pragma abicoder v2; + +import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol"; +import { AddressUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; +import { SafeMathUpgradeable } from "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; + +import { L1ArbitrumMessenger } from "../arbitrum/L1ArbitrumMessenger.sol"; +import { IBridge } from "../arbitrum/IBridge.sol"; +import { IInbox } from "../arbitrum/IInbox.sol"; +import { IOutbox } from "../arbitrum/IOutbox.sol"; +import { ITokenGateway } from "../arbitrum/ITokenGateway.sol"; +import { Managed } from "../governance/Managed.sol"; +import { GraphTokenGateway } from "./GraphTokenGateway.sol"; +import { IGraphToken } from "../token/IGraphToken.sol"; + +/** + * @title L1 Graph Token Gateway Contract + * @dev Provides the L1 side of the Ethereum-Arbitrum GRT bridge. Sends GRT to the L2 chain + * by escrowing them and sending a message to the L2 gateway, and receives tokens from L2 by + * releasing them from escrow. + * Based on Offchain Labs' reference implementation and Livepeer's arbitrum-lpt-bridge + * (See: https://github.com/OffchainLabs/arbitrum/tree/master/packages/arb-bridge-peripherals/contracts/tokenbridge + * and https://github.com/livepeer/arbitrum-lpt-bridge) + */ +contract L1GraphTokenGateway is Initializable, GraphTokenGateway, L1ArbitrumMessenger { + using SafeMathUpgradeable for uint256; + + /// Address of the Graph Token contract on L2 + address public l2GRT; + /// Address of the Arbitrum Inbox + address public inbox; + /// Address of the Arbitrum Gateway Router on L1 + address public l1Router; + /// Address of the L2GraphTokenGateway on L2 that is the counterpart of this gateway + address public l2Counterpart; + /// Address of the BridgeEscrow contract that holds the GRT in the bridge + address public escrow; + /// Addresses for which this mapping is true are allowed to send callhooks in outbound transfers + mapping(address => bool) public callhookAllowlist; + /// Total amount minted from L2 + uint256 public totalMintedFromL2; + /// Accumulated allowance for tokens minted from L2 at lastL2MintAllowanceUpdateBlock + uint256 public accumulatedL2MintAllowanceSnapshot; + /// Block at which new L2 allowance starts accumulating + uint256 public lastL2MintAllowanceUpdateBlock; + /// New L2 mint allowance per block + uint256 public l2MintAllowancePerBlock; + + /// Emitted when an outbound transfer is initiated, i.e. tokens are deposited from L1 to L2 + event DepositInitiated( + address l1Token, + address indexed from, + address indexed to, + uint256 indexed sequenceNumber, + uint256 amount + ); + + /// Emitted when an incoming transfer is finalized, i.e tokens are withdrawn from L2 to L1 + event WithdrawalFinalized( + address l1Token, + address indexed from, + address indexed to, + uint256 indexed exitNum, + uint256 amount + ); + + /// Emitted when the Arbitrum Inbox and Gateway Router addresses have been updated + event ArbitrumAddressesSet(address inbox, address l1Router); + /// Emitted when the L2 GRT address has been updated + event L2TokenAddressSet(address l2GRT); + /// Emitted when the counterpart L2GraphTokenGateway address has been updated + event L2CounterpartAddressSet(address l2Counterpart); + /// Emitted when the escrow address has been updated + event EscrowAddressSet(address escrow); + /// Emitted when an address is added to the callhook allowlist + event AddedToCallhookAllowlist(address newAllowlisted); + /// Emitted when an address is removed from the callhook allowlist + event RemovedFromCallhookAllowlist(address notAllowlisted); + /// Emitted when the L2 mint allowance per block is updated + event L2MintAllowanceUpdated( + uint256 accumulatedL2MintAllowanceSnapshot, + uint256 l2MintAllowancePerBlock, + uint256 lastL2MintAllowanceUpdateBlock + ); + /// Emitted when tokens are minted due to an incoming transfer from L2 + event TokensMintedFromL2(uint256 amount); + + /** + * @dev Allows a function to be called only by the gateway's L2 counterpart. + * The message will actually come from the Arbitrum Bridge, but the Outbox + * can tell us who the sender from L2 is. + */ + modifier onlyL2Counterpart() { + require(inbox != address(0), "INBOX_NOT_SET"); + require(l2Counterpart != address(0), "L2_COUNTERPART_NOT_SET"); + + // a message coming from the counterpart gateway was executed by the bridge + IBridge bridge = IInbox(inbox).bridge(); + require(msg.sender == address(bridge), "NOT_FROM_BRIDGE"); + + // and the outbox reports that the L2 address of the sender is the counterpart gateway + address l2ToL1Sender = IOutbox(bridge.activeOutbox()).l2ToL1Sender(); + require(l2ToL1Sender == l2Counterpart, "ONLY_COUNTERPART_GATEWAY"); + _; + } + + /** + * @notice Initialize the L1GraphTokenGateway contract. + * @dev The contract will be paused. + * Note some parameters have to be set separately as they are generally + * not expected to be available at initialization time: + * - inbox and l1Router using setArbitrumAddresses + * - l2GRT using setL2TokenAddress + * - l2Counterpart using setL2CounterpartAddress + * - escrow using setEscrowAddress + * - allowlisted callhook callers using addToCallhookAllowlist + * - pauseGuardian using setPauseGuardian + * @param _controller Address of the Controller that manages this contract + */ + function initialize(address _controller) external onlyImpl initializer { + Managed._initialize(_controller); + _paused = true; + } + + /** + * @notice Sets the addresses for L1 contracts provided by Arbitrum + * @param _inbox Address of the Inbox that is part of the Arbitrum Bridge + * @param _l1Router Address of the Gateway Router + */ + function setArbitrumAddresses(address _inbox, address _l1Router) external onlyGovernor { + require(_inbox != address(0), "INVALID_INBOX"); + require(_l1Router != address(0), "INVALID_L1_ROUTER"); + require(!callhookAllowlist[_l1Router], "ROUTER_CANT_BE_ALLOWLISTED"); + require(AddressUpgradeable.isContract(_inbox), "INBOX_MUST_BE_CONTRACT"); + require(AddressUpgradeable.isContract(_l1Router), "ROUTER_MUST_BE_CONTRACT"); + inbox = _inbox; + l1Router = _l1Router; + emit ArbitrumAddressesSet(_inbox, _l1Router); + } + + /** + * @notice Sets the address of the L2 Graph Token + * @param _l2GRT Address of the GRT contract on L2 + */ + function setL2TokenAddress(address _l2GRT) external onlyGovernor { + require(_l2GRT != address(0), "INVALID_L2_GRT"); + l2GRT = _l2GRT; + emit L2TokenAddressSet(_l2GRT); + } + + /** + * @notice Sets the address of the counterpart gateway on L2 + * @param _l2Counterpart Address of the corresponding L2GraphTokenGateway on Arbitrum + */ + function setL2CounterpartAddress(address _l2Counterpart) external onlyGovernor { + require(_l2Counterpart != address(0), "INVALID_L2_COUNTERPART"); + l2Counterpart = _l2Counterpart; + emit L2CounterpartAddressSet(_l2Counterpart); + } + + /** + * @notice Sets the address of the escrow contract on L1 + * @param _escrow Address of the BridgeEscrow + */ + function setEscrowAddress(address _escrow) external onlyGovernor { + require(_escrow != address(0), "INVALID_ESCROW"); + require(AddressUpgradeable.isContract(_escrow), "MUST_BE_CONTRACT"); + escrow = _escrow; + emit EscrowAddressSet(_escrow); + } + + /** + * @notice Adds an address to the callhook allowlist. + * This address will be allowed to include callhooks when transferring tokens. + * @param _newAllowlisted Address to add to the allowlist + */ + function addToCallhookAllowlist(address _newAllowlisted) external onlyGovernor { + require(_newAllowlisted != address(0), "INVALID_ADDRESS"); + require(_newAllowlisted != l1Router, "CANT_ALLOW_ROUTER"); + require(AddressUpgradeable.isContract(_newAllowlisted), "MUST_BE_CONTRACT"); + require(!callhookAllowlist[_newAllowlisted], "ALREADY_ALLOWLISTED"); + callhookAllowlist[_newAllowlisted] = true; + emit AddedToCallhookAllowlist(_newAllowlisted); + } + + /** + * @notice Removes an address from the callhook allowlist. + * This address will no longer be allowed to include callhooks when transferring tokens. + * @param _notAllowlisted Address to remove from the allowlist + */ + function removeFromCallhookAllowlist(address _notAllowlisted) external onlyGovernor { + require(_notAllowlisted != address(0), "INVALID_ADDRESS"); + require(callhookAllowlist[_notAllowlisted], "NOT_ALLOWLISTED"); + callhookAllowlist[_notAllowlisted] = false; + emit RemovedFromCallhookAllowlist(_notAllowlisted); + } + + /** + * @dev Updates the L2 mint allowance per block + * It is meant to be called _after_ the issuancePerBlock is updated in L2. + * The caller should provide the new issuance per block and the block at which it was updated, + * the function will automatically compute the values so that the bridge's mint allowance + * correctly tracks the maximum rewards minted in L2. + * @param _l2IssuancePerBlock New issuancePerBlock that has been set in L2 + * @param _updateBlockNum L1 Block number at which issuancePerBlock was updated in L2 + */ + function updateL2MintAllowance(uint256 _l2IssuancePerBlock, uint256 _updateBlockNum) + external + onlyGovernor + { + require(_updateBlockNum < block.number, "BLOCK_MUST_BE_PAST"); + require(_updateBlockNum > lastL2MintAllowanceUpdateBlock, "BLOCK_MUST_BE_INCREMENTING"); + accumulatedL2MintAllowanceSnapshot = accumulatedL2MintAllowanceAtBlock(_updateBlockNum); + lastL2MintAllowanceUpdateBlock = _updateBlockNum; + l2MintAllowancePerBlock = _l2IssuancePerBlock; + emit L2MintAllowanceUpdated( + accumulatedL2MintAllowanceSnapshot, + l2MintAllowancePerBlock, + lastL2MintAllowanceUpdateBlock + ); + } + + /** + * @dev Manually sets the parameters used to compute the L2 mint allowance + * The use of this function is not recommended, use updateL2MintAllowance instead; + * this one is only meant to be used as a backup recovery if a previous call to + * updateL2MintAllowance was done with incorrect values. + * @param _accumulatedL2MintAllowanceSnapshot Accumulated L2 mint allowance at L1 block _lastL2MintAllowanceUpdateBlock + * @param _l2MintAllowancePerBlock L2 issuance per block since block number _lastL2MintAllowanceUpdateBlock + * @param _lastL2MintAllowanceUpdateBlock L1 Block number at which issuancePerBlock was last updated in L2 + */ + function setL2MintAllowanceParametersManual( + uint256 _accumulatedL2MintAllowanceSnapshot, + uint256 _l2MintAllowancePerBlock, + uint256 _lastL2MintAllowanceUpdateBlock + ) external onlyGovernor { + require(_lastL2MintAllowanceUpdateBlock < block.number, "BLOCK_MUST_BE_PAST"); + accumulatedL2MintAllowanceSnapshot = _accumulatedL2MintAllowanceSnapshot; + l2MintAllowancePerBlock = _l2MintAllowancePerBlock; + lastL2MintAllowanceUpdateBlock = _lastL2MintAllowanceUpdateBlock; + emit L2MintAllowanceUpdated( + accumulatedL2MintAllowanceSnapshot, + l2MintAllowancePerBlock, + lastL2MintAllowanceUpdateBlock + ); + } + + /** + * @notice Creates and sends a retryable ticket to transfer GRT to L2 using the Arbitrum Inbox. + * The tokens are escrowed by the gateway until they are withdrawn back to L1. + * The ticket must be redeemed on L2 to receive tokens at the specified address. + * Note that the caller must previously allow the gateway to spend the specified amount of GRT. + * @dev maxGas and gasPriceBid must be set using Arbitrum's NodeInterface.estimateRetryableTicket method. + * Also note that allowlisted senders (some protocol contracts) can include additional calldata + * for a callhook to be executed on the L2 side when the tokens are received. In this case, the L2 transaction + * can revert if the callhook reverts, potentially locking the tokens on the bridge if the callhook + * never succeeds. This requires extra care when adding contracts to the allowlist, but is necessary to ensure that + * the tickets can be retried in the case of a temporary failure, and to ensure the atomicity of callhooks + * with token transfers. + * @param _l1Token L1 Address of the GRT contract (needed for compatibility with Arbitrum Gateway Router) + * @param _to Recipient address on L2 + * @param _amount Amount of tokens to transfer + * @param _maxGas Gas limit for L2 execution of the ticket + * @param _gasPriceBid Price per gas on L2 + * @param _data Encoded maxSubmissionCost and sender address along with additional calldata + * @return Sequence number of the retryable ticket created by Inbox + */ + function outboundTransfer( + address _l1Token, + address _to, + uint256 _amount, + uint256 _maxGas, + uint256 _gasPriceBid, + bytes calldata _data + ) external payable override notPaused returns (bytes memory) { + IGraphToken token = graphToken(); + require(_amount != 0, "INVALID_ZERO_AMOUNT"); + require(_l1Token == address(token), "TOKEN_NOT_GRT"); + require(_to != address(0), "INVALID_DESTINATION"); + + // nested scopes to avoid stack too deep errors + address from; + uint256 seqNum; + { + uint256 maxSubmissionCost; + bytes memory outboundCalldata; + { + bytes memory extraData; + (from, maxSubmissionCost, extraData) = _parseOutboundData(_data); + require( + extraData.length == 0 || callhookAllowlist[msg.sender] == true, + "CALL_HOOK_DATA_NOT_ALLOWED" + ); + require(maxSubmissionCost != 0, "NO_SUBMISSION_COST"); + outboundCalldata = getOutboundCalldata(_l1Token, from, _to, _amount, extraData); + } + { + L2GasParams memory gasParams = L2GasParams( + maxSubmissionCost, + _maxGas, + _gasPriceBid + ); + // transfer tokens to escrow + token.transferFrom(from, escrow, _amount); + seqNum = sendTxToL2( + inbox, + l2Counterpart, + from, + msg.value, + 0, + gasParams, + outboundCalldata + ); + } + } + emit DepositInitiated(_l1Token, from, _to, seqNum, _amount); + + return abi.encode(seqNum); + } + + /** + * @notice Receives withdrawn tokens from L2 + * The equivalent tokens are released from escrow and sent to the destination. + * @dev can only accept transactions coming from the L2 GRT Gateway. + * The last parameter is unused but kept for compatibility with Arbitrum gateways, + * and the encoded exitNum is assumed to be 0. + * @param _l1Token L1 Address of the GRT contract (needed for compatibility with Arbitrum Gateway Router) + * @param _from Address of the sender + * @param _to Recipient address on L1 + * @param _amount Amount of tokens transferred + */ + function finalizeInboundTransfer( + address _l1Token, + address _from, + address _to, + uint256 _amount, + bytes calldata // _data, contains exitNum, unused by this contract + ) external payable override notPaused onlyL2Counterpart { + IGraphToken token = graphToken(); + require(_l1Token == address(token), "TOKEN_NOT_GRT"); + + uint256 escrowBalance = token.balanceOf(escrow); + if (_amount > escrowBalance) { + // This will revert if trying to mint more than allowed + _mintFromL2(_amount.sub(escrowBalance)); + } + token.transferFrom(escrow, _to, _amount); + + emit WithdrawalFinalized(_l1Token, _from, _to, 0, _amount); + } + + /** + * @notice Calculate the L2 address of a bridged token + * @dev In our case, this would only work for GRT. + * @param _l1ERC20 address of L1 GRT contract + * @return L2 address of the bridged GRT token + */ + function calculateL2TokenAddress(address _l1ERC20) external view override returns (address) { + IGraphToken token = graphToken(); + if (_l1ERC20 != address(token)) { + return address(0); + } + return l2GRT; + } + + /** + * @notice Get the address of the L2GraphTokenGateway + * @dev This is added for compatibility with the Arbitrum Router's + * gateway registration process. + * @return Address of the L2 gateway connected to this gateway + */ + function counterpartGateway() external view returns (address) { + return l2Counterpart; + } + + /** + * @notice Creates calldata required to create a retryable ticket + * @dev encodes the target function with its params which + * will be called on L2 when the retryable ticket is redeemed + * @param _l1Token Address of the Graph token contract on L1 + * @param _from Address on L1 from which we're transferring tokens + * @param _to Address on L2 to which we're transferring tokens + * @param _amount Amount of GRT to transfer + * @param _data Additional call data for the L2 transaction, which must be empty unless the caller is allowlisted + * @return Encoded calldata (including function selector) for the L2 transaction + */ + function getOutboundCalldata( + address _l1Token, + address _from, + address _to, + uint256 _amount, + bytes memory _data + ) public pure returns (bytes memory) { + return + abi.encodeWithSelector( + ITokenGateway.finalizeInboundTransfer.selector, + _l1Token, + _from, + _to, + _amount, + _data + ); + } + + /** + * @dev Runs state validation before unpausing, reverts if + * something is not set properly + */ + function _checksBeforeUnpause() internal view override { + require(inbox != address(0), "INBOX_NOT_SET"); + require(l1Router != address(0), "ROUTER_NOT_SET"); + require(l2Counterpart != address(0), "L2_COUNTERPART_NOT_SET"); + require(escrow != address(0), "ESCROW_NOT_SET"); + } + + /** + * @notice Decodes calldata required for migration of tokens + * @dev Data must include maxSubmissionCost, extraData can be left empty. When the router + * sends an outbound message, data also contains the from address. + * @param _data encoded callhook data + * @return Sender of the tx + * @return Base ether value required to keep retryable ticket alive + * @return Additional data sent to L2 + */ + function _parseOutboundData(bytes calldata _data) + private + view + returns ( + address, + uint256, + bytes memory + ) + { + address from; + uint256 maxSubmissionCost; + bytes memory extraData; + if (msg.sender == l1Router) { + // Data encoded by the Gateway Router includes the sender address + (from, extraData) = abi.decode(_data, (address, bytes)); + } else { + from = msg.sender; + extraData = _data; + } + // User-encoded data contains the max retryable ticket submission cost + // and additional L2 calldata + (maxSubmissionCost, extraData) = abi.decode(extraData, (uint256, bytes)); + return (from, maxSubmissionCost, extraData); + } + + /** + * @dev Get the accumulated L2 mint allowance at a particular block number + * @param _blockNum Block at which allowance will be computed + * @return The accumulated GRT amount that can be minted from L2 at the specified block + */ + function accumulatedL2MintAllowanceAtBlock(uint256 _blockNum) public view returns (uint256) { + require(_blockNum >= lastL2MintAllowanceUpdateBlock, "INVALID_BLOCK_FOR_MINT_ALLOWANCE"); + return + accumulatedL2MintAllowanceSnapshot.add( + l2MintAllowancePerBlock.mul(_blockNum.sub(lastL2MintAllowanceUpdateBlock)) + ); + } + + /** + * @dev Mint new L1 tokens coming from L2 + * This will check if the amount to mint is within the L2's mint allowance, and revert otherwise. + * The tokens will be sent to the bridge escrow (from where they will then be sent to the destinatary + * of the current inbound transfer). + * @param _amount Number of tokens to mint + */ + function _mintFromL2(uint256 _amount) internal { + // If we're trying to mint more than allowed, something's gone terribly wrong + // (either the L2 issuance is wrong, or the Arbitrum bridge has been compromised) + require(_l2MintAmountAllowed(_amount), "INVALID_L2_MINT_AMOUNT"); + totalMintedFromL2 = totalMintedFromL2.add(_amount); + graphToken().mint(escrow, _amount); + emit TokensMintedFromL2(_amount); + } + + /** + * @dev Check if minting a certain amount of tokens from L2 is within allowance + * @param _amount Number of tokens that would be minted + * @return true if minting those tokens is allowed, or false if it would be over allowance + */ + function _l2MintAmountAllowed(uint256 _amount) internal view returns (bool) { + return (totalMintedFromL2.add(_amount) <= accumulatedL2MintAllowanceAtBlock(block.number)); + } +} diff --git a/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/governance/IController.sol b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/governance/IController.sol new file mode 100644 index 000000000..7df3d94ee --- /dev/null +++ b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/governance/IController.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; + +interface IController { + function getGovernor() external view returns (address); + + // -- Registry -- + + function setContractProxy(bytes32 _id, address _contractAddress) external; + + function unsetContractProxy(bytes32 _id) external; + + function updateController(bytes32 _id, address _controller) external; + + function getContractProxy(bytes32 _id) external view returns (address); + + // -- Pausing -- + + function setPartialPaused(bool _partialPaused) external; + + function setPaused(bool _paused) external; + + function setPauseGuardian(address _newPauseGuardian) external; + + function paused() external view returns (bool); + + function partialPaused() external view returns (bool); +} diff --git a/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/governance/IManaged.sol b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/governance/IManaged.sol new file mode 100644 index 000000000..1a458a460 --- /dev/null +++ b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/governance/IManaged.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +interface IManaged { + function setController(address _controller) external; +} diff --git a/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/governance/Managed.sol b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/governance/Managed.sol new file mode 100644 index 000000000..6b8fe624e --- /dev/null +++ b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/governance/Managed.sol @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import { IController } from "./IController.sol"; + +import { ICuration } from "../curation/ICuration.sol"; +import { IEpochManager } from "../epochs/IEpochManager.sol"; +import { IRewardsManager } from "../rewards/IRewardsManager.sol"; +import { IStaking } from "../staking/IStaking.sol"; +import { IGraphToken } from "../token/IGraphToken.sol"; +import { ITokenGateway } from "../arbitrum/ITokenGateway.sol"; + +import { IManaged } from "./IManaged.sol"; + +/** + * @title Graph Managed contract + * @dev The Managed contract provides an interface to interact with the Controller. + * It also provides local caching for contract addresses. This mechanism relies on calling the + * public `syncAllContracts()` function whenever a contract changes in the controller. + * + * Inspired by Livepeer: + * https://github.com/livepeer/protocol/blob/streamflow/contracts/Controller.sol + */ +abstract contract Managed is IManaged { + // -- State -- + + /// Controller that contract is registered with + IController public controller; + /// @dev Cache for the addresses of the contracts retrieved from the controller + mapping(bytes32 => address) private _addressCache; + /// @dev Gap for future storage variables + uint256[10] private __gap; + + // Immutables + bytes32 private immutable CURATION = keccak256("Curation"); + bytes32 private immutable EPOCH_MANAGER = keccak256("EpochManager"); + bytes32 private immutable REWARDS_MANAGER = keccak256("RewardsManager"); + bytes32 private immutable STAKING = keccak256("Staking"); + bytes32 private immutable GRAPH_TOKEN = keccak256("GraphToken"); + bytes32 private immutable GRAPH_TOKEN_GATEWAY = keccak256("GraphTokenGateway"); + + // -- Events -- + + /// Emitted when a contract parameter has been updated + event ParameterUpdated(string param); + /// Emitted when the controller address has been set + event SetController(address controller); + + /// Emitted when contract with `nameHash` is synced to `contractAddress`. + event ContractSynced(bytes32 indexed nameHash, address contractAddress); + + // -- Modifiers -- + + /** + * @dev Revert if the controller is paused or partially paused + */ + function _notPartialPaused() internal view { + require(!controller.paused(), "Paused"); + require(!controller.partialPaused(), "Partial-paused"); + } + + /** + * @dev Revert if the controller is paused + */ + function _notPaused() internal view virtual { + require(!controller.paused(), "Paused"); + } + + /** + * @dev Revert if the caller is not the governor + */ + function _onlyGovernor() internal view { + require(msg.sender == controller.getGovernor(), "Only Controller governor"); + } + + /** + * @dev Revert if the caller is not the Controller + */ + function _onlyController() internal view { + require(msg.sender == address(controller), "Caller must be Controller"); + } + + /** + * @dev Revert if the controller is paused or partially paused + */ + modifier notPartialPaused() { + _notPartialPaused(); + _; + } + + /** + * @dev Revert if the controller is paused + */ + modifier notPaused() { + _notPaused(); + _; + } + + /** + * @dev Revert if the caller is not the Controller + */ + modifier onlyController() { + _onlyController(); + _; + } + + /** + * @dev Revert if the caller is not the governor + */ + modifier onlyGovernor() { + _onlyGovernor(); + _; + } + + // -- Functions -- + + /** + * @dev Initialize a Managed contract + * @param _controller Address for the Controller that manages this contract + */ + function _initialize(address _controller) internal { + _setController(_controller); + } + + /** + * @notice Set Controller. Only callable by current controller. + * @param _controller Controller contract address + */ + function setController(address _controller) external override onlyController { + _setController(_controller); + } + + /** + * @dev Set controller. + * @param _controller Controller contract address + */ + function _setController(address _controller) internal { + require(_controller != address(0), "Controller must be set"); + controller = IController(_controller); + emit SetController(_controller); + } + + /** + * @dev Return Curation interface + * @return Curation contract registered with Controller + */ + function curation() internal view returns (ICuration) { + return ICuration(_resolveContract(CURATION)); + } + + /** + * @dev Return EpochManager interface + * @return Epoch manager contract registered with Controller + */ + function epochManager() internal view returns (IEpochManager) { + return IEpochManager(_resolveContract(EPOCH_MANAGER)); + } + + /** + * @dev Return RewardsManager interface + * @return Rewards manager contract registered with Controller + */ + function rewardsManager() internal view returns (IRewardsManager) { + return IRewardsManager(_resolveContract(REWARDS_MANAGER)); + } + + /** + * @dev Return Staking interface + * @return Staking contract registered with Controller + */ + function staking() internal view returns (IStaking) { + return IStaking(_resolveContract(STAKING)); + } + + /** + * @dev Return GraphToken interface + * @return Graph token contract registered with Controller + */ + function graphToken() internal view returns (IGraphToken) { + return IGraphToken(_resolveContract(GRAPH_TOKEN)); + } + + /** + * @dev Return GraphTokenGateway (L1 or L2) interface + * @return Graph token gateway contract registered with Controller + */ + function graphTokenGateway() internal view returns (ITokenGateway) { + return ITokenGateway(_resolveContract(GRAPH_TOKEN_GATEWAY)); + } + + /** + * @dev Resolve a contract address from the cache or the Controller if not found + * @param _nameHash keccak256 hash of the contract name + * @return Address of the contract + */ + function _resolveContract(bytes32 _nameHash) internal view returns (address) { + address contractAddress = _addressCache[_nameHash]; + if (contractAddress == address(0)) { + contractAddress = controller.getContractProxy(_nameHash); + } + return contractAddress; + } + + /** + * @dev Cache a contract address from the Controller registry. + * @param _name Name of the contract to sync into the cache + */ + function _syncContract(string memory _name) internal { + bytes32 nameHash = keccak256(abi.encodePacked(_name)); + address contractAddress = controller.getContractProxy(nameHash); + if (_addressCache[nameHash] != contractAddress) { + _addressCache[nameHash] = contractAddress; + emit ContractSynced(nameHash, contractAddress); + } + } + + /** + * @notice Sync protocol contract addresses from the Controller registry + * @dev This function will cache all the contracts using the latest addresses + * Anyone can call the function whenever a Proxy contract change in the + * controller to ensure the protocol is using the latest version + */ + function syncAllContracts() external { + _syncContract("Curation"); + _syncContract("EpochManager"); + _syncContract("RewardsManager"); + _syncContract("Staking"); + _syncContract("GraphToken"); + _syncContract("GraphTokenGateway"); + } +} diff --git a/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/governance/Pausable.sol b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/governance/Pausable.sol new file mode 100644 index 000000000..552b0aa15 --- /dev/null +++ b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/governance/Pausable.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +abstract contract Pausable { + /** + * @dev "Partial paused" pauses exit and enter functions for GRT, but not internal + * functions, such as allocating + */ + bool internal _partialPaused; + /** + * @dev Paused will pause all major protocol functions + */ + bool internal _paused; + + /// Timestamp for the last time the partial pause was set + uint256 public lastPausePartialTime; + /// Timestamp for the last time the full pause was set + uint256 public lastPauseTime; + + /// Pause guardian is a separate entity from the governor that can + /// pause and unpause the protocol, fully or partially + address public pauseGuardian; + + /// Emitted when the partial pause state changed + event PartialPauseChanged(bool isPaused); + /// Emitted when the full pause state changed + event PauseChanged(bool isPaused); + /// Emitted when the pause guardian is changed + event NewPauseGuardian(address indexed oldPauseGuardian, address indexed pauseGuardian); + + /** + * @dev Change the partial paused state of the contract + * @param _toPause New value for the partial pause state (true means the contracts will be partially paused) + */ + function _setPartialPaused(bool _toPause) internal { + if (_toPause == _partialPaused) { + return; + } + _partialPaused = _toPause; + if (_partialPaused) { + lastPausePartialTime = block.timestamp; + } + emit PartialPauseChanged(_partialPaused); + } + + /** + * @dev Change the paused state of the contract + * @param _toPause New value for the pause state (true means the contracts will be paused) + */ + function _setPaused(bool _toPause) internal { + if (_toPause == _paused) { + return; + } + _paused = _toPause; + if (_paused) { + lastPauseTime = block.timestamp; + } + emit PauseChanged(_paused); + } + + /** + * @dev Change the Pause Guardian + * @param newPauseGuardian The address of the new Pause Guardian + */ + function _setPauseGuardian(address newPauseGuardian) internal { + address oldPauseGuardian = pauseGuardian; + pauseGuardian = newPauseGuardian; + emit NewPauseGuardian(oldPauseGuardian, pauseGuardian); + } +} diff --git a/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/rewards/IRewardsManager.sol b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/rewards/IRewardsManager.sol new file mode 100644 index 000000000..87d3c2455 --- /dev/null +++ b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/rewards/IRewardsManager.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +interface IRewardsManager { + /** + * @dev Stores accumulated rewards and snapshots related to a particular SubgraphDeployment. + */ + struct Subgraph { + uint256 accRewardsForSubgraph; + uint256 accRewardsForSubgraphSnapshot; + uint256 accRewardsPerSignalSnapshot; + uint256 accRewardsPerAllocatedToken; + } + + // -- Config -- + + function setIssuancePerBlock(uint256 _issuancePerBlock) external; + + function setMinimumSubgraphSignal(uint256 _minimumSubgraphSignal) external; + + // -- Denylist -- + + function setSubgraphAvailabilityOracle(address _subgraphAvailabilityOracle) external; + + function setDenied(bytes32 _subgraphDeploymentID, bool _deny) external; + + function setDeniedMany(bytes32[] calldata _subgraphDeploymentID, bool[] calldata _deny) + external; + + function isDenied(bytes32 _subgraphDeploymentID) external view returns (bool); + + // -- Getters -- + + function getNewRewardsPerSignal() external view returns (uint256); + + function getAccRewardsPerSignal() external view returns (uint256); + + function getAccRewardsForSubgraph(bytes32 _subgraphDeploymentID) + external + view + returns (uint256); + + function getAccRewardsPerAllocatedToken(bytes32 _subgraphDeploymentID) + external + view + returns (uint256, uint256); + + function getRewards(address _allocationID) external view returns (uint256); + + // -- Updates -- + + function updateAccRewardsPerSignal() external returns (uint256); + + function takeRewards(address _allocationID) external returns (uint256); + + // -- Hooks -- + + function onSubgraphSignalUpdate(bytes32 _subgraphDeploymentID) external returns (uint256); + + function onSubgraphAllocationUpdate(bytes32 _subgraphDeploymentID) external returns (uint256); +} diff --git a/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/staking/IStaking.sol b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/staking/IStaking.sol new file mode 100644 index 000000000..86942e4d1 --- /dev/null +++ b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/staking/IStaking.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; +pragma abicoder v2; + +import "./IStakingData.sol"; + +interface IStaking is IStakingData { + // -- Allocation Data -- + + /** + * @dev Possible states an allocation can be + * States: + * - Null = indexer == address(0) + * - Active = not Null && tokens > 0 + * - Closed = Active && closedAtEpoch != 0 + * - Finalized = Closed && closedAtEpoch + channelDisputeEpochs > now() + * - Claimed = not Null && tokens == 0 + */ + enum AllocationState { + Null, + Active, + Closed, + Finalized, + Claimed + } + + // -- Configuration -- + + function setMinimumIndexerStake(uint256 _minimumIndexerStake) external; + + function setThawingPeriod(uint32 _thawingPeriod) external; + + function setCurationPercentage(uint32 _percentage) external; + + function setProtocolPercentage(uint32 _percentage) external; + + function setChannelDisputeEpochs(uint32 _channelDisputeEpochs) external; + + function setMaxAllocationEpochs(uint32 _maxAllocationEpochs) external; + + function setRebateRatio(uint32 _alphaNumerator, uint32 _alphaDenominator) external; + + function setDelegationRatio(uint32 _delegationRatio) external; + + function setDelegationParameters( + uint32 _indexingRewardCut, + uint32 _queryFeeCut, + uint32 _cooldownBlocks + ) external; + + function setDelegationParametersCooldown(uint32 _blocks) external; + + function setDelegationUnbondingPeriod(uint32 _delegationUnbondingPeriod) external; + + function setDelegationTaxPercentage(uint32 _percentage) external; + + function setSlasher(address _slasher, bool _allowed) external; + + function setAssetHolder(address _assetHolder, bool _allowed) external; + + // -- Operation -- + + function setOperator(address _operator, bool _allowed) external; + + function isOperator(address _operator, address _indexer) external view returns (bool); + + // -- Staking -- + + function stake(uint256 _tokens) external; + + function stakeTo(address _indexer, uint256 _tokens) external; + + function unstake(uint256 _tokens) external; + + function slash( + address _indexer, + uint256 _tokens, + uint256 _reward, + address _beneficiary + ) external; + + function withdraw() external; + + function setRewardsDestination(address _destination) external; + + // -- Delegation -- + + function delegate(address _indexer, uint256 _tokens) external returns (uint256); + + function undelegate(address _indexer, uint256 _shares) external returns (uint256); + + function withdrawDelegated(address _indexer, address _newIndexer) external returns (uint256); + + // -- Channel management and allocations -- + + function allocate( + bytes32 _subgraphDeploymentID, + uint256 _tokens, + address _allocationID, + bytes32 _metadata, + bytes calldata _proof + ) external; + + function allocateFrom( + address _indexer, + bytes32 _subgraphDeploymentID, + uint256 _tokens, + address _allocationID, + bytes32 _metadata, + bytes calldata _proof + ) external; + + function closeAllocation(address _allocationID, bytes32 _poi) external; + + function closeAllocationMany(CloseAllocationRequest[] calldata _requests) external; + + function closeAndAllocate( + address _oldAllocationID, + bytes32 _poi, + address _indexer, + bytes32 _subgraphDeploymentID, + uint256 _tokens, + address _allocationID, + bytes32 _metadata, + bytes calldata _proof + ) external; + + function collect(uint256 _tokens, address _allocationID) external; + + function claim(address _allocationID, bool _restake) external; + + function claimMany(address[] calldata _allocationID, bool _restake) external; + + // -- Getters and calculations -- + + function hasStake(address _indexer) external view returns (bool); + + function getIndexerStakedTokens(address _indexer) external view returns (uint256); + + function getIndexerCapacity(address _indexer) external view returns (uint256); + + function getAllocation(address _allocationID) external view returns (Allocation memory); + + function getAllocationState(address _allocationID) external view returns (AllocationState); + + function isAllocation(address _allocationID) external view returns (bool); + + function getSubgraphAllocatedTokens(bytes32 _subgraphDeploymentID) + external + view + returns (uint256); + + function getDelegation(address _indexer, address _delegator) + external + view + returns (Delegation memory); + + function isDelegator(address _indexer, address _delegator) external view returns (bool); +} diff --git a/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/staking/IStakingData.sol b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/staking/IStakingData.sol new file mode 100644 index 000000000..348a5a7f9 --- /dev/null +++ b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/staking/IStakingData.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; + +interface IStakingData { + /** + * @dev Allocate GRT tokens for the purpose of serving queries of a subgraph deployment + * An allocation is created in the allocate() function and consumed in claim() + */ + struct Allocation { + address indexer; + bytes32 subgraphDeploymentID; + uint256 tokens; // Tokens allocated to a SubgraphDeployment + uint256 createdAtEpoch; // Epoch when it was created + uint256 closedAtEpoch; // Epoch when it was closed + uint256 collectedFees; // Collected fees for the allocation + uint256 effectiveAllocation; // Effective allocation when closed + uint256 accRewardsPerAllocatedToken; // Snapshot used for reward calc + } + + /** + * @dev Represents a request to close an allocation with a specific proof of indexing. + * This is passed when calling closeAllocationMany to define the closing parameters for + * each allocation. + */ + struct CloseAllocationRequest { + address allocationID; + bytes32 poi; + } + + // -- Delegation Data -- + + /** + * @dev Delegation pool information. One per indexer. + */ + struct DelegationPool { + uint32 cooldownBlocks; // Blocks to wait before updating parameters + uint32 indexingRewardCut; // in PPM + uint32 queryFeeCut; // in PPM + uint256 updatedAtBlock; // Block when the pool was last updated + uint256 tokens; // Total tokens as pool reserves + uint256 shares; // Total shares minted in the pool + mapping(address => Delegation) delegators; // Mapping of delegator => Delegation + } + + /** + * @dev Individual delegation data of a delegator in a pool. + */ + struct Delegation { + uint256 shares; // Shares owned by a delegator in the pool + uint256 tokensLocked; // Tokens locked for undelegation + uint256 tokensLockedUntil; // Block when locked tokens can be withdrawn + } +} diff --git a/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/token/IGraphToken.sol b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/token/IGraphToken.sol new file mode 100644 index 000000000..8255e18d5 --- /dev/null +++ b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/token/IGraphToken.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface IGraphToken is IERC20 { + // -- Mint and Burn -- + + function burn(uint256 amount) external; + + function burnFrom(address _from, uint256 amount) external; + + function mint(address _to, uint256 _amount) external; + + // -- Mint Admin -- + + function addMinter(address _account) external; + + function removeMinter(address _account) external; + + function renounceMinter() external; + + function isMinter(address _account) external view returns (bool); + + // -- Permit -- + + function permit( + address _owner, + address _spender, + uint256 _value, + uint256 _deadline, + uint8 _v, + bytes32 _r, + bytes32 _s + ) external; + + // -- Allowance -- + + function increaseAllowance(address spender, uint256 addedValue) external returns (bool); + + function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool); +} diff --git a/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/upgrades/GraphUpgradeable.sol b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/upgrades/GraphUpgradeable.sol new file mode 100644 index 000000000..92ce80ad7 --- /dev/null +++ b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/upgrades/GraphUpgradeable.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import { IGraphProxy } from "./IGraphProxy.sol"; + +/** + * @title Graph Upgradeable + * @dev This contract is intended to be inherited from upgradeable contracts. + */ +abstract contract GraphUpgradeable { + /** + * @dev Storage slot with the address of the current implementation. + * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is + * validated in the constructor. + */ + bytes32 internal constant IMPLEMENTATION_SLOT = + 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + /** + * @dev Check if the caller is the proxy admin. + */ + modifier onlyProxyAdmin(IGraphProxy _proxy) { + require(msg.sender == _proxy.admin(), "Caller must be the proxy admin"); + _; + } + + /** + * @dev Check if the caller is the implementation. + */ + modifier onlyImpl() { + require(msg.sender == _implementation(), "Only implementation"); + _; + } + + /** + * @dev Returns the current implementation. + * @return impl Address of the current implementation + */ + function _implementation() internal view returns (address impl) { + bytes32 slot = IMPLEMENTATION_SLOT; + // solhint-disable-next-line no-inline-assembly + assembly { + impl := sload(slot) + } + } + + /** + * @notice Accept to be an implementation of proxy. + * @param _proxy Proxy to accept + */ + function acceptProxy(IGraphProxy _proxy) external onlyProxyAdmin(_proxy) { + _proxy.acceptUpgrade(); + } + + /** + * @notice Accept to be an implementation of proxy and then call a function from the new + * implementation as specified by `_data`, which should be an encoded function call. This is + * useful to initialize new storage variables in the proxied contract. + * @param _proxy Proxy to accept + * @param _data Calldata for the initialization function call (including selector) + */ + function acceptProxyAndCall(IGraphProxy _proxy, bytes calldata _data) + external + onlyProxyAdmin(_proxy) + { + _proxy.acceptUpgradeAndCall(_data); + } +} diff --git a/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/upgrades/IGraphProxy.sol b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/upgrades/IGraphProxy.sol new file mode 100644 index 000000000..61946e948 --- /dev/null +++ b/packages/legacy/verified/L1GraphTokenGateway/sources/contracts/upgrades/IGraphProxy.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +interface IGraphProxy { + function admin() external returns (address); + + function setAdmin(address _newAdmin) external; + + function implementation() external returns (address); + + function pendingImplementation() external returns (address); + + function upgradeTo(address _newImplementation) external; + + function acceptUpgrade() external; + + function acceptUpgradeAndCall(bytes calldata data) external; +} diff --git a/packages/legacy/verified/L1Staking/compiler.json b/packages/legacy/verified/L1Staking/compiler.json new file mode 100644 index 000000000..b13e2eb82 --- /dev/null +++ b/packages/legacy/verified/L1Staking/compiler.json @@ -0,0 +1,62 @@ +{ + "version": "0.7.6+commit.7338295f", + "settings": { + "metadata": { + "bytecodeHash": "ipfs", + "useLiteralContent": true + }, + "libraries": { + "contracts/staking/libs/Exponential.sol": { + "LibExponential": "0xd0aae7a0a6ae551dca969183e1bb376f8a455ea1" + } + }, + "optimizer": { + "runs": 200, + "enabled": true + }, + "evmVersion": "istanbul", + "remappings": [] + }, + "target": { + "source": "contracts/staking/L1Staking.sol", + "name": "L1Staking" + }, + "sourcifyMatch": "match", + "sourceHashes": { + "contracts/base/Multicall.sol": "5b00bd1e7c7cfb6f6f1f0c837e6976af5960deb2b858eecae246773647dd084c", + "contracts/discovery/IGNS.sol": "0a4c68898721a068313a7fa32b99ebe9da1a432067dcb968a4d7a11e5d72e619", + "contracts/base/IMulticall.sol": "bbc79b9531ebaf4245bab4b7fed363d2e479cc9ce24d579e56a4d8ab44094dea", + "contracts/staking/Staking.sol": "3ce7cf8ff5c557a151967e1111b1f1bd2bd11e2c42ce7250f378f81db4c5f733", + "contracts/staking/IStaking.sol": "b6e6066ed39cb7fa2c2ce5d525accd36f35a503fa0e61a446147b9f210198b7e", + "contracts/utils/TokenUtils.sol": "f1069ba54bdf449002414f051d99538ca02d8253a1670ec1398727002ffff45b", + "contracts/staking/L1Staking.sol": "5100a027690a1edc859b3e08e237d462a6f4d10a6c242e98a328f7b3d52854dc", + "contracts/token/IGraphToken.sol": "244ca084fd409c9ad3969e7ac5548346495466442ff7f802288b112d687705d2", + "contracts/curation/ICuration.sol": "34624b8df89549050f23bb3c606c9c60440330ab23551f4d4f1f0b94aef6afc9", + "contracts/governance/Managed.sol": "99e5aa75c463e7fd401e6f2805afb45201fbdbfc26c21c0cb0bb38da59a12cf5", + "contracts/governance/IManaged.sol": "2f4a25095e90d6b08f1789dde49cb87d46888090ccc4e6ab1459e316c746cd29", + "contracts/staking/libs/Stakes.sol": "d2954b67e9bf2681a3d958ab2d3eda8d8c7942e4fade92cd4670e33458f6d676", + "contracts/epochs/IEpochManager.sol": "cb45047682865d6ed215af7d05d505b8fa7a1e66b1e54b45c1a47798281f47ab", + "contracts/staking/IStakingBase.sol": "9acf13c60dca513be4999d12d79c5ed20cce1a5dd3ae985c2c22448ea7e9557f", + "contracts/staking/IStakingData.sol": "27faec50cd81bfeba67ac3a3fb35eaa0cd8d1912ec05e9647fae8746f46e1301", + "contracts/upgrades/IGraphProxy.sol": "d7b89c0be1a05c93facc640a08d0b4f1e0b4557cb268d9143e4c42ce24bbd1a9", + "contracts/l2/staking/IL2Staking.sol": "553d9b2fd2a6f5f029331cba59759ff228e5f063d9744c524b66d62d0d909af9", + "contracts/arbitrum/ITokenGateway.sol": "55bd317bf1ca12a2308cc51ad4ec6c370dcb6d1cdd1631ec2308e57528ad7600", + "contracts/governance/IController.sol": "beb591bb70a3b94f025a5f64bbb3fc6788043fb8d5f1cdffb21acb8cbbce8f8d", + "contracts/staking/IL1StakingBase.sol": "0f59b415f5d05b12aee02dab38a4a6cf5756c00146479ae5b078fba11be17189", + "contracts/staking/StakingStorage.sol": "82b7a295372eed804f79965db4c5b8701c2aa8841cd215a037964da95c78793c", + "contracts/staking/libs/MathUtils.sol": "721f4b3dbde3170e895a45c09905972db388321a2d7f61408ad4e2fd8d5a34a8", + "contracts/rewards/IRewardsManager.sol": "5cf8c3593f475719620e6a01b478fa3b5644d3848f82e7768bb36d43069478dd", + "contracts/staking/L1StakingStorage.sol": "4d5ccf8344098269a50d4998d2810ecf8fd2093081db5ce936354c425a551968", + "contracts/staking/StakingExtension.sol": "ce00170ae467f6d2471b755eb62750d0a374010594d77f9361d9f0790a2ec93f", + "contracts/staking/libs/Exponential.sol": "a92b219af4020910a14c70a44695ab82d943afcc330c06f7e158a8f3ee423c9b", + "contracts/gateway/ICallhookReceiver.sol": "36f674617a548524a637a2009674ac878e630a867bccfb0866403138f0a8554d", + "contracts/l2/staking/IL2StakingBase.sol": "c60faa05ddc9a4b0a20528a2e171276c9302fc02f35ab84a00da9592db0bd851", + "contracts/staking/IStakingExtension.sol": "cd244dd27c74c51948020f31724e896c13f3750211761c3c5549c61cce45f30f", + "contracts/staking/libs/LibFixedMath.sol": "1193c1194a42e4f3b9d8983907d4ccb51809f7b3a0514c8d49bb107d77a0065e", + "contracts/upgrades/GraphUpgradeable.sol": "fdb340dbeb5de52ea8243fbfab9d2ef8fdfb916e909dc4e1ae6b314cde0ea083", + "@openzeppelin/contracts/math/SafeMath.sol": "4a04d0a20a19e3ef1dcabae9cad9ba006430a4e7eec4d9b519db87999722c98a", + "@openzeppelin/contracts/cryptography/ECDSA.sol": "0c3c8f0e880bfb11298ab86b2863e7a6ce8c85e443fbf4998b8ed3b32739c853", + "@openzeppelin/contracts/token/ERC20/IERC20.sol": "0573c2961569aa4906845d0cd428b5b7394956170054ceeaa8f8af96cd44875c", + "contracts/staking/IL1GraphTokenLockTransferTool.sol": "e836c1da44725ec0c639c2f2c4b5fb6de869bb819c69d1f87369387f0206f03e" + } +} diff --git a/packages/legacy/verified/L1Staking/sources/@openzeppelin/contracts/cryptography/ECDSA.sol b/packages/legacy/verified/L1Staking/sources/@openzeppelin/contracts/cryptography/ECDSA.sol new file mode 100644 index 000000000..74326eab8 --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/@openzeppelin/contracts/cryptography/ECDSA.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. + * + * These functions can be used to verify that a message was signed by the holder + * of the private keys of a given address. + */ +library ECDSA { + /** + * @dev Returns the address that signed a hashed message (`hash`) with + * `signature`. This address can then be used for verification purposes. + * + * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: + * this function rejects them by requiring the `s` value to be in the lower + * half order, and the `v` value to be either 27 or 28. + * + * IMPORTANT: `hash` _must_ be the result of a hash operation for the + * verification to be secure: it is possible to craft signatures that + * recover to arbitrary addresses for non-hashed data. A safe way to ensure + * this is by receiving a hash of the original message (which may otherwise + * be too long), and then calling {toEthSignedMessageHash} on it. + */ + function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { + // Check the signature length + if (signature.length != 65) { + revert("ECDSA: invalid signature length"); + } + + // Divide the signature in r, s and v variables + bytes32 r; + bytes32 s; + uint8 v; + + // ecrecover takes the signature parameters, and the only way to get them + // currently is to use assembly. + // solhint-disable-next-line no-inline-assembly + assembly { + r := mload(add(signature, 0x20)) + s := mload(add(signature, 0x40)) + v := byte(0, mload(add(signature, 0x60))) + } + + return recover(hash, v, r, s); + } + + /** + * @dev Overload of {ECDSA-recover-bytes32-bytes-} that receives the `v`, + * `r` and `s` signature fields separately. + */ + function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) { + // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature + // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines + // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most + // signatures from current libraries generate a unique signature with an s-value in the lower half order. + // + // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value + // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or + // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept + // these malleable signatures as well. + require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, "ECDSA: invalid signature 's' value"); + require(v == 27 || v == 28, "ECDSA: invalid signature 'v' value"); + + // If the signature is valid (and not malleable), return the signer address + address signer = ecrecover(hash, v, r, s); + require(signer != address(0), "ECDSA: invalid signature"); + + return signer; + } + + /** + * @dev Returns an Ethereum Signed Message, created from a `hash`. This + * replicates the behavior of the + * https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`eth_sign`] + * JSON-RPC method. + * + * See {recover}. + */ + function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) { + // 32 is the length in bytes of hash, + // enforced by the type signature above + return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); + } +} diff --git a/packages/legacy/verified/L1Staking/sources/@openzeppelin/contracts/math/SafeMath.sol b/packages/legacy/verified/L1Staking/sources/@openzeppelin/contracts/math/SafeMath.sol new file mode 100644 index 000000000..3e81f295b --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/@openzeppelin/contracts/math/SafeMath.sol @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + + /** + * @dev Returns the substraction of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b > a) return (false, 0); + return (true, a - b); + } + + /** + * @dev Returns the multiplication of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + + /** + * @dev Returns the division of two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b == 0) return (false, 0); + return (true, a / b); + } + + /** + * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b == 0) return (false, 0); + return (true, a % b); + } + + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + require(b <= a, "SafeMath: subtraction overflow"); + return a - b; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) return 0; + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + require(b > 0, "SafeMath: division by zero"); + return a / b; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + require(b > 0, "SafeMath: modulo by zero"); + return a % b; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {trySub}. + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + return a - b; + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting with custom message on + * division by zero. The result is rounded towards zero. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryDiv}. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + return a / b; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting with custom message when dividing by zero. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryMod}. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + return a % b; + } +} diff --git a/packages/legacy/verified/L1Staking/sources/@openzeppelin/contracts/token/ERC20/IERC20.sol b/packages/legacy/verified/L1Staking/sources/@openzeppelin/contracts/token/ERC20/IERC20.sol new file mode 100644 index 000000000..9b877d119 --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/@openzeppelin/contracts/token/ERC20/IERC20.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/arbitrum/ITokenGateway.sol b/packages/legacy/verified/L1Staking/sources/contracts/arbitrum/ITokenGateway.sol new file mode 100644 index 000000000..977fe07f2 --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/arbitrum/ITokenGateway.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2020, Offchain Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Originally copied from: + * https://github.com/OffchainLabs/arbitrum/tree/e3a6307ad8a2dc2cad35728a2a9908cfd8dd8ef9/packages/arb-bridge-peripherals + * + * MODIFIED from Offchain Labs' implementation: + * - Changed solidity version to 0.7.6 (pablo@edgeandnode.com) + * + */ + +pragma solidity ^0.7.6; + +interface ITokenGateway { + /// @notice event deprecated in favor of DepositInitiated and WithdrawalInitiated + // event OutboundTransferInitiated( + // address token, + // address indexed _from, + // address indexed _to, + // uint256 indexed _transferId, + // uint256 _amount, + // bytes _data + // ); + + /// @notice event deprecated in favor of DepositFinalized and WithdrawalFinalized + // event InboundTransferFinalized( + // address token, + // address indexed _from, + // address indexed _to, + // uint256 indexed _transferId, + // uint256 _amount, + // bytes _data + // ); + + function outboundTransfer( + address _token, + address _to, + uint256 _amount, + uint256 _maxGas, + uint256 _gasPriceBid, + bytes calldata _data + ) external payable returns (bytes memory); + + function finalizeInboundTransfer( + address _token, + address _from, + address _to, + uint256 _amount, + bytes calldata _data + ) external payable; + + /** + * @notice Calculate the address used when bridging an ERC20 token + * @dev the L1 and L2 address oracles may not always be in sync. + * For example, a custom token may have been registered but not deployed or the contract self destructed. + * @param l1ERC20 address of L1 token + * @return L2 address of a bridged ERC20 token + */ + function calculateL2TokenAddress(address l1ERC20) external view returns (address); +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/base/IMulticall.sol b/packages/legacy/verified/L1Staking/sources/contracts/base/IMulticall.sol new file mode 100644 index 000000000..3269f694b --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/base/IMulticall.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; +pragma abicoder v2; + +/** + * @title Multicall interface + * @notice Enables calling multiple methods in a single call to the contract + */ +interface IMulticall { + /** + * @notice Call multiple functions in the current contract and return the data from all of them if they all succeed + * @param data The encoded function data for each of the calls to make to this contract + * @return results The results from each of the calls passed in via data + */ + function multicall(bytes[] calldata data) external returns (bytes[] memory results); +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/base/Multicall.sol b/packages/legacy/verified/L1Staking/sources/contracts/base/Multicall.sol new file mode 100644 index 000000000..49111840d --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/base/Multicall.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; +pragma abicoder v2; + +import "./IMulticall.sol"; + +// Inspired by https://github.com/Uniswap/uniswap-v3-periphery/blob/main/contracts/base/Multicall.sol +// Note: Removed payable from the multicall + +/** + * @title Multicall + * @notice Enables calling multiple methods in a single call to the contract + */ +abstract contract Multicall is IMulticall { + /// @inheritdoc IMulticall + function multicall(bytes[] calldata data) external override returns (bytes[] memory results) { + results = new bytes[](data.length); + for (uint256 i = 0; i < data.length; i++) { + (bool success, bytes memory result) = address(this).delegatecall(data[i]); + + if (!success) { + // Next 5 lines from https://ethereum.stackexchange.com/a/83577 + if (result.length < 68) revert(); + assembly { + result := add(result, 0x04) + } + revert(abi.decode(result, (string))); + } + + results[i] = result; + } + } +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/curation/ICuration.sol b/packages/legacy/verified/L1Staking/sources/contracts/curation/ICuration.sol new file mode 100644 index 000000000..dffff46cd --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/curation/ICuration.sol @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +/** + * @title Curation Interface + * @dev Interface for the Curation contract (and L2Curation too) + */ +interface ICuration { + // -- Configuration -- + + /** + * @notice Update the default reserve ratio to `_defaultReserveRatio` + * @param _defaultReserveRatio Reserve ratio (in PPM) + */ + function setDefaultReserveRatio(uint32 _defaultReserveRatio) external; + + /** + * @notice Update the minimum deposit amount needed to intialize a new subgraph + * @param _minimumCurationDeposit Minimum amount of tokens required deposit + */ + function setMinimumCurationDeposit(uint256 _minimumCurationDeposit) external; + + /** + * @notice Set the curation tax percentage to charge when a curator deposits GRT tokens. + * @param _percentage Curation tax percentage charged when depositing GRT tokens + */ + function setCurationTaxPercentage(uint32 _percentage) external; + + /** + * @notice Set the master copy to use as clones for the curation token. + * @param _curationTokenMaster Address of implementation contract to use for curation tokens + */ + function setCurationTokenMaster(address _curationTokenMaster) external; + + // -- Curation -- + + /** + * @notice Deposit Graph Tokens in exchange for signal of a SubgraphDeployment curation pool. + * @param _subgraphDeploymentID Subgraph deployment pool from where to mint signal + * @param _tokensIn Amount of Graph Tokens to deposit + * @param _signalOutMin Expected minimum amount of signal to receive + * @return Amount of signal minted + * @return Amount of curation tax burned + */ + function mint( + bytes32 _subgraphDeploymentID, + uint256 _tokensIn, + uint256 _signalOutMin + ) external returns (uint256, uint256); + + /** + * @notice Burn _signal from the SubgraphDeployment curation pool + * @param _subgraphDeploymentID SubgraphDeployment the curator is returning signal + * @param _signalIn Amount of signal to return + * @param _tokensOutMin Expected minimum amount of tokens to receive + * @return Tokens returned + */ + function burn( + bytes32 _subgraphDeploymentID, + uint256 _signalIn, + uint256 _tokensOutMin + ) external returns (uint256); + + /** + * @notice Assign Graph Tokens collected as curation fees to the curation pool reserve. + * @param _subgraphDeploymentID SubgraphDeployment where funds should be allocated as reserves + * @param _tokens Amount of Graph Tokens to add to reserves + */ + function collect(bytes32 _subgraphDeploymentID, uint256 _tokens) external; + + // -- Getters -- + + /** + * @notice Check if any GRT tokens are deposited for a SubgraphDeployment. + * @param _subgraphDeploymentID SubgraphDeployment to check if curated + * @return True if curated, false otherwise + */ + function isCurated(bytes32 _subgraphDeploymentID) external view returns (bool); + + /** + * @notice Get the amount of signal a curator has in a curation pool. + * @param _curator Curator owning the signal tokens + * @param _subgraphDeploymentID Subgraph deployment curation pool + * @return Amount of signal owned by a curator for the subgraph deployment + */ + function getCuratorSignal(address _curator, bytes32 _subgraphDeploymentID) + external + view + returns (uint256); + + /** + * @notice Get the amount of signal in a curation pool. + * @param _subgraphDeploymentID Subgraph deployment curation poool + * @return Amount of signal minted for the subgraph deployment + */ + function getCurationPoolSignal(bytes32 _subgraphDeploymentID) external view returns (uint256); + + /** + * @notice Get the amount of token reserves in a curation pool. + * @param _subgraphDeploymentID Subgraph deployment curation poool + * @return Amount of token reserves in the curation pool + */ + function getCurationPoolTokens(bytes32 _subgraphDeploymentID) external view returns (uint256); + + /** + * @notice Calculate amount of signal that can be bought with tokens in a curation pool. + * This function considers and excludes the deposit tax. + * @param _subgraphDeploymentID Subgraph deployment to mint signal + * @param _tokensIn Amount of tokens used to mint signal + * @return Amount of signal that can be bought + * @return Amount of tokens that will be burned as curation tax + */ + function tokensToSignal(bytes32 _subgraphDeploymentID, uint256 _tokensIn) + external + view + returns (uint256, uint256); + + /** + * @notice Calculate number of tokens to get when burning signal from a curation pool. + * @param _subgraphDeploymentID Subgraph deployment to burn signal + * @param _signalIn Amount of signal to burn + * @return Amount of tokens to get for the specified amount of signal + */ + function signalToTokens(bytes32 _subgraphDeploymentID, uint256 _signalIn) + external + view + returns (uint256); + + /** + * @notice Tax charged when curators deposit funds. + * Parts per million. (Allows for 4 decimal points, 999,999 = 99.9999%) + * @return Curation tax percentage expressed in PPM + */ + function curationTaxPercentage() external view returns (uint32); +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/discovery/IGNS.sol b/packages/legacy/verified/L1Staking/sources/contracts/discovery/IGNS.sol new file mode 100644 index 000000000..80cc99820 --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/discovery/IGNS.sol @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +/** + * @title Interface for GNS + */ +interface IGNS { + // -- Pool -- + + /** + * @dev The SubgraphData struct holds information about subgraphs + * and their signal; both nSignal (i.e. name signal at the GNS level) + * and vSignal (i.e. version signal at the Curation contract level) + */ + struct SubgraphData { + uint256 vSignal; // The token of the subgraph-deployment bonding curve + uint256 nSignal; // The token of the subgraph bonding curve + mapping(address => uint256) curatorNSignal; + bytes32 subgraphDeploymentID; + uint32 reserveRatioDeprecated; // Ratio for the bonding curve, always 1 in PPM, deprecated. + bool disabled; + uint256 withdrawableGRT; + } + + /** + * @dev The LegacySubgraphKey struct holds the account and sequence ID + * used to generate subgraph IDs in legacy subgraphs. + */ + struct LegacySubgraphKey { + address account; + uint256 accountSeqID; + } + + // -- Configuration -- + + /** + * @notice Approve curation contract to pull funds. + */ + function approveAll() external; + + /** + * @notice Set the owner fee percentage. This is used to prevent a subgraph owner to drain all + * the name curators tokens while upgrading or deprecating and is configurable in parts per million. + * @param _ownerTaxPercentage Owner tax percentage + */ + function setOwnerTaxPercentage(uint32 _ownerTaxPercentage) external; + + // -- Publishing -- + + /** + * @notice Allows a graph account to set a default name + * @param _graphAccount Account that is setting its name + * @param _nameSystem Name system account already has ownership of a name in + * @param _nameIdentifier The unique identifier that is used to identify the name in the system + * @param _name The name being set as default + */ + function setDefaultName( + address _graphAccount, + uint8 _nameSystem, + bytes32 _nameIdentifier, + string calldata _name + ) external; + + /** + * @notice Allows a subgraph owner to update the metadata of a subgraph they have published + * @param _subgraphID Subgraph ID + * @param _subgraphMetadata IPFS hash for the subgraph metadata + */ + function updateSubgraphMetadata(uint256 _subgraphID, bytes32 _subgraphMetadata) external; + + /** + * @notice Publish a new subgraph. + * @param _subgraphDeploymentID Subgraph deployment for the subgraph + * @param _versionMetadata IPFS hash for the subgraph version metadata + * @param _subgraphMetadata IPFS hash for the subgraph metadata + */ + function publishNewSubgraph( + bytes32 _subgraphDeploymentID, + bytes32 _versionMetadata, + bytes32 _subgraphMetadata + ) external; + + /** + * @notice Publish a new version of an existing subgraph. + * @param _subgraphID Subgraph ID + * @param _subgraphDeploymentID Subgraph deployment ID of the new version + * @param _versionMetadata IPFS hash for the subgraph version metadata + */ + function publishNewVersion( + uint256 _subgraphID, + bytes32 _subgraphDeploymentID, + bytes32 _versionMetadata + ) external; + + /** + * @notice Deprecate a subgraph. The bonding curve is destroyed, the vSignal is burned, and the GNS + * contract holds the GRT from burning the vSignal, which all curators can withdraw manually. + * Can only be done by the subgraph owner. + * @param _subgraphID Subgraph ID + */ + function deprecateSubgraph(uint256 _subgraphID) external; + + // -- Curation -- + + /** + * @notice Deposit GRT into a subgraph and mint signal. + * @param _subgraphID Subgraph ID + * @param _tokensIn The amount of tokens the nameCurator wants to deposit + * @param _nSignalOutMin Expected minimum amount of name signal to receive + */ + function mintSignal( + uint256 _subgraphID, + uint256 _tokensIn, + uint256 _nSignalOutMin + ) external; + + /** + * @notice Burn signal for a subgraph and return the GRT. + * @param _subgraphID Subgraph ID + * @param _nSignal The amount of nSignal the nameCurator wants to burn + * @param _tokensOutMin Expected minimum amount of tokens to receive + */ + function burnSignal( + uint256 _subgraphID, + uint256 _nSignal, + uint256 _tokensOutMin + ) external; + + /** + * @notice Move subgraph signal from sender to `_recipient` + * @param _subgraphID Subgraph ID + * @param _recipient Address to send the signal to + * @param _amount The amount of nSignal to transfer + */ + function transferSignal( + uint256 _subgraphID, + address _recipient, + uint256 _amount + ) external; + + /** + * @notice Withdraw tokens from a deprecated subgraph. + * When the subgraph is deprecated, any curator can call this function and + * withdraw the GRT they are entitled for its original deposit + * @param _subgraphID Subgraph ID + */ + function withdraw(uint256 _subgraphID) external; + + // -- Getters -- + + /** + * @notice Return the owner of a subgraph. + * @param _tokenID Subgraph ID + * @return Owner address + */ + function ownerOf(uint256 _tokenID) external view returns (address); + + /** + * @notice Return the total signal on the subgraph. + * @param _subgraphID Subgraph ID + * @return Total signal on the subgraph + */ + function subgraphSignal(uint256 _subgraphID) external view returns (uint256); + + /** + * @notice Return the total tokens on the subgraph at current value. + * @param _subgraphID Subgraph ID + * @return Total tokens on the subgraph + */ + function subgraphTokens(uint256 _subgraphID) external view returns (uint256); + + /** + * @notice Calculate subgraph signal to be returned for an amount of tokens. + * @param _subgraphID Subgraph ID + * @param _tokensIn Tokens being exchanged for subgraph signal + * @return Amount of subgraph signal and curation tax + */ + function tokensToNSignal(uint256 _subgraphID, uint256 _tokensIn) + external + view + returns ( + uint256, + uint256, + uint256 + ); + + /** + * @notice Calculate tokens returned for an amount of subgraph signal. + * @param _subgraphID Subgraph ID + * @param _nSignalIn Subgraph signal being exchanged for tokens + * @return Amount of tokens returned for an amount of subgraph signal + */ + function nSignalToTokens(uint256 _subgraphID, uint256 _nSignalIn) + external + view + returns (uint256, uint256); + + /** + * @notice Calculate subgraph signal to be returned for an amount of subgraph deployment signal. + * @param _subgraphID Subgraph ID + * @param _vSignalIn Amount of subgraph deployment signal to exchange for subgraph signal + * @return Amount of subgraph signal that can be bought + */ + function vSignalToNSignal(uint256 _subgraphID, uint256 _vSignalIn) + external + view + returns (uint256); + + /** + * @notice Calculate subgraph deployment signal to be returned for an amount of subgraph signal. + * @param _subgraphID Subgraph ID + * @param _nSignalIn Subgraph signal being exchanged for subgraph deployment signal + * @return Amount of subgraph deployment signal that can be returned + */ + function nSignalToVSignal(uint256 _subgraphID, uint256 _nSignalIn) + external + view + returns (uint256); + + /** + * @notice Get the amount of subgraph signal a curator has. + * @param _subgraphID Subgraph ID + * @param _curator Curator address + * @return Amount of subgraph signal owned by a curator + */ + function getCuratorSignal(uint256 _subgraphID, address _curator) + external + view + returns (uint256); + + /** + * @notice Return whether a subgraph is published. + * @param _subgraphID Subgraph ID + * @return Return true if subgraph is currently published + */ + function isPublished(uint256 _subgraphID) external view returns (bool); + + /** + * @notice Return whether a subgraph is a legacy subgraph (created before subgraph NFTs). + * @param _subgraphID Subgraph ID + * @return Return true if subgraph is a legacy subgraph + */ + function isLegacySubgraph(uint256 _subgraphID) external view returns (bool); + + /** + * @notice Returns account and sequence ID for a legacy subgraph (created before subgraph NFTs). + * @param _subgraphID Subgraph ID + * @return account Account that created the subgraph (or 0 if it's not a legacy subgraph) + * @return seqID Sequence number for the subgraph + */ + function getLegacySubgraphKey(uint256 _subgraphID) + external + view + returns (address account, uint256 seqID); +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/epochs/IEpochManager.sol b/packages/legacy/verified/L1Staking/sources/contracts/epochs/IEpochManager.sol new file mode 100644 index 000000000..36b1f47a3 --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/epochs/IEpochManager.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +interface IEpochManager { + // -- Configuration -- + + function setEpochLength(uint256 _epochLength) external; + + // -- Epochs + + function runEpoch() external; + + // -- Getters -- + + function isCurrentEpochRun() external view returns (bool); + + function blockNum() external view returns (uint256); + + function blockHash(uint256 _block) external view returns (bytes32); + + function currentEpoch() external view returns (uint256); + + function currentEpochBlock() external view returns (uint256); + + function currentEpochBlockSinceStart() external view returns (uint256); + + function epochsSince(uint256 _epoch) external view returns (uint256); + + function epochsSinceUpdate() external view returns (uint256); +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/gateway/ICallhookReceiver.sol b/packages/legacy/verified/L1Staking/sources/contracts/gateway/ICallhookReceiver.sol new file mode 100644 index 000000000..ff0fbfab1 --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/gateway/ICallhookReceiver.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/** + * @title Interface for contracts that can receive callhooks through the Arbitrum GRT bridge + * @dev Any contract that can receive a callhook on L2, sent through the bridge from L1, must + * be allowlisted by the governor, but also implement this interface that contains + * the function that will actually be called by the L2GraphTokenGateway. + */ +pragma solidity ^0.7.6; + +interface ICallhookReceiver { + /** + * @notice Receive tokens with a callhook from the bridge + * @param _from Token sender in L1 + * @param _amount Amount of tokens that were transferred + * @param _data ABI-encoded callhook data + */ + function onTokenTransfer( + address _from, + uint256 _amount, + bytes calldata _data + ) external; +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/governance/IController.sol b/packages/legacy/verified/L1Staking/sources/contracts/governance/IController.sol new file mode 100644 index 000000000..7df3d94ee --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/governance/IController.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; + +interface IController { + function getGovernor() external view returns (address); + + // -- Registry -- + + function setContractProxy(bytes32 _id, address _contractAddress) external; + + function unsetContractProxy(bytes32 _id) external; + + function updateController(bytes32 _id, address _controller) external; + + function getContractProxy(bytes32 _id) external view returns (address); + + // -- Pausing -- + + function setPartialPaused(bool _partialPaused) external; + + function setPaused(bool _paused) external; + + function setPauseGuardian(address _newPauseGuardian) external; + + function paused() external view returns (bool); + + function partialPaused() external view returns (bool); +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/governance/IManaged.sol b/packages/legacy/verified/L1Staking/sources/contracts/governance/IManaged.sol new file mode 100644 index 000000000..76f05e0fb --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/governance/IManaged.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import { IController } from "./IController.sol"; + +/** + * @title Managed Interface + * @dev Interface for contracts that can be managed by a controller. + */ +interface IManaged { + /** + * @notice Set the controller that manages this contract + * @dev Only the current controller can set a new controller + * @param _controller Address of the new controller + */ + function setController(address _controller) external; + + /** + * @notice Sync protocol contract addresses from the Controller registry + * @dev This function will cache all the contracts using the latest addresses. + * Anyone can call the function whenever a Proxy contract change in the + * controller to ensure the protocol is using the latest version. + */ + function syncAllContracts() external; + + /** + * @notice Get the Controller that manages this contract + * @return The Controller as an IController interface + */ + function controller() external view returns (IController); +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/governance/Managed.sol b/packages/legacy/verified/L1Staking/sources/contracts/governance/Managed.sol new file mode 100644 index 000000000..fb65e71b9 --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/governance/Managed.sol @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import { IController } from "./IController.sol"; + +import { ICuration } from "../curation/ICuration.sol"; +import { IEpochManager } from "../epochs/IEpochManager.sol"; +import { IRewardsManager } from "../rewards/IRewardsManager.sol"; +import { IStaking } from "../staking/IStaking.sol"; +import { IStakingBase } from "../staking/IStakingBase.sol"; +import { IGraphToken } from "../token/IGraphToken.sol"; +import { ITokenGateway } from "../arbitrum/ITokenGateway.sol"; +import { IGNS } from "../discovery/IGNS.sol"; + +import { IManaged } from "./IManaged.sol"; + +/** + * @title Graph Managed contract + * @dev The Managed contract provides an interface to interact with the Controller. + * It also provides local caching for contract addresses. This mechanism relies on calling the + * public `syncAllContracts()` function whenever a contract changes in the controller. + * + * Inspired by Livepeer: + * https://github.com/livepeer/protocol/blob/streamflow/contracts/Controller.sol + */ +abstract contract Managed is IManaged { + // -- State -- + + /// Controller that manages this contract + IController public override controller; + /// @dev Cache for the addresses of the contracts retrieved from the controller + mapping(bytes32 => address) private _addressCache; + /// @dev Gap for future storage variables + uint256[10] private __gap; + + // Immutables + bytes32 private immutable CURATION = keccak256("Curation"); + bytes32 private immutable EPOCH_MANAGER = keccak256("EpochManager"); + bytes32 private immutable REWARDS_MANAGER = keccak256("RewardsManager"); + bytes32 private immutable STAKING = keccak256("Staking"); + bytes32 private immutable GRAPH_TOKEN = keccak256("GraphToken"); + bytes32 private immutable GRAPH_TOKEN_GATEWAY = keccak256("GraphTokenGateway"); + bytes32 private immutable GNS = keccak256("GNS"); + + // -- Events -- + + /// Emitted when a contract parameter has been updated + event ParameterUpdated(string param); + /// Emitted when the controller address has been set + event SetController(address controller); + + /// Emitted when contract with `nameHash` is synced to `contractAddress`. + event ContractSynced(bytes32 indexed nameHash, address contractAddress); + + // -- Modifiers -- + + /** + * @dev Revert if the controller is paused or partially paused + */ + function _notPartialPaused() internal view { + require(!controller.paused(), "Paused"); + require(!controller.partialPaused(), "Partial-paused"); + } + + /** + * @dev Revert if the controller is paused + */ + function _notPaused() internal view virtual { + require(!controller.paused(), "Paused"); + } + + /** + * @dev Revert if the caller is not the governor + */ + function _onlyGovernor() internal view { + require(msg.sender == controller.getGovernor(), "Only Controller governor"); + } + + /** + * @dev Revert if the caller is not the Controller + */ + function _onlyController() internal view { + require(msg.sender == address(controller), "Caller must be Controller"); + } + + /** + * @dev Revert if the controller is paused or partially paused + */ + modifier notPartialPaused() { + _notPartialPaused(); + _; + } + + /** + * @dev Revert if the controller is paused + */ + modifier notPaused() { + _notPaused(); + _; + } + + /** + * @dev Revert if the caller is not the Controller + */ + modifier onlyController() { + _onlyController(); + _; + } + + /** + * @dev Revert if the caller is not the governor + */ + modifier onlyGovernor() { + _onlyGovernor(); + _; + } + + // -- Functions -- + + /** + * @dev Initialize a Managed contract + * @param _controller Address for the Controller that manages this contract + */ + function _initialize(address _controller) internal { + _setController(_controller); + } + + /** + * @notice Set Controller. Only callable by current controller. + * @param _controller Controller contract address + */ + function setController(address _controller) external override onlyController { + _setController(_controller); + } + + /** + * @dev Set controller. + * @param _controller Controller contract address + */ + function _setController(address _controller) internal { + require(_controller != address(0), "Controller must be set"); + controller = IController(_controller); + emit SetController(_controller); + } + + /** + * @dev Return Curation interface + * @return Curation contract registered with Controller + */ + function curation() internal view returns (ICuration) { + return ICuration(_resolveContract(CURATION)); + } + + /** + * @dev Return EpochManager interface + * @return Epoch manager contract registered with Controller + */ + function epochManager() internal view returns (IEpochManager) { + return IEpochManager(_resolveContract(EPOCH_MANAGER)); + } + + /** + * @dev Return RewardsManager interface + * @return Rewards manager contract registered with Controller + */ + function rewardsManager() internal view returns (IRewardsManager) { + return IRewardsManager(_resolveContract(REWARDS_MANAGER)); + } + + /** + * @dev Return Staking interface + * @return Staking contract registered with Controller + */ + function staking() internal view returns (IStaking) { + return IStaking(_resolveContract(STAKING)); + } + + /** + * @dev Return GraphToken interface + * @return Graph token contract registered with Controller + */ + function graphToken() internal view returns (IGraphToken) { + return IGraphToken(_resolveContract(GRAPH_TOKEN)); + } + + /** + * @dev Return GraphTokenGateway (L1 or L2) interface + * @return Graph token gateway contract registered with Controller + */ + function graphTokenGateway() internal view returns (ITokenGateway) { + return ITokenGateway(_resolveContract(GRAPH_TOKEN_GATEWAY)); + } + + /** + * @dev Return GNS (L1 or L2) interface. + * @return Address of the GNS contract registered with Controller, as an IGNS interface. + */ + function gns() internal view returns (IGNS) { + return IGNS(_resolveContract(GNS)); + } + + /** + * @dev Resolve a contract address from the cache or the Controller if not found. + * @param _nameHash keccak256 hash of the contract name + * @return Address of the contract + */ + function _resolveContract(bytes32 _nameHash) internal view returns (address) { + address contractAddress = _addressCache[_nameHash]; + if (contractAddress == address(0)) { + contractAddress = controller.getContractProxy(_nameHash); + } + return contractAddress; + } + + /** + * @dev Cache a contract address from the Controller registry. + * @param _nameHash keccak256 hash of the name of the contract to sync into the cache + */ + function _syncContract(bytes32 _nameHash) internal { + address contractAddress = controller.getContractProxy(_nameHash); + if (_addressCache[_nameHash] != contractAddress) { + _addressCache[_nameHash] = contractAddress; + emit ContractSynced(_nameHash, contractAddress); + } + } + + /** + * @notice Sync protocol contract addresses from the Controller registry + * @dev This function will cache all the contracts using the latest addresses + * Anyone can call the function whenever a Proxy contract change in the + * controller to ensure the protocol is using the latest version + */ + function syncAllContracts() external override { + _syncContract(CURATION); + _syncContract(EPOCH_MANAGER); + _syncContract(REWARDS_MANAGER); + _syncContract(STAKING); + _syncContract(GRAPH_TOKEN); + _syncContract(GRAPH_TOKEN_GATEWAY); + _syncContract(GNS); + } +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/l2/staking/IL2Staking.sol b/packages/legacy/verified/L1Staking/sources/contracts/l2/staking/IL2Staking.sol new file mode 100644 index 000000000..73ff936ce --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/l2/staking/IL2Staking.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; +pragma abicoder v2; + +import { IStaking } from "../../staking/IStaking.sol"; +import { IL2StakingBase } from "./IL2StakingBase.sol"; + +/** + * @title Interface for the L2 Staking contract + * @notice This is the interface that should be used when interacting with the L2 Staking contract. + * It extends the IStaking interface with the functions that are specific to L2, adding the callhook receiver + * to receive transferred stake and delegation from L1. + * @dev Note that L2Staking doesn't actually inherit this interface. This is because of + * the custom setup of the Staking contract where part of the functionality is implemented + * in a separate contract (StakingExtension) to which calls are delegated through the fallback function. + */ +interface IL2Staking is IStaking, IL2StakingBase { + /// @dev Message codes for the L1 -> L2 bridge callhook + enum L1MessageCodes { + RECEIVE_INDEXER_STAKE_CODE, + RECEIVE_DELEGATION_CODE + } + + /// @dev Encoded message struct when receiving indexer stake through the bridge + struct ReceiveIndexerStakeData { + address indexer; + } + + /// @dev Encoded message struct when receiving delegation through the bridge + struct ReceiveDelegationData { + address indexer; + address delegator; + } +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/l2/staking/IL2StakingBase.sol b/packages/legacy/verified/L1Staking/sources/contracts/l2/staking/IL2StakingBase.sol new file mode 100644 index 000000000..8b8cd92ab --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/l2/staking/IL2StakingBase.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import { ICallhookReceiver } from "../../gateway/ICallhookReceiver.sol"; + +/** + * @title Base interface for the L2Staking contract. + * @notice This interface is used to define the callhook receiver interface that is implemented by L2Staking. + * @dev Note it includes only the L2-specific functionality, not the full IStaking interface. + */ +interface IL2StakingBase is ICallhookReceiver { + event TransferredDelegationReturnedToDelegator( + address indexed indexer, + address indexed delegator, + uint256 amount + ); +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/rewards/IRewardsManager.sol b/packages/legacy/verified/L1Staking/sources/contracts/rewards/IRewardsManager.sol new file mode 100644 index 000000000..87d3c2455 --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/rewards/IRewardsManager.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +interface IRewardsManager { + /** + * @dev Stores accumulated rewards and snapshots related to a particular SubgraphDeployment. + */ + struct Subgraph { + uint256 accRewardsForSubgraph; + uint256 accRewardsForSubgraphSnapshot; + uint256 accRewardsPerSignalSnapshot; + uint256 accRewardsPerAllocatedToken; + } + + // -- Config -- + + function setIssuancePerBlock(uint256 _issuancePerBlock) external; + + function setMinimumSubgraphSignal(uint256 _minimumSubgraphSignal) external; + + // -- Denylist -- + + function setSubgraphAvailabilityOracle(address _subgraphAvailabilityOracle) external; + + function setDenied(bytes32 _subgraphDeploymentID, bool _deny) external; + + function setDeniedMany(bytes32[] calldata _subgraphDeploymentID, bool[] calldata _deny) + external; + + function isDenied(bytes32 _subgraphDeploymentID) external view returns (bool); + + // -- Getters -- + + function getNewRewardsPerSignal() external view returns (uint256); + + function getAccRewardsPerSignal() external view returns (uint256); + + function getAccRewardsForSubgraph(bytes32 _subgraphDeploymentID) + external + view + returns (uint256); + + function getAccRewardsPerAllocatedToken(bytes32 _subgraphDeploymentID) + external + view + returns (uint256, uint256); + + function getRewards(address _allocationID) external view returns (uint256); + + // -- Updates -- + + function updateAccRewardsPerSignal() external returns (uint256); + + function takeRewards(address _allocationID) external returns (uint256); + + // -- Hooks -- + + function onSubgraphSignalUpdate(bytes32 _subgraphDeploymentID) external returns (uint256); + + function onSubgraphAllocationUpdate(bytes32 _subgraphDeploymentID) external returns (uint256); +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/staking/IL1GraphTokenLockTransferTool.sol b/packages/legacy/verified/L1Staking/sources/contracts/staking/IL1GraphTokenLockTransferTool.sol new file mode 100644 index 000000000..b935682b9 --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/staking/IL1GraphTokenLockTransferTool.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; +pragma abicoder v2; + +/** + * @title Interface for the L1GraphTokenLockTransferTool contract + * @dev This interface defines the function to get the L2 wallet address for a given L1 token lock wallet. + * The Transfer Tool contract is implemented in the token-distribution repo: https://github.com/graphprotocol/token-distribution/pull/64 + * and is only included here to provide support in L1Staking for the transfer of stake and delegation + * owned by token lock contracts. See GIP-0046 for details: https://forum.thegraph.com/t/4023 + */ +interface IL1GraphTokenLockTransferTool { + /** + * @notice Pulls ETH from an L1 wallet's account to use for L2 ticket gas. + * @dev This function is only callable by the staking contract. + * @param _l1Wallet Address of the L1 token lock wallet + * @param _amount Amount of ETH to pull from the transfer tool contract + */ + function pullETH(address _l1Wallet, uint256 _amount) external; + + /** + * @notice Get the L2 token lock wallet address for a given L1 token lock wallet + * @dev In the actual L1GraphTokenLockTransferTool contract, this is simply the default getter for a public mapping variable. + * @param _l1Wallet Address of the L1 token lock wallet + * @return Address of the L2 token lock wallet if the wallet has an L2 counterpart, or address zero if + * the wallet doesn't have an L2 counterpart (or is not known to be a token lock wallet). + */ + function l2WalletAddress(address _l1Wallet) external view returns (address); +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/staking/IL1StakingBase.sol b/packages/legacy/verified/L1Staking/sources/contracts/staking/IL1StakingBase.sol new file mode 100644 index 000000000..58749a247 --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/staking/IL1StakingBase.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; +pragma abicoder v2; + +import { IL1GraphTokenLockTransferTool } from "./IL1GraphTokenLockTransferTool.sol"; + +/** + * @title Base interface for the L1Staking contract. + * @notice This interface is used to define the transfer tools that are implemented in L1Staking. + * @dev Note it includes only the L1-specific functionality, not the full IStaking interface. + */ +interface IL1StakingBase { + /// @dev Emitted when an indexer transfers their stake to L2. + /// This can happen several times as indexers can transfer partial stake. + event IndexerStakeTransferredToL2( + address indexed indexer, + address indexed l2Indexer, + uint256 transferredStakeTokens + ); + + /// @dev Emitted when a delegator transfers their delegation to L2 + event DelegationTransferredToL2( + address indexed delegator, + address indexed l2Delegator, + address indexed indexer, + address l2Indexer, + uint256 transferredDelegationTokens + ); + + /// @dev Emitted when the L1GraphTokenLockTransferTool is set + event L1GraphTokenLockTransferToolSet(address l1GraphTokenLockTransferTool); + + /// @dev Emitted when a delegator unlocks their tokens ahead of time because the indexer has transferred to L2 + event StakeDelegatedUnlockedDueToL2Transfer(address indexed indexer, address indexed delegator); + + /** + * @notice Set the L1GraphTokenLockTransferTool contract address + * @dev This function can only be called by the governor. + * @param _l1GraphTokenLockTransferTool Address of the L1GraphTokenLockTransferTool contract + */ + function setL1GraphTokenLockTransferTool( + IL1GraphTokenLockTransferTool _l1GraphTokenLockTransferTool + ) external; + + /** + * @notice Send an indexer's stake to L2. + * @dev This function can only be called by the indexer (not an operator). + * It will validate that the remaining stake is sufficient to cover all the allocated + * stake, so the indexer might have to close some allocations before transferring. + * It will also check that the indexer's stake is not locked for withdrawal. + * Since the indexer address might be an L1-only contract, the function takes a beneficiary + * address that will be the indexer's address in L2. + * The caller must provide an amount of ETH to use for the L2 retryable ticket, that + * must be at least `_maxSubmissionCost + _gasPriceBid * _maxGas`. + * @param _l2Beneficiary Address of the indexer in L2. If the indexer has previously transferred stake, this must match the previously-used value. + * @param _amount Amount of stake GRT to transfer to L2 + * @param _maxGas Max gas to use for the L2 retryable ticket + * @param _gasPriceBid Gas price bid for the L2 retryable ticket + * @param _maxSubmissionCost Max submission cost for the L2 retryable ticket + */ + function transferStakeToL2( + address _l2Beneficiary, + uint256 _amount, + uint256 _maxGas, + uint256 _gasPriceBid, + uint256 _maxSubmissionCost + ) external payable; + + /** + * @notice Send an indexer's stake to L2, from a GraphTokenLockWallet vesting contract. + * @dev This function can only be called by the indexer (not an operator). + * It will validate that the remaining stake is sufficient to cover all the allocated + * stake, so the indexer might have to close some allocations before transferring. + * It will also check that the indexer's stake is not locked for withdrawal. + * The L2 beneficiary for the stake will be determined by calling the L1GraphTokenLockTransferTool contract, + * so the caller must have previously transferred tokens through that first + * (see GIP-0046 for details: https://forum.thegraph.com/t/4023). + * The ETH for the L2 gas will be pulled from the L1GraphTokenLockTransferTool, so the owner of + * the GraphTokenLockWallet must have previously deposited at least `_maxSubmissionCost + _gasPriceBid * _maxGas` + * ETH into the L1GraphTokenLockTransferTool contract (using its depositETH function). + * @param _amount Amount of stake GRT to transfer to L2 + * @param _maxGas Max gas to use for the L2 retryable ticket + * @param _gasPriceBid Gas price bid for the L2 retryable ticket + * @param _maxSubmissionCost Max submission cost for the L2 retryable ticket + */ + function transferLockedStakeToL2( + uint256 _amount, + uint256 _maxGas, + uint256 _gasPriceBid, + uint256 _maxSubmissionCost + ) external; + + /** + * @notice Send a delegator's delegated tokens to L2 + * @dev This function can only be called by the delegator. + * This function will validate that the indexer has transferred their stake using transferStakeToL2, + * and that the delegation is not locked for undelegation. + * Since the delegator's address might be an L1-only contract, the function takes a beneficiary + * address that will be the delegator's address in L2. + * The caller must provide an amount of ETH to use for the L2 retryable ticket, that + * must be at least `_maxSubmissionCost + _gasPriceBid * _maxGas`. + * @param _indexer Address of the indexer (in L1, before transferring to L2) + * @param _l2Beneficiary Address of the delegator in L2 + * @param _maxGas Max gas to use for the L2 retryable ticket + * @param _gasPriceBid Gas price bid for the L2 retryable ticket + * @param _maxSubmissionCost Max submission cost for the L2 retryable ticket + */ + function transferDelegationToL2( + address _indexer, + address _l2Beneficiary, + uint256 _maxGas, + uint256 _gasPriceBid, + uint256 _maxSubmissionCost + ) external payable; + + /** + * @notice Send a delegator's delegated tokens to L2, for a GraphTokenLockWallet vesting contract + * @dev This function can only be called by the delegator. + * This function will validate that the indexer has transferred their stake using transferStakeToL2, + * and that the delegation is not locked for undelegation. + * The L2 beneficiary for the delegation will be determined by calling the L1GraphTokenLockTransferTool contract, + * so the caller must have previously transferred tokens through that first + * (see GIP-0046 for details: https://forum.thegraph.com/t/4023). + * The ETH for the L2 gas will be pulled from the L1GraphTokenLockTransferTool, so the owner of + * the GraphTokenLockWallet must have previously deposited at least `_maxSubmissionCost + _gasPriceBid * _maxGas` + * ETH into the L1GraphTokenLockTransferTool contract (using its depositETH function). + * @param _indexer Address of the indexer (in L1, before transferring to L2) + * @param _maxGas Max gas to use for the L2 retryable ticket + * @param _gasPriceBid Gas price bid for the L2 retryable ticket + * @param _maxSubmissionCost Max submission cost for the L2 retryable ticket + */ + function transferLockedDelegationToL2( + address _indexer, + uint256 _maxGas, + uint256 _gasPriceBid, + uint256 _maxSubmissionCost + ) external; + + /** + * @notice Unlock a delegator's delegated tokens, if the indexer has transferred to L2 + * @dev This function can only be called by the delegator. + * This function will validate that the indexer has transferred their stake using transferStakeToL2, + * and that the indexer has no remaining stake in L1. + * The tokens must previously be locked for undelegation by calling `undelegate()`, + * and can be withdrawn with `withdrawDelegated()` immediately after calling this. + * @param _indexer Address of the indexer (in L1, before transferring to L2) + */ + function unlockDelegationToTransferredIndexer(address _indexer) external; +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/staking/IStaking.sol b/packages/legacy/verified/L1Staking/sources/contracts/staking/IStaking.sol new file mode 100644 index 000000000..d730ef806 --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/staking/IStaking.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; +pragma abicoder v2; + +import { IStakingBase } from "./IStakingBase.sol"; +import { IStakingExtension } from "./IStakingExtension.sol"; +import { IMulticall } from "../base/IMulticall.sol"; +import { IManaged } from "../governance/IManaged.sol"; + +/** + * @title Interface for the Staking contract + * @notice This is the interface that should be used when interacting with the Staking contract. + * @dev Note that Staking doesn't actually inherit this interface. This is because of + * the custom setup of the Staking contract where part of the functionality is implemented + * in a separate contract (StakingExtension) to which calls are delegated through the fallback function. + */ +interface IStaking is IStakingBase, IStakingExtension, IMulticall, IManaged { + // Nothing to see here +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/staking/IStakingBase.sol b/packages/legacy/verified/L1Staking/sources/contracts/staking/IStakingBase.sol new file mode 100644 index 000000000..acc8ac678 --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/staking/IStakingBase.sol @@ -0,0 +1,402 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; +pragma abicoder v2; + +import { IStakingData } from "./IStakingData.sol"; + +/** + * @title Base interface for the Staking contract. + * @dev This interface includes only what's implemented in the base Staking contract. + * It does not include the L1 and L2 specific functionality. It also does not include + * several functions that are implemented in the StakingExtension contract, and are called + * via delegatecall through the fallback function. See IStaking.sol for an interface + * that includes the full functionality. + */ +interface IStakingBase is IStakingData { + /** + * @dev Emitted when `indexer` stakes `tokens` amount. + */ + event StakeDeposited(address indexed indexer, uint256 tokens); + + /** + * @dev Emitted when `indexer` unstaked and locked `tokens` amount until `until` block. + */ + event StakeLocked(address indexed indexer, uint256 tokens, uint256 until); + + /** + * @dev Emitted when `indexer` withdrew `tokens` staked. + */ + event StakeWithdrawn(address indexed indexer, uint256 tokens); + + /** + * @dev Emitted when `indexer` allocated `tokens` amount to `subgraphDeploymentID` + * during `epoch`. + * `allocationID` indexer derived address used to identify the allocation. + * `metadata` additional information related to the allocation. + */ + event AllocationCreated( + address indexed indexer, + bytes32 indexed subgraphDeploymentID, + uint256 epoch, + uint256 tokens, + address indexed allocationID, + bytes32 metadata + ); + + /** + * @dev Emitted when `indexer` close an allocation in `epoch` for `allocationID`. + * An amount of `tokens` get unallocated from `subgraphDeploymentID`. + * This event also emits the POI (proof of indexing) submitted by the indexer. + * `isPublic` is true if the sender was someone other than the indexer. + */ + event AllocationClosed( + address indexed indexer, + bytes32 indexed subgraphDeploymentID, + uint256 epoch, + uint256 tokens, + address indexed allocationID, + address sender, + bytes32 poi, + bool isPublic + ); + + /** + * @dev Emitted when `indexer` collects a rebate on `subgraphDeploymentID` for `allocationID`. + * `epoch` is the protocol epoch the rebate was collected on + * The rebate is for `tokens` amount which are being provided by `assetHolder`; `queryFees` + * is the amount up for rebate after `curationFees` are distributed and `protocolTax` is burnt. + * `queryRebates` is the amount distributed to the `indexer` with `delegationFees` collected + * and sent to the delegation pool. + */ + event RebateCollected( + address assetHolder, + address indexed indexer, + bytes32 indexed subgraphDeploymentID, + address indexed allocationID, + uint256 epoch, + uint256 tokens, + uint256 protocolTax, + uint256 curationFees, + uint256 queryFees, + uint256 queryRebates, + uint256 delegationRewards + ); + + /** + * @dev Emitted when `indexer` update the delegation parameters for its delegation pool. + */ + event DelegationParametersUpdated( + address indexed indexer, + uint32 indexingRewardCut, + uint32 queryFeeCut, + uint32 cooldownBlocks + ); + + /** + * @dev Emitted when `caller` set `assetHolder` address as `allowed` to send funds + * to staking contract. + */ + event AssetHolderUpdate(address indexed caller, address indexed assetHolder, bool allowed); + + /** + * @dev Emitted when `indexer` set `operator` access. + */ + event SetOperator(address indexed indexer, address indexed operator, bool allowed); + + /** + * @dev Emitted when `indexer` set an address to receive rewards. + */ + event SetRewardsDestination(address indexed indexer, address indexed destination); + + /** + * @dev Emitted when `extensionImpl` was set as the address of the StakingExtension contract + * to which extended functionality is delegated. + */ + event ExtensionImplementationSet(address indexed extensionImpl); + + /** + * @dev Possible states an allocation can be. + * States: + * - Null = indexer == address(0) + * - Active = not Null && tokens > 0 + * - Closed = Active && closedAtEpoch != 0 + */ + enum AllocationState { + Null, + Active, + Closed + } + + /** + * @notice Initialize this contract. + * @param _controller Address of the controller that manages this contract + * @param _minimumIndexerStake Minimum amount of tokens that an indexer must stake + * @param _thawingPeriod Number of blocks that tokens get locked after unstaking + * @param _protocolPercentage Percentage of query fees that are burned as protocol fee (in PPM) + * @param _curationPercentage Percentage of query fees that are given to curators (in PPM) + * @param _maxAllocationEpochs The maximum number of epochs that an allocation can be active + * @param _delegationUnbondingPeriod The period in epochs that tokens get locked after undelegating + * @param _delegationRatio The ratio between an indexer's own stake and the delegation they can use + * @param _rebatesParameters Alpha and lambda parameters for rebates function + * @param _extensionImpl Address of the StakingExtension implementation + */ + function initialize( + address _controller, + uint256 _minimumIndexerStake, + uint32 _thawingPeriod, + uint32 _protocolPercentage, + uint32 _curationPercentage, + uint32 _maxAllocationEpochs, + uint32 _delegationUnbondingPeriod, + uint32 _delegationRatio, + RebatesParameters calldata _rebatesParameters, + address _extensionImpl + ) external; + + /** + * @notice Set the address of the StakingExtension implementation. + * @dev This function can only be called by the governor. + * @param _extensionImpl Address of the StakingExtension implementation + */ + function setExtensionImpl(address _extensionImpl) external; + + /** + * @notice Set the address of the counterpart (L1 or L2) staking contract. + * @dev This function can only be called by the governor. + * @param _counterpart Address of the counterpart staking contract in the other chain, without any aliasing. + */ + function setCounterpartStakingAddress(address _counterpart) external; + + /** + * @notice Set the minimum stake needed to be an Indexer + * @dev This function can only be called by the governor. + * @param _minimumIndexerStake Minimum amount of tokens that an indexer must stake + */ + function setMinimumIndexerStake(uint256 _minimumIndexerStake) external; + + /** + * @notice Set the number of blocks that tokens get locked after unstaking + * @dev This function can only be called by the governor. + * @param _thawingPeriod Number of blocks that tokens get locked after unstaking + */ + function setThawingPeriod(uint32 _thawingPeriod) external; + + /** + * @notice Set the curation percentage of query fees sent to curators. + * @dev This function can only be called by the governor. + * @param _percentage Percentage of query fees sent to curators + */ + function setCurationPercentage(uint32 _percentage) external; + + /** + * @notice Set a protocol percentage to burn when collecting query fees. + * @dev This function can only be called by the governor. + * @param _percentage Percentage of query fees to burn as protocol fee + */ + function setProtocolPercentage(uint32 _percentage) external; + + /** + * @notice Set the max time allowed for indexers to allocate on a subgraph + * before others are allowed to close the allocation. + * @dev This function can only be called by the governor. + * @param _maxAllocationEpochs Allocation duration limit in epochs + */ + function setMaxAllocationEpochs(uint32 _maxAllocationEpochs) external; + + /** + * @notice Set the rebate parameters + * @dev This function can only be called by the governor. + * @param _alphaNumerator Numerator of `alpha` + * @param _alphaDenominator Denominator of `alpha` + * @param _lambdaNumerator Numerator of `lambda` + * @param _lambdaDenominator Denominator of `lambda` + */ + function setRebateParameters( + uint32 _alphaNumerator, + uint32 _alphaDenominator, + uint32 _lambdaNumerator, + uint32 _lambdaDenominator + ) external; + + /** + * @notice Set an address as allowed asset holder. + * @dev This function can only be called by the governor. + * @param _assetHolder Address of allowed source for state channel funds + * @param _allowed True if asset holder is allowed + */ + function setAssetHolder(address _assetHolder, bool _allowed) external; + + /** + * @notice Authorize or unauthorize an address to be an operator for the caller. + * @param _operator Address to authorize or unauthorize + * @param _allowed Whether the operator is authorized or not + */ + function setOperator(address _operator, bool _allowed) external; + + /** + * @notice Deposit tokens on the indexer's stake. + * The amount staked must be over the minimumIndexerStake. + * @param _tokens Amount of tokens to stake + */ + function stake(uint256 _tokens) external; + + /** + * @notice Deposit tokens on the Indexer stake, on behalf of the Indexer. + * The amount staked must be over the minimumIndexerStake. + * @param _indexer Address of the indexer + * @param _tokens Amount of tokens to stake + */ + function stakeTo(address _indexer, uint256 _tokens) external; + + /** + * @notice Unstake tokens from the indexer stake, lock them until the thawing period expires. + * @dev NOTE: The function accepts an amount greater than the currently staked tokens. + * If that happens, it will try to unstake the max amount of tokens it can. + * The reason for this behaviour is to avoid time conditions while the transaction + * is in flight. + * @param _tokens Amount of tokens to unstake + */ + function unstake(uint256 _tokens) external; + + /** + * @notice Withdraw indexer tokens once the thawing period has passed. + */ + function withdraw() external; + + /** + * @notice Set the destination where to send rewards for an indexer. + * @param _destination Rewards destination address. If set to zero, rewards will be restaked + */ + function setRewardsDestination(address _destination) external; + + /** + * @notice Set the delegation parameters for the caller. + * @param _indexingRewardCut Percentage of indexing rewards left for the indexer + * @param _queryFeeCut Percentage of query fees left for the indexer + * @param _cooldownBlocks Period that need to pass to update delegation parameters + */ + function setDelegationParameters( + uint32 _indexingRewardCut, + uint32 _queryFeeCut, + uint32 _cooldownBlocks + ) external; + + /** + * @notice Allocate available tokens to a subgraph deployment. + * @param _subgraphDeploymentID ID of the SubgraphDeployment where tokens will be allocated + * @param _tokens Amount of tokens to allocate + * @param _allocationID The allocation identifier + * @param _metadata IPFS hash for additional information about the allocation + * @param _proof A 65-bytes Ethereum signed message of `keccak256(indexerAddress,allocationID)` + */ + function allocate( + bytes32 _subgraphDeploymentID, + uint256 _tokens, + address _allocationID, + bytes32 _metadata, + bytes calldata _proof + ) external; + + /** + * @notice Allocate available tokens to a subgraph deployment from and indexer's stake. + * The caller must be the indexer or the indexer's operator. + * @param _indexer Indexer address to allocate funds from. + * @param _subgraphDeploymentID ID of the SubgraphDeployment where tokens will be allocated + * @param _tokens Amount of tokens to allocate + * @param _allocationID The allocation identifier + * @param _metadata IPFS hash for additional information about the allocation + * @param _proof A 65-bytes Ethereum signed message of `keccak256(indexerAddress,allocationID)` + */ + function allocateFrom( + address _indexer, + bytes32 _subgraphDeploymentID, + uint256 _tokens, + address _allocationID, + bytes32 _metadata, + bytes calldata _proof + ) external; + + /** + * @notice Close an allocation and free the staked tokens. + * To be eligible for rewards a proof of indexing must be presented. + * Presenting a bad proof is subject to slashable condition. + * To opt out of rewards set _poi to 0x0 + * @param _allocationID The allocation identifier + * @param _poi Proof of indexing submitted for the allocated period + */ + function closeAllocation(address _allocationID, bytes32 _poi) external; + + /** + * @notice Collect query fees from state channels and assign them to an allocation. + * Funds received are only accepted from a valid sender. + * @dev To avoid reverting on the withdrawal from channel flow this function will: + * 1) Accept calls with zero tokens. + * 2) Accept calls after an allocation passed the dispute period, in that case, all + * the received tokens are burned. + * @param _tokens Amount of tokens to collect + * @param _allocationID Allocation where the tokens will be assigned + */ + function collect(uint256 _tokens, address _allocationID) external; + + /** + * @notice Return true if operator is allowed for indexer. + * @param _operator Address of the operator + * @param _indexer Address of the indexer + * @return True if operator is allowed for indexer, false otherwise + */ + function isOperator(address _operator, address _indexer) external view returns (bool); + + /** + * @notice Getter that returns if an indexer has any stake. + * @param _indexer Address of the indexer + * @return True if indexer has staked tokens + */ + function hasStake(address _indexer) external view returns (bool); + + /** + * @notice Get the total amount of tokens staked by the indexer. + * @param _indexer Address of the indexer + * @return Amount of tokens staked by the indexer + */ + function getIndexerStakedTokens(address _indexer) external view returns (uint256); + + /** + * @notice Get the total amount of tokens available to use in allocations. + * This considers the indexer stake and delegated tokens according to delegation ratio + * @param _indexer Address of the indexer + * @return Amount of tokens available to allocate including delegation + */ + function getIndexerCapacity(address _indexer) external view returns (uint256); + + /** + * @notice Return the allocation by ID. + * @param _allocationID Address used as allocation identifier + * @return Allocation data + */ + function getAllocation(address _allocationID) external view returns (Allocation memory); + + /** + * @notice Return the current state of an allocation + * @param _allocationID Allocation identifier + * @return AllocationState enum with the state of the allocation + */ + function getAllocationState(address _allocationID) external view returns (AllocationState); + + /** + * @notice Return if allocationID is used. + * @param _allocationID Address used as signer by the indexer for an allocation + * @return True if allocationID already used + */ + function isAllocation(address _allocationID) external view returns (bool); + + /** + * @notice Return the total amount of tokens allocated to subgraph. + * @param _subgraphDeploymentID Deployment ID for the subgraph + * @return Total tokens allocated to subgraph + */ + function getSubgraphAllocatedTokens(bytes32 _subgraphDeploymentID) + external + view + returns (uint256); +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/staking/IStakingData.sol b/packages/legacy/verified/L1Staking/sources/contracts/staking/IStakingData.sol new file mode 100644 index 000000000..39ca8f3e9 --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/staking/IStakingData.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; + +/** + * @title Staking Data interface + * @dev This interface defines some structures used by the Staking contract. + */ +interface IStakingData { + /** + * @dev Allocate GRT tokens for the purpose of serving queries of a subgraph deployment + * An allocation is created in the allocate() function and closed in closeAllocation() + */ + struct Allocation { + address indexer; + bytes32 subgraphDeploymentID; + uint256 tokens; // Tokens allocated to a SubgraphDeployment + uint256 createdAtEpoch; // Epoch when it was created + uint256 closedAtEpoch; // Epoch when it was closed + uint256 collectedFees; // Collected fees for the allocation + uint256 __DEPRECATED_effectiveAllocation; // Effective allocation when closed + uint256 accRewardsPerAllocatedToken; // Snapshot used for reward calc + uint256 distributedRebates; // Collected rebates that have been rebated + } + + // -- Delegation Data -- + + /** + * @dev Delegation pool information. One per indexer. + */ + struct DelegationPool { + uint32 cooldownBlocks; // Blocks to wait before updating parameters + uint32 indexingRewardCut; // in PPM + uint32 queryFeeCut; // in PPM + uint256 updatedAtBlock; // Block when the pool was last updated + uint256 tokens; // Total tokens as pool reserves + uint256 shares; // Total shares minted in the pool + mapping(address => Delegation) delegators; // Mapping of delegator => Delegation + } + + /** + * @dev Individual delegation data of a delegator in a pool. + */ + struct Delegation { + uint256 shares; // Shares owned by a delegator in the pool + uint256 tokensLocked; // Tokens locked for undelegation + uint256 tokensLockedUntil; // Block when locked tokens can be withdrawn + } + + /** + * @dev Rebates parameters. Used to avoid stack too deep errors in Staking initialize function. + */ + struct RebatesParameters { + uint32 alphaNumerator; + uint32 alphaDenominator; + uint32 lambdaNumerator; + uint32 lambdaDenominator; + } +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/staking/IStakingExtension.sol b/packages/legacy/verified/L1Staking/sources/contracts/staking/IStakingExtension.sol new file mode 100644 index 000000000..815577e2f --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/staking/IStakingExtension.sol @@ -0,0 +1,344 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; +pragma abicoder v2; + +import { IStakingData } from "./IStakingData.sol"; +import { Stakes } from "./libs/Stakes.sol"; + +/** + * @title Interface for the StakingExtension contract + * @dev This interface defines the events and functions implemented + * in the StakingExtension contract, which is used to extend the functionality + * of the Staking contract while keeping it within the 24kB mainnet size limit. + * In particular, this interface includes delegation functions and various storage + * getters. + */ +interface IStakingExtension is IStakingData { + /** + * @dev DelegationPool struct as returned by delegationPools(), since + * the original DelegationPool in IStakingData.sol contains a nested mapping. + */ + struct DelegationPoolReturn { + uint32 cooldownBlocks; // Blocks to wait before updating parameters + uint32 indexingRewardCut; // in PPM + uint32 queryFeeCut; // in PPM + uint256 updatedAtBlock; // Block when the pool was last updated + uint256 tokens; // Total tokens as pool reserves + uint256 shares; // Total shares minted in the pool + } + + /** + * @dev Emitted when `delegator` delegated `tokens` to the `indexer`, the delegator + * gets `shares` for the delegation pool proportionally to the tokens staked. + */ + event StakeDelegated( + address indexed indexer, + address indexed delegator, + uint256 tokens, + uint256 shares + ); + + /** + * @dev Emitted when `delegator` undelegated `tokens` from `indexer`. + * Tokens get locked for withdrawal after a period of time. + */ + event StakeDelegatedLocked( + address indexed indexer, + address indexed delegator, + uint256 tokens, + uint256 shares, + uint256 until + ); + + /** + * @dev Emitted when `delegator` withdrew delegated `tokens` from `indexer`. + */ + event StakeDelegatedWithdrawn( + address indexed indexer, + address indexed delegator, + uint256 tokens + ); + + /** + * @dev Emitted when `indexer` was slashed for a total of `tokens` amount. + * Tracks `reward` amount of tokens given to `beneficiary`. + */ + event StakeSlashed( + address indexed indexer, + uint256 tokens, + uint256 reward, + address beneficiary + ); + + /** + * @dev Emitted when `caller` set `slasher` address as `allowed` to slash stakes. + */ + event SlasherUpdate(address indexed caller, address indexed slasher, bool allowed); + + /** + * @notice Set the delegation ratio. + * If set to 10 it means the indexer can use up to 10x the indexer staked amount + * from their delegated tokens + * @dev This function is only callable by the governor + * @param _delegationRatio Delegation capacity multiplier + */ + function setDelegationRatio(uint32 _delegationRatio) external; + + /** + * @notice Set the minimum time in blocks an indexer needs to wait to change delegation parameters. + * Indexers can set a custom amount time for their own cooldown, but it must be greater than this. + * @dev This function is only callable by the governor + * @param _blocks Number of blocks to set the delegation parameters cooldown period + */ + function setDelegationParametersCooldown(uint32 _blocks) external; + + /** + * @notice Set the time, in epochs, a Delegator needs to wait to withdraw tokens after undelegating. + * @dev This function is only callable by the governor + * @param _delegationUnbondingPeriod Period in epochs to wait for token withdrawals after undelegating + */ + function setDelegationUnbondingPeriod(uint32 _delegationUnbondingPeriod) external; + + /** + * @notice Set a delegation tax percentage to burn when delegated funds are deposited. + * @dev This function is only callable by the governor + * @param _percentage Percentage of delegated tokens to burn as delegation tax, expressed in parts per million + */ + function setDelegationTaxPercentage(uint32 _percentage) external; + + /** + * @notice Set or unset an address as allowed slasher. + * @dev This function can only be called by the governor. + * @param _slasher Address of the party allowed to slash indexers + * @param _allowed True if slasher is allowed + */ + function setSlasher(address _slasher, bool _allowed) external; + + /** + * @notice Delegate tokens to an indexer. + * @param _indexer Address of the indexer to which tokens are delegated + * @param _tokens Amount of tokens to delegate + * @return Amount of shares issued from the delegation pool + */ + function delegate(address _indexer, uint256 _tokens) external returns (uint256); + + /** + * @notice Undelegate tokens from an indexer. Tokens will be locked for the unbonding period. + * @param _indexer Address of the indexer to which tokens had been delegated + * @param _shares Amount of shares to return and undelegate tokens + * @return Amount of tokens returned for the shares of the delegation pool + */ + function undelegate(address _indexer, uint256 _shares) external returns (uint256); + + /** + * @notice Withdraw undelegated tokens once the unbonding period has passed, and optionally + * re-delegate to a new indexer. + * @param _indexer Withdraw available tokens delegated to indexer + * @param _newIndexer Re-delegate to indexer address if non-zero, withdraw if zero address + */ + function withdrawDelegated(address _indexer, address _newIndexer) external returns (uint256); + + /** + * @notice Slash the indexer stake. Delegated tokens are not subject to slashing. + * @dev Can only be called by the slasher role. + * @param _indexer Address of indexer to slash + * @param _tokens Amount of tokens to slash from the indexer stake + * @param _reward Amount of reward tokens to send to a beneficiary + * @param _beneficiary Address of a beneficiary to receive a reward for the slashing + */ + function slash( + address _indexer, + uint256 _tokens, + uint256 _reward, + address _beneficiary + ) external; + + /** + * @notice Return the delegation from a delegator to an indexer. + * @param _indexer Address of the indexer where funds have been delegated + * @param _delegator Address of the delegator + * @return Delegation data + */ + function getDelegation(address _indexer, address _delegator) + external + view + returns (Delegation memory); + + /** + * @notice Return whether the delegator has delegated to the indexer. + * @param _indexer Address of the indexer where funds have been delegated + * @param _delegator Address of the delegator + * @return True if delegator has tokens delegated to the indexer + */ + function isDelegator(address _indexer, address _delegator) external view returns (bool); + + /** + * @notice Returns amount of delegated tokens ready to be withdrawn after unbonding period. + * @param _delegation Delegation of tokens from delegator to indexer + * @return Amount of tokens to withdraw + */ + function getWithdraweableDelegatedTokens(Delegation memory _delegation) + external + view + returns (uint256); + + /** + * @notice Getter for the delegationRatio, i.e. the delegation capacity multiplier: + * If delegation ratio is 100, and an Indexer has staked 5 GRT, + * then they can use up to 500 GRT from the delegated stake + * @return Delegation ratio + */ + function delegationRatio() external view returns (uint32); + + /** + * @notice Getter for delegationParametersCooldown: + * Minimum time in blocks an indexer needs to wait to change delegation parameters + * @return Delegation parameters cooldown in blocks + */ + function delegationParametersCooldown() external view returns (uint32); + + /** + * @notice Getter for delegationUnbondingPeriod: + * Time in epochs a delegator needs to wait to withdraw delegated stake + * @return Delegation unbonding period in epochs + */ + function delegationUnbondingPeriod() external view returns (uint32); + + /** + * @notice Getter for delegationTaxPercentage: + * Percentage of tokens to tax a delegation deposit, expressed in parts per million + * @return Delegation tax percentage in parts per million + */ + function delegationTaxPercentage() external view returns (uint32); + + /** + * @notice Getter for delegationPools[_indexer]: + * gets the delegation pool structure for a particular indexer. + * @param _indexer Address of the indexer for which to query the delegation pool + * @return Delegation pool as a DelegationPoolReturn struct + */ + function delegationPools(address _indexer) external view returns (DelegationPoolReturn memory); + + /** + * @notice Getter for operatorAuth[_indexer][_maybeOperator]: + * returns true if the operator is authorized to operate on behalf of the indexer. + * @param _indexer The indexer address for which to query authorization + * @param _maybeOperator The address that may or may not be an operator + * @return True if the operator is authorized to operate on behalf of the indexer + */ + function operatorAuth(address _indexer, address _maybeOperator) external view returns (bool); + + /** + * @notice Getter for rewardsDestination[_indexer]: + * returns the address where the indexer's rewards are sent. + * @param _indexer The indexer address for which to query the rewards destination + * @return The address where the indexer's rewards are sent, zero if none is set in which case rewards are re-staked + */ + function rewardsDestination(address _indexer) external view returns (address); + + /** + * @notice Getter for assetHolders[_maybeAssetHolder]: + * returns true if the address is an asset holder, i.e. an entity that can collect + * query fees into the Staking contract. + * @param _maybeAssetHolder The address that may or may not be an asset holder + * @return True if the address is an asset holder + */ + function assetHolders(address _maybeAssetHolder) external view returns (bool); + + /** + * @notice Getter for subgraphAllocations[_subgraphDeploymentId]: + * returns the amount of tokens allocated to a subgraph deployment. + * @param _subgraphDeploymentId The subgraph deployment for which to query the allocations + * @return The amount of tokens allocated to the subgraph deployment + */ + function subgraphAllocations(bytes32 _subgraphDeploymentId) external view returns (uint256); + + /** + * @notice Getter for slashers[_maybeSlasher]: + * returns true if the address is a slasher, i.e. an entity that can slash indexers + * @param _maybeSlasher Address for which to check the slasher role + * @return True if the address is a slasher + */ + function slashers(address _maybeSlasher) external view returns (bool); + + /** + * @notice Getter for minimumIndexerStake: the minimum + * amount of GRT that an indexer needs to stake. + * @return Minimum indexer stake in GRT + */ + function minimumIndexerStake() external view returns (uint256); + + /** + * @notice Getter for thawingPeriod: the time in blocks an + * indexer needs to wait to unstake tokens. + * @return Thawing period in blocks + */ + function thawingPeriod() external view returns (uint32); + + /** + * @notice Getter for curationPercentage: the percentage of + * query fees that are distributed to curators. + * @return Curation percentage in parts per million + */ + function curationPercentage() external view returns (uint32); + + /** + * @notice Getter for protocolPercentage: the percentage of + * query fees that are burned as protocol fees. + * @return Protocol percentage in parts per million + */ + function protocolPercentage() external view returns (uint32); + + /** + * @notice Getter for maxAllocationEpochs: the maximum time in epochs + * that an allocation can be open before anyone is allowed to close it. This + * also caps the effective allocation when sending the allocation's query fees + * to the rebate pool. + * @return Maximum allocation period in epochs + */ + function maxAllocationEpochs() external view returns (uint32); + + /** + * @notice Getter for the numerator of the rebates alpha parameter + * @return Alpha numerator + */ + function alphaNumerator() external view returns (uint32); + + /** + * @notice Getter for the denominator of the rebates alpha parameter + * @return Alpha denominator + */ + function alphaDenominator() external view returns (uint32); + + /** + * @notice Getter for the numerator of the rebates lambda parameter + * @return Lambda numerator + */ + function lambdaNumerator() external view returns (uint32); + + /** + * @notice Getter for the denominator of the rebates lambda parameter + * @return Lambda denominator + */ + function lambdaDenominator() external view returns (uint32); + + /** + * @notice Getter for stakes[_indexer]: + * gets the stake information for an indexer as a Stakes.Indexer struct. + * @param _indexer Indexer address for which to query the stake information + * @return Stake information for the specified indexer, as a Stakes.Indexer struct + */ + function stakes(address _indexer) external view returns (Stakes.Indexer memory); + + /** + * @notice Getter for allocations[_allocationID]: + * gets an allocation's information as an IStakingData.Allocation struct. + * @param _allocationID Allocation ID for which to query the allocation information + * @return The specified allocation, as an IStakingData.Allocation struct + */ + function allocations(address _allocationID) + external + view + returns (IStakingData.Allocation memory); +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/staking/L1Staking.sol b/packages/legacy/verified/L1Staking/sources/contracts/staking/L1Staking.sol new file mode 100644 index 000000000..cf9bca304 --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/staking/L1Staking.sol @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; +pragma abicoder v2; + +import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; + +import { ITokenGateway } from "../arbitrum/ITokenGateway.sol"; +import { Staking } from "./Staking.sol"; +import { Stakes } from "./libs/Stakes.sol"; +import { IStakingData } from "./IStakingData.sol"; +import { IL2Staking } from "../l2/staking/IL2Staking.sol"; +import { L1StakingV1Storage } from "./L1StakingStorage.sol"; +import { IGraphToken } from "../token/IGraphToken.sol"; +import { IL1StakingBase } from "./IL1StakingBase.sol"; +import { MathUtils } from "./libs/MathUtils.sol"; +import { IL1GraphTokenLockTransferTool } from "./IL1GraphTokenLockTransferTool.sol"; + +/** + * @title L1Staking contract + * @dev This contract is the L1 variant of the Staking contract. It adds functions + * to send an indexer's stake to L2, and to send delegation to L2 as well. + */ +contract L1Staking is Staking, L1StakingV1Storage, IL1StakingBase { + using Stakes for Stakes.Indexer; + using SafeMath for uint256; + + /** + * @notice Receive ETH into the Staking contract + * @dev Only the L1GraphTokenLockTransferTool can send ETH, as part of the + * transfer of stake/delegation for vesting lock wallets. + */ + receive() external payable { + require( + msg.sender == address(l1GraphTokenLockTransferTool), + "Only transfer tool can send ETH" + ); + } + + /** + * @notice Set the L1GraphTokenLockTransferTool contract address + * @dev This function can only be called by the governor. + * @param _l1GraphTokenLockTransferTool Address of the L1GraphTokenLockTransferTool contract + */ + function setL1GraphTokenLockTransferTool( + IL1GraphTokenLockTransferTool _l1GraphTokenLockTransferTool + ) external override onlyGovernor { + l1GraphTokenLockTransferTool = _l1GraphTokenLockTransferTool; + emit L1GraphTokenLockTransferToolSet(address(_l1GraphTokenLockTransferTool)); + } + + /** + * @notice Send an indexer's stake to L2. + * @dev This function can only be called by the indexer (not an operator). + * It will validate that the remaining stake is sufficient to cover all the allocated + * stake, so the indexer might have to close some allocations before transferring. + * It will also check that the indexer's stake is not locked for withdrawal. + * Since the indexer address might be an L1-only contract, the function takes a beneficiary + * address that will be the indexer's address in L2. + * The caller must provide an amount of ETH to use for the L2 retryable ticket, that + * must be at _exactly_ `_maxSubmissionCost + _gasPriceBid * _maxGas`. + * Any refunds for the submission fee or L2 gas will be lost. + * @param _l2Beneficiary Address of the indexer in L2. If the indexer has previously transferred stake, this must match the previously-used value. + * @param _amount Amount of stake GRT to transfer to L2 + * @param _maxGas Max gas to use for the L2 retryable ticket + * @param _gasPriceBid Gas price bid for the L2 retryable ticket + * @param _maxSubmissionCost Max submission cost for the L2 retryable ticket + */ + function transferStakeToL2( + address _l2Beneficiary, + uint256 _amount, + uint256 _maxGas, + uint256 _gasPriceBid, + uint256 _maxSubmissionCost + ) external payable override notPartialPaused { + require( + msg.value == _maxSubmissionCost.add(_gasPriceBid.mul(_maxGas)), + "INVALID_ETH_AMOUNT" + ); + _transferStakeToL2( + msg.sender, + _l2Beneficiary, + _amount, + _maxGas, + _gasPriceBid, + _maxSubmissionCost, + msg.value + ); + } + + /** + * @notice Send an indexer's stake to L2, from a GraphTokenLockWallet vesting contract. + * @dev This function can only be called by the indexer (not an operator). + * It will validate that the remaining stake is sufficient to cover all the allocated + * stake, so the indexer might have to close some allocations before transferring. + * It will also check that the indexer's stake is not locked for withdrawal. + * The L2 beneficiary for the stake will be determined by calling the L1GraphTokenLockTransferTool contract, + * so the caller must have previously transferred tokens through that first + * (see GIP-0046 for details: https://forum.thegraph.com/t/4023). + * The ETH for the L2 gas will be pulled from the L1GraphTokenLockTransferTool, so the owner of + * the GraphTokenLockWallet must have previously deposited at least `_maxSubmissionCost + _gasPriceBid * _maxGas` + * ETH into the L1GraphTokenLockTransferTool contract (using its depositETH function). + * Any refunds for the submission fee or L2 gas will be lost. + * @param _amount Amount of stake GRT to transfer to L2 + * @param _maxGas Max gas to use for the L2 retryable ticket + * @param _gasPriceBid Gas price bid for the L2 retryable ticket + * @param _maxSubmissionCost Max submission cost for the L2 retryable ticket + */ + function transferLockedStakeToL2( + uint256 _amount, + uint256 _maxGas, + uint256 _gasPriceBid, + uint256 _maxSubmissionCost + ) external override notPartialPaused { + address l2Beneficiary = l1GraphTokenLockTransferTool.l2WalletAddress(msg.sender); + require(l2Beneficiary != address(0), "LOCK NOT TRANSFERRED"); + uint256 balance = address(this).balance; + uint256 ethAmount = _maxSubmissionCost.add(_maxGas.mul(_gasPriceBid)); + l1GraphTokenLockTransferTool.pullETH(msg.sender, ethAmount); + require(address(this).balance == balance.add(ethAmount), "ETH TRANSFER FAILED"); + _transferStakeToL2( + msg.sender, + l2Beneficiary, + _amount, + _maxGas, + _gasPriceBid, + _maxSubmissionCost, + ethAmount + ); + } + + /** + * @notice Send a delegator's delegated tokens to L2 + * @dev This function can only be called by the delegator. + * This function will validate that the indexer has transferred their stake using transferStakeToL2, + * and that the delegation is not locked for undelegation. + * Since the delegator's address might be an L1-only contract, the function takes a beneficiary + * address that will be the delegator's address in L2. + * The caller must provide an amount of ETH to use for the L2 retryable ticket, that + * must be _exactly_ `_maxSubmissionCost + _gasPriceBid * _maxGas`. + * Any refunds for the submission fee or L2 gas will be lost. + * @param _indexer Address of the indexer (in L1, before transferring to L2) + * @param _l2Beneficiary Address of the delegator in L2 + * @param _maxGas Max gas to use for the L2 retryable ticket + * @param _gasPriceBid Gas price bid for the L2 retryable ticket + * @param _maxSubmissionCost Max submission cost for the L2 retryable ticket + */ + function transferDelegationToL2( + address _indexer, + address _l2Beneficiary, + uint256 _maxGas, + uint256 _gasPriceBid, + uint256 _maxSubmissionCost + ) external payable override notPartialPaused { + require( + msg.value == _maxSubmissionCost.add(_gasPriceBid.mul(_maxGas)), + "INVALID_ETH_AMOUNT" + ); + _transferDelegationToL2( + msg.sender, + _indexer, + _l2Beneficiary, + _maxGas, + _gasPriceBid, + _maxSubmissionCost, + msg.value + ); + } + + /** + * @notice Send a delegator's delegated tokens to L2, for a GraphTokenLockWallet vesting contract + * @dev This function can only be called by the delegator. + * This function will validate that the indexer has transferred their stake using transferStakeToL2, + * and that the delegation is not locked for undelegation. + * The L2 beneficiary for the delegation will be determined by calling the L1GraphTokenLockTransferTool contract, + * so the caller must have previously transferred tokens through that first + * (see GIP-0046 for details: https://forum.thegraph.com/t/4023). + * The ETH for the L2 gas will be pulled from the L1GraphTokenLockTransferTool, so the owner of + * the GraphTokenLockWallet must have previously deposited at least `_maxSubmissionCost + _gasPriceBid * _maxGas` + * ETH into the L1GraphTokenLockTransferTool contract (using its depositETH function). + * Any refunds for the submission fee or L2 gas will be lost. + * @param _indexer Address of the indexer (in L1, before transferring to L2) + * @param _maxGas Max gas to use for the L2 retryable ticket + * @param _gasPriceBid Gas price bid for the L2 retryable ticket + * @param _maxSubmissionCost Max submission cost for the L2 retryable ticket + */ + function transferLockedDelegationToL2( + address _indexer, + uint256 _maxGas, + uint256 _gasPriceBid, + uint256 _maxSubmissionCost + ) external override notPartialPaused { + address l2Beneficiary = l1GraphTokenLockTransferTool.l2WalletAddress(msg.sender); + require(l2Beneficiary != address(0), "LOCK NOT TRANSFERRED"); + uint256 balance = address(this).balance; + uint256 ethAmount = _maxSubmissionCost.add(_maxGas.mul(_gasPriceBid)); + l1GraphTokenLockTransferTool.pullETH(msg.sender, ethAmount); + require(address(this).balance == balance.add(ethAmount), "ETH TRANSFER FAILED"); + _transferDelegationToL2( + msg.sender, + _indexer, + l2Beneficiary, + _maxGas, + _gasPriceBid, + _maxSubmissionCost, + ethAmount + ); + } + + /** + * @notice Unlock a delegator's delegated tokens, if the indexer has transferred to L2 + * @dev This function can only be called by the delegator. + * This function will validate that the indexer has transferred their stake using transferStakeToL2, + * and that the indexer has no remaining stake in L1. + * The tokens must previously be locked for undelegation by calling `undelegate()`, + * and can be withdrawn with `withdrawDelegated()` immediately after calling this. + * @param _indexer Address of the indexer (in L1, before transferring to L2) + */ + function unlockDelegationToTransferredIndexer(address _indexer) + external + override + notPartialPaused + { + require( + indexerTransferredToL2[_indexer] != address(0) && __stakes[_indexer].tokensStaked == 0, + "indexer not transferred" + ); + + Delegation storage delegation = __delegationPools[_indexer].delegators[msg.sender]; + require(delegation.tokensLocked != 0, "! locked"); + + // Unlock the delegation + delegation.tokensLockedUntil = epochManager().currentEpoch(); + + // After this, the delegator should be able to withdraw in the current block + emit StakeDelegatedUnlockedDueToL2Transfer(_indexer, msg.sender); + } + + /** + * @dev Implements sending an indexer's stake to L2. + * This function can only be called by the indexer (not an operator). + * It will validate that the remaining stake is sufficient to cover all the allocated + * stake, so the indexer might have to close some allocations before transferring. + * It will also check that the indexer's stake is not locked for withdrawal. + * Since the indexer address might be an L1-only contract, the function takes a beneficiary + * address that will be the indexer's address in L2. + * @param _l2Beneficiary Address of the indexer in L2. If the indexer has previously transferred stake, this must match the previously-used value. + * @param _amount Amount of stake GRT to transfer to L2 + * @param _maxGas Max gas to use for the L2 retryable ticket + * @param _gasPriceBid Gas price bid for the L2 retryable ticket + * @param _maxSubmissionCost Max submission cost for the L2 retryable ticket + * @param _ethAmount Amount of ETH to send with the retryable ticket + */ + function _transferStakeToL2( + address _indexer, + address _l2Beneficiary, + uint256 _amount, + uint256 _maxGas, + uint256 _gasPriceBid, + uint256 _maxSubmissionCost, + uint256 _ethAmount + ) internal { + Stakes.Indexer storage indexerStake = __stakes[_indexer]; + require(indexerStake.tokensStaked != 0, "tokensStaked == 0"); + // Indexers shouldn't be trying to withdraw tokens before transferring to L2. + // Allowing this would complicate our accounting so we require that they have no + // tokens locked for withdrawal. + require(indexerStake.tokensLocked == 0, "tokensLocked != 0"); + + require(_l2Beneficiary != address(0), "l2Beneficiary == 0"); + if (indexerTransferredToL2[_indexer] != address(0)) { + require( + indexerTransferredToL2[_indexer] == _l2Beneficiary, + "l2Beneficiary != previous" + ); + } else { + indexerTransferredToL2[_indexer] = _l2Beneficiary; + require(_amount >= __minimumIndexerStake, "!minimumIndexerStake sent"); + } + // Ensure minimum stake + indexerStake.tokensStaked = indexerStake.tokensStaked.sub(_amount); + require( + indexerStake.tokensStaked == 0 || indexerStake.tokensStaked >= __minimumIndexerStake, + "!minimumIndexerStake remaining" + ); + + IStakingData.DelegationPool storage delegationPool = __delegationPools[_indexer]; + + if (indexerStake.tokensStaked == 0) { + // require that no allocations are open + require(indexerStake.tokensAllocated == 0, "allocated"); + } else { + // require that the indexer has enough stake to cover all allocations + uint256 tokensDelegatedCap = indexerStake.tokensStaked.mul(uint256(__delegationRatio)); + uint256 tokensDelegatedCapacity = MathUtils.min( + delegationPool.tokens, + tokensDelegatedCap + ); + require( + indexerStake.tokensUsed() <= indexerStake.tokensStaked.add(tokensDelegatedCapacity), + "! allocation capacity" + ); + } + + IL2Staking.ReceiveIndexerStakeData memory functionData; + functionData.indexer = _l2Beneficiary; + + bytes memory extraData = abi.encode( + uint8(IL2Staking.L1MessageCodes.RECEIVE_INDEXER_STAKE_CODE), + abi.encode(functionData) + ); + + _sendTokensAndMessageToL2Staking( + _amount, + _maxGas, + _gasPriceBid, + _maxSubmissionCost, + _ethAmount, + extraData + ); + + emit IndexerStakeTransferredToL2(_indexer, _l2Beneficiary, _amount); + } + + /** + * @dev Implements sending a delegator's delegated tokens to L2. + * This function can only be called by the delegator. + * This function will validate that the indexer has transferred their stake using transferStakeToL2, + * and that the delegation is not locked for undelegation. + * Since the delegator's address might be an L1-only contract, the function takes a beneficiary + * address that will be the delegator's address in L2. + * @param _indexer Address of the indexer (in L1, before transferring to L2) + * @param _l2Beneficiary Address of the delegator in L2 + * @param _maxGas Max gas to use for the L2 retryable ticket + * @param _gasPriceBid Gas price bid for the L2 retryable ticket + * @param _maxSubmissionCost Max submission cost for the L2 retryable ticket + * @param _ethAmount Amount of ETH to send with the retryable ticket + */ + function _transferDelegationToL2( + address _delegator, + address _indexer, + address _l2Beneficiary, + uint256 _maxGas, + uint256 _gasPriceBid, + uint256 _maxSubmissionCost, + uint256 _ethAmount + ) internal { + require(_l2Beneficiary != address(0), "l2Beneficiary == 0"); + require(indexerTransferredToL2[_indexer] != address(0), "indexer not transferred"); + + // Get the delegation pool of the indexer + DelegationPool storage pool = __delegationPools[_indexer]; + Delegation storage delegation = pool.delegators[_delegator]; + + // Check that the delegation is not locked for undelegation + require(delegation.tokensLocked == 0, "tokensLocked != 0"); + require(delegation.shares != 0, "delegation == 0"); + // Calculate tokens to get in exchange for the shares + uint256 tokensToSend = delegation.shares.mul(pool.tokens).div(pool.shares); + + // Update the delegation pool + pool.tokens = pool.tokens.sub(tokensToSend); + pool.shares = pool.shares.sub(delegation.shares); + + // Update the delegation + delegation.shares = 0; + bytes memory extraData; + { + IL2Staking.ReceiveDelegationData memory functionData; + functionData.indexer = indexerTransferredToL2[_indexer]; + functionData.delegator = _l2Beneficiary; + extraData = abi.encode( + uint8(IL2Staking.L1MessageCodes.RECEIVE_DELEGATION_CODE), + abi.encode(functionData) + ); + } + + _sendTokensAndMessageToL2Staking( + tokensToSend, + _maxGas, + _gasPriceBid, + _maxSubmissionCost, + _ethAmount, + extraData + ); + emit DelegationTransferredToL2( + _delegator, + _l2Beneficiary, + _indexer, + indexerTransferredToL2[_indexer], + tokensToSend + ); + } + + /** + * @dev Sends a message to the L2Staking with some extra data, + * also sending some tokens, using the L1GraphTokenGateway. + * @param _tokens Amount of tokens to send to L2 + * @param _maxGas Max gas to use for the L2 retryable ticket + * @param _gasPriceBid Gas price bid for the L2 retryable ticket + * @param _maxSubmissionCost Max submission cost for the L2 retryable ticket + * @param _value Amount of ETH to send with the message + * @param _extraData Extra data for the callhook on L2Staking + */ + function _sendTokensAndMessageToL2Staking( + uint256 _tokens, + uint256 _maxGas, + uint256 _gasPriceBid, + uint256 _maxSubmissionCost, + uint256 _value, + bytes memory _extraData + ) internal { + IGraphToken grt = graphToken(); + ITokenGateway gateway = graphTokenGateway(); + grt.approve(address(gateway), _tokens); + gateway.outboundTransfer{ value: _value }( + address(grt), + counterpartStakingAddress, + _tokens, + _maxGas, + _gasPriceBid, + abi.encode(_maxSubmissionCost, _extraData) + ); + } +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/staking/L1StakingStorage.sol b/packages/legacy/verified/L1Staking/sources/contracts/staking/L1StakingStorage.sol new file mode 100644 index 000000000..bd7a7f0ee --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/staking/L1StakingStorage.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; +pragma abicoder v2; + +import { IL1GraphTokenLockTransferTool } from "./IL1GraphTokenLockTransferTool.sol"; + +/** + * @title L1StakingV1Storage + * @notice This contract holds all the L1-specific storage variables for the L1Staking contract, version 1 + * @dev When adding new versions, make sure to move the gap to the new version and + * reduce the size of the gap accordingly. + */ +abstract contract L1StakingV1Storage { + /// If an indexer has transferred to L2, this mapping will hold the indexer's address in L2 + mapping(address => address) public indexerTransferredToL2; + /// @dev For locked indexers/delegations, this contract holds the mapping of L1 to L2 addresses + IL1GraphTokenLockTransferTool internal l1GraphTokenLockTransferTool; + /// @dev Storage gap to keep storage slots fixed in future versions + uint256[50] private __gap; +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/staking/Staking.sol b/packages/legacy/verified/L1Staking/sources/contracts/staking/Staking.sol new file mode 100644 index 000000000..589141210 --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/staking/Staking.sol @@ -0,0 +1,1091 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; +pragma abicoder v2; + +import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; +import { ECDSA } from "@openzeppelin/contracts/cryptography/ECDSA.sol"; + +import { Multicall } from "../base/Multicall.sol"; +import { GraphUpgradeable } from "../upgrades/GraphUpgradeable.sol"; +import { TokenUtils } from "../utils/TokenUtils.sol"; +import { IGraphToken } from "../token/IGraphToken.sol"; +import { IStakingBase } from "./IStakingBase.sol"; +import { StakingV4Storage } from "./StakingStorage.sol"; +import { MathUtils } from "./libs/MathUtils.sol"; +import { Stakes } from "./libs/Stakes.sol"; +import { Managed } from "../governance/Managed.sol"; +import { ICuration } from "../curation/ICuration.sol"; +import { IRewardsManager } from "../rewards/IRewardsManager.sol"; +import { StakingExtension } from "./StakingExtension.sol"; +import { LibExponential } from "./libs/Exponential.sol"; + +/** + * @title Base Staking contract + * @dev The Staking contract allows Indexers to Stake on Subgraphs. Indexers Stake by creating + * Allocations on a Subgraph. It also allows Delegators to Delegate towards an Indexer. The + * contract also has the slashing functionality. + * The contract is abstract as the implementation that is deployed depends on each layer: L1Staking on mainnet + * and L2Staking on Arbitrum. + * Note that this contract delegates part of its functionality to a StakingExtension contract. + * This is due to the 24kB contract size limit on Ethereum. + */ +abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, Multicall { + using SafeMath for uint256; + using Stakes for Stakes.Indexer; + + /// @dev 100% in parts per million + uint32 internal constant MAX_PPM = 1000000; + + // -- Events are declared in IStakingBase -- // + + /** + * @notice Delegates the current call to the StakingExtension implementation. + * @dev This function does not return to its internal call site, it will return directly to the + * external caller. + */ + // solhint-disable-next-line payable-fallback, no-complex-fallback + fallback() external { + require(_implementation() != address(0), "only through proxy"); + // solhint-disable-next-line no-inline-assembly + assembly { + // (a) get free memory pointer + let ptr := mload(0x40) + + // (b) get address of the implementation + // CAREFUL here: this only works because extensionImpl is the first variable in this slot + // (otherwise we may have to apply an offset) + let impl := and(sload(extensionImpl.slot), 0xffffffffffffffffffffffffffffffffffffffff) + + // (1) copy incoming call data + calldatacopy(ptr, 0, calldatasize()) + + // (2) forward call to logic contract + let result := delegatecall(gas(), impl, ptr, calldatasize(), 0, 0) + let size := returndatasize() + + // (3) retrieve return data + returndatacopy(ptr, 0, size) + + // (4) forward return data back to caller + switch result + case 0 { + revert(ptr, size) + } + default { + return(ptr, size) + } + } + } + + /** + * @notice Initialize this contract. + * @param _controller Address of the controller that manages this contract + * @param _minimumIndexerStake Minimum amount of tokens that an indexer must stake + * @param _thawingPeriod Number of epochs that tokens get locked after unstaking + * @param _protocolPercentage Percentage of query fees that are burned as protocol fee (in PPM) + * @param _curationPercentage Percentage of query fees that are given to curators (in PPM) + * @param _maxAllocationEpochs The maximum number of epochs that an allocation can be active + * @param _delegationUnbondingPeriod The period in epochs that tokens get locked after undelegating + * @param _delegationRatio The ratio between an indexer's own stake and the delegation they can use + * @param _rebatesParameters Alpha and lambda parameters for rebates function + * @param _extensionImpl Address of the StakingExtension implementation + */ + function initialize( + address _controller, + uint256 _minimumIndexerStake, + uint32 _thawingPeriod, + uint32 _protocolPercentage, + uint32 _curationPercentage, + uint32 _maxAllocationEpochs, + uint32 _delegationUnbondingPeriod, + uint32 _delegationRatio, + RebatesParameters calldata _rebatesParameters, + address _extensionImpl + ) external override onlyImpl { + Managed._initialize(_controller); + + // Settings + + _setMinimumIndexerStake(_minimumIndexerStake); + _setThawingPeriod(_thawingPeriod); + + _setProtocolPercentage(_protocolPercentage); + _setCurationPercentage(_curationPercentage); + + _setMaxAllocationEpochs(_maxAllocationEpochs); + + _setRebateParameters( + _rebatesParameters.alphaNumerator, + _rebatesParameters.alphaDenominator, + _rebatesParameters.lambdaNumerator, + _rebatesParameters.lambdaDenominator + ); + + extensionImpl = _extensionImpl; + + // solhint-disable-next-line avoid-low-level-calls + (bool success, ) = extensionImpl.delegatecall( + abi.encodeWithSelector( + StakingExtension.initialize.selector, + _delegationUnbondingPeriod, + 0, + _delegationRatio, + 0 + ) + ); + require(success, "Extension init failed"); + emit ExtensionImplementationSet(_extensionImpl); + } + + /** + * @notice Set the address of the StakingExtension implementation. + * @dev This function can only be called by the governor. + * @param _extensionImpl Address of the StakingExtension implementation + */ + function setExtensionImpl(address _extensionImpl) external override onlyGovernor { + extensionImpl = _extensionImpl; + emit ExtensionImplementationSet(_extensionImpl); + } + + /** + * @notice Set the address of the counterpart (L1 or L2) staking contract. + * @dev This function can only be called by the governor. + * @param _counterpart Address of the counterpart staking contract in the other chain, without any aliasing. + */ + function setCounterpartStakingAddress(address _counterpart) external override onlyGovernor { + counterpartStakingAddress = _counterpart; + emit ParameterUpdated("counterpartStakingAddress"); + } + + /** + * @notice Set the minimum stake required to be an indexer. + * @param _minimumIndexerStake Minimum indexer stake + */ + function setMinimumIndexerStake(uint256 _minimumIndexerStake) external override onlyGovernor { + _setMinimumIndexerStake(_minimumIndexerStake); + } + + /** + * @notice Set the thawing period for unstaking. + * @param _thawingPeriod Period in blocks to wait for token withdrawals after unstaking + */ + function setThawingPeriod(uint32 _thawingPeriod) external override onlyGovernor { + _setThawingPeriod(_thawingPeriod); + } + + /** + * @notice Set the curation percentage of query fees sent to curators. + * @param _percentage Percentage of query fees sent to curators + */ + function setCurationPercentage(uint32 _percentage) external override onlyGovernor { + _setCurationPercentage(_percentage); + } + + /** + * @notice Set a protocol percentage to burn when collecting query fees. + * @param _percentage Percentage of query fees to burn as protocol fee + */ + function setProtocolPercentage(uint32 _percentage) external override onlyGovernor { + _setProtocolPercentage(_percentage); + } + + /** + * @notice Set the max time allowed for indexers to allocate on a subgraph + * before others are allowed to close the allocation. + * @param _maxAllocationEpochs Allocation duration limit in epochs + */ + function setMaxAllocationEpochs(uint32 _maxAllocationEpochs) external override onlyGovernor { + _setMaxAllocationEpochs(_maxAllocationEpochs); + } + + /** + * @dev Set the rebate parameters. + * @param _alphaNumerator Numerator of `alpha` in the rebates function + * @param _alphaDenominator Denominator of `alpha` in the rebates function + * @param _lambdaNumerator Numerator of `lambda` in the rebates function + * @param _lambdaDenominator Denominator of `lambda` in the rebates function + */ + function setRebateParameters( + uint32 _alphaNumerator, + uint32 _alphaDenominator, + uint32 _lambdaNumerator, + uint32 _lambdaDenominator + ) external override onlyGovernor { + _setRebateParameters( + _alphaNumerator, + _alphaDenominator, + _lambdaNumerator, + _lambdaDenominator + ); + } + + /** + * @notice Set an address as allowed asset holder. + * @param _assetHolder Address of allowed source for state channel funds + * @param _allowed True if asset holder is allowed + */ + function setAssetHolder(address _assetHolder, bool _allowed) external override onlyGovernor { + require(_assetHolder != address(0), "!assetHolder"); + __assetHolders[_assetHolder] = _allowed; + emit AssetHolderUpdate(msg.sender, _assetHolder, _allowed); + } + + /** + * @notice Authorize or unauthorize an address to be an operator for the caller. + * @param _operator Address to authorize or unauthorize + * @param _allowed Whether the operator is authorized or not + */ + function setOperator(address _operator, bool _allowed) external override { + require(_operator != msg.sender, "operator == sender"); + __operatorAuth[msg.sender][_operator] = _allowed; + emit SetOperator(msg.sender, _operator, _allowed); + } + + /** + * @notice Deposit tokens on the indexer's stake. + * The amount staked must be over the minimumIndexerStake. + * @param _tokens Amount of tokens to stake + */ + function stake(uint256 _tokens) external override { + stakeTo(msg.sender, _tokens); + } + + /** + * @notice Unstake tokens from the indexer stake, lock them until the thawing period expires. + * @dev NOTE: The function accepts an amount greater than the currently staked tokens. + * If that happens, it will try to unstake the max amount of tokens it can. + * The reason for this behaviour is to avoid time conditions while the transaction + * is in flight. + * @param _tokens Amount of tokens to unstake + */ + function unstake(uint256 _tokens) external override notPartialPaused { + address indexer = msg.sender; + Stakes.Indexer storage indexerStake = __stakes[indexer]; + + require(indexerStake.tokensStaked > 0, "!stake"); + + // Tokens to lock is capped to the available tokens + uint256 tokensToLock = MathUtils.min(indexerStake.tokensAvailable(), _tokens); + require(tokensToLock > 0, "!stake-avail"); + + // Ensure minimum stake + uint256 newStake = indexerStake.tokensSecureStake().sub(tokensToLock); + require(newStake == 0 || newStake >= __minimumIndexerStake, "!minimumIndexerStake"); + + // Before locking more tokens, withdraw any unlocked ones if possible + uint256 tokensToWithdraw = indexerStake.tokensWithdrawable(); + if (tokensToWithdraw > 0) { + _withdraw(indexer); + } + + // Update the indexer stake locking tokens + indexerStake.lockTokens(tokensToLock, __thawingPeriod); + + emit StakeLocked(indexer, indexerStake.tokensLocked, indexerStake.tokensLockedUntil); + } + + /** + * @notice Withdraw indexer tokens once the thawing period has passed. + */ + function withdraw() external override notPaused { + _withdraw(msg.sender); + } + + /** + * @notice Set the destination where to send rewards for an indexer. + * @param _destination Rewards destination address. If set to zero, rewards will be restaked + */ + function setRewardsDestination(address _destination) external override { + __rewardsDestination[msg.sender] = _destination; + emit SetRewardsDestination(msg.sender, _destination); + } + + /** + * @notice Allocate available tokens to a subgraph deployment. + * @param _subgraphDeploymentID ID of the SubgraphDeployment where tokens will be allocated + * @param _tokens Amount of tokens to allocate + * @param _allocationID The allocation identifier + * @param _metadata IPFS hash for additional information about the allocation + * @param _proof A 65-bytes Ethereum signed message of `keccak256(indexerAddress,allocationID)` + */ + function allocate( + bytes32 _subgraphDeploymentID, + uint256 _tokens, + address _allocationID, + bytes32 _metadata, + bytes calldata _proof + ) external override notPaused { + _allocate(msg.sender, _subgraphDeploymentID, _tokens, _allocationID, _metadata, _proof); + } + + /** + * @notice Allocate available tokens to a subgraph deployment from and indexer's stake. + * The caller must be the indexer or the indexer's operator. + * @param _indexer Indexer address to allocate funds from. + * @param _subgraphDeploymentID ID of the SubgraphDeployment where tokens will be allocated + * @param _tokens Amount of tokens to allocate + * @param _allocationID The allocation identifier + * @param _metadata IPFS hash for additional information about the allocation + * @param _proof A 65-bytes Ethereum signed message of `keccak256(indexerAddress,allocationID)` + */ + function allocateFrom( + address _indexer, + bytes32 _subgraphDeploymentID, + uint256 _tokens, + address _allocationID, + bytes32 _metadata, + bytes calldata _proof + ) external override notPaused { + _allocate(_indexer, _subgraphDeploymentID, _tokens, _allocationID, _metadata, _proof); + } + + /** + * @notice Close an allocation and free the staked tokens. + * To be eligible for rewards a proof of indexing must be presented. + * Presenting a bad proof is subject to slashable condition. + * To opt out of rewards set _poi to 0x0 + * @param _allocationID The allocation identifier + * @param _poi Proof of indexing submitted for the allocated period + */ + function closeAllocation(address _allocationID, bytes32 _poi) external override notPaused { + _closeAllocation(_allocationID, _poi); + } + + /** + * @dev Collect and rebate query fees from state channels to the indexer + * Funds received are only accepted from a valid sender. + * To avoid reverting on the withdrawal from channel flow this function will accept calls with zero tokens. + * We use an exponential rebate formula to calculate the amount of tokens to rebate to the indexer. + * This implementation allows collecting multiple times on the same allocation, keeping track of the + * total amount rebated, the total amount collected and compensating the indexer for the difference. + * @param _tokens Amount of tokens to collect + * @param _allocationID Allocation where the tokens will be assigned + */ + function collect(uint256 _tokens, address _allocationID) external override { + // Allocation identifier validation + require(_allocationID != address(0), "!alloc"); + + // The contract caller must be an authorized asset holder + require(__assetHolders[msg.sender] == true, "!assetHolder"); + + // Allocation must exist + AllocationState allocState = _getAllocationState(_allocationID); + require(allocState != AllocationState.Null, "!collect"); + + Allocation storage alloc = __allocations[_allocationID]; + bytes32 subgraphDeploymentID = alloc.subgraphDeploymentID; + + uint256 queryFees = _tokens; // Tokens collected from the channel + uint256 protocolTax = 0; // Tokens burnt as protocol tax + uint256 curationFees = 0; // Tokens distributed to curators as curation fees + uint256 queryRebates = 0; // Tokens to distribute to indexer + uint256 delegationRewards = 0; // Tokens to distribute to delegators + + // Process query fees only if non-zero amount + if (queryFees > 0) { + // -- Pull tokens from the authorized sender -- + IGraphToken graphToken = graphToken(); + TokenUtils.pullTokens(graphToken, msg.sender, queryFees); + + // -- Collect protocol tax -- + protocolTax = _collectTax(graphToken, queryFees, __protocolPercentage); + queryFees = queryFees.sub(protocolTax); + + // -- Collect curation fees -- + // Only if the subgraph deployment is curated + curationFees = _collectCurationFees( + graphToken, + subgraphDeploymentID, + queryFees, + __curationPercentage + ); + queryFees = queryFees.sub(curationFees); + + // -- Process rebate reward -- + // Using accumulated fees and subtracting previously distributed rebates + // allows for multiple vouchers to be collected while following the rebate formula + alloc.collectedFees = alloc.collectedFees.add(queryFees); + + // No rebates if indexer has no stake or if lambda is zero + uint256 newRebates = (alloc.tokens == 0 || __lambdaNumerator == 0) + ? 0 + : LibExponential.exponentialRebates( + alloc.collectedFees, + alloc.tokens, + __alphaNumerator, + __alphaDenominator, + __lambdaNumerator, + __lambdaDenominator + ); + + // -- Ensure rebates to distribute are within bounds -- + // Indexers can become under or over rebated if rebate parameters (alpha, lambda) + // change between successive collect calls for the same allocation + + // Ensure rebates to distribute are not negative (indexer is over-rebated) + queryRebates = MathUtils.diffOrZero(newRebates, alloc.distributedRebates); + + // Ensure rebates to distribute are not greater than available (indexer is under-rebated) + queryRebates = MathUtils.min(queryRebates, queryFees); + + // -- Burn rebates remanent -- + TokenUtils.burnTokens(graphToken, queryFees.sub(queryRebates)); + + // -- Distribute rebates -- + if (queryRebates > 0) { + alloc.distributedRebates = alloc.distributedRebates.add(queryRebates); + + // -- Collect delegation rewards into the delegation pool -- + delegationRewards = _collectDelegationQueryRewards(alloc.indexer, queryRebates); + queryRebates = queryRebates.sub(delegationRewards); + + // -- Transfer or restake rebates -- + _sendRewards( + graphToken, + queryRebates, + alloc.indexer, + __rewardsDestination[alloc.indexer] == address(0) + ); + } + } + + emit RebateCollected( + msg.sender, + alloc.indexer, + subgraphDeploymentID, + _allocationID, + epochManager().currentEpoch(), + _tokens, + protocolTax, + curationFees, + queryFees, + queryRebates, + delegationRewards + ); + } + + /** + * @notice Return if allocationID is used. + * @param _allocationID Address used as signer by the indexer for an allocation + * @return True if allocationID already used + */ + function isAllocation(address _allocationID) external view override returns (bool) { + return _getAllocationState(_allocationID) != AllocationState.Null; + } + + /** + * @notice Getter that returns if an indexer has any stake. + * @param _indexer Address of the indexer + * @return True if indexer has staked tokens + */ + function hasStake(address _indexer) external view override returns (bool) { + return __stakes[_indexer].tokensStaked > 0; + } + + /** + * @notice Return the allocation by ID. + * @param _allocationID Address used as allocation identifier + * @return Allocation data + */ + function getAllocation(address _allocationID) + external + view + override + returns (Allocation memory) + { + return __allocations[_allocationID]; + } + + /** + * @notice Return the current state of an allocation + * @param _allocationID Allocation identifier + * @return AllocationState enum with the state of the allocation + */ + function getAllocationState(address _allocationID) + external + view + override + returns (AllocationState) + { + return _getAllocationState(_allocationID); + } + + /** + * @notice Return the total amount of tokens allocated to subgraph. + * @param _subgraphDeploymentID Deployment ID for the subgraph + * @return Total tokens allocated to subgraph + */ + function getSubgraphAllocatedTokens(bytes32 _subgraphDeploymentID) + external + view + override + returns (uint256) + { + return __subgraphAllocations[_subgraphDeploymentID]; + } + + /** + * @notice Get the total amount of tokens staked by the indexer. + * @param _indexer Address of the indexer + * @return Amount of tokens staked by the indexer + */ + function getIndexerStakedTokens(address _indexer) external view override returns (uint256) { + return __stakes[_indexer].tokensStaked; + } + + /** + * @notice Deposit tokens on the Indexer stake, on behalf of the Indexer. + * The amount staked must be over the minimumIndexerStake. + * @param _indexer Address of the indexer + * @param _tokens Amount of tokens to stake + */ + function stakeTo(address _indexer, uint256 _tokens) public override notPartialPaused { + require(_tokens > 0, "!tokens"); + + // Transfer tokens to stake from caller to this contract + TokenUtils.pullTokens(graphToken(), msg.sender, _tokens); + + // Stake the transferred tokens + _stake(_indexer, _tokens); + } + + /** + * @notice Set the delegation parameters for the caller. + * @param _indexingRewardCut Percentage of indexing rewards left for the indexer + * @param _queryFeeCut Percentage of query fees left for the indexer + * @param _cooldownBlocks Period that need to pass to update delegation parameters + */ + function setDelegationParameters( + uint32 _indexingRewardCut, + uint32 _queryFeeCut, + uint32 _cooldownBlocks + ) public override { + _setDelegationParameters(msg.sender, _indexingRewardCut, _queryFeeCut, _cooldownBlocks); + } + + /** + * @notice Get the total amount of tokens available to use in allocations. + * This considers the indexer stake and delegated tokens according to delegation ratio + * @param _indexer Address of the indexer + * @return Amount of tokens available to allocate including delegation + */ + function getIndexerCapacity(address _indexer) public view override returns (uint256) { + Stakes.Indexer memory indexerStake = __stakes[_indexer]; + uint256 tokensDelegated = __delegationPools[_indexer].tokens; + + uint256 tokensDelegatedCap = indexerStake.tokensSecureStake().mul( + uint256(__delegationRatio) + ); + uint256 tokensDelegatedCapacity = MathUtils.min(tokensDelegated, tokensDelegatedCap); + + return indexerStake.tokensAvailableWithDelegation(tokensDelegatedCapacity); + } + + /** + * @notice Return true if operator is allowed for indexer. + * @param _operator Address of the operator + * @param _indexer Address of the indexer + * @return True if operator is allowed for indexer, false otherwise + */ + function isOperator(address _operator, address _indexer) public view override returns (bool) { + return __operatorAuth[_indexer][_operator]; + } + + /** + * @dev Internal: Set the minimum indexer stake required. + * @param _minimumIndexerStake Minimum indexer stake + */ + function _setMinimumIndexerStake(uint256 _minimumIndexerStake) private { + require(_minimumIndexerStake > 0, "!minimumIndexerStake"); + __minimumIndexerStake = _minimumIndexerStake; + emit ParameterUpdated("minimumIndexerStake"); + } + + /** + * @dev Internal: Set the thawing period for unstaking. + * @param _thawingPeriod Period in blocks to wait for token withdrawals after unstaking + */ + function _setThawingPeriod(uint32 _thawingPeriod) private { + require(_thawingPeriod > 0, "!thawingPeriod"); + __thawingPeriod = _thawingPeriod; + emit ParameterUpdated("thawingPeriod"); + } + + /** + * @dev Internal: Set the curation percentage of query fees sent to curators. + * @param _percentage Percentage of query fees sent to curators + */ + function _setCurationPercentage(uint32 _percentage) private { + // Must be within 0% to 100% (inclusive) + require(_percentage <= MAX_PPM, ">percentage"); + __curationPercentage = _percentage; + emit ParameterUpdated("curationPercentage"); + } + + /** + * @dev Internal: Set a protocol percentage to burn when collecting query fees. + * @param _percentage Percentage of query fees to burn as protocol fee + */ + function _setProtocolPercentage(uint32 _percentage) private { + // Must be within 0% to 100% (inclusive) + require(_percentage <= MAX_PPM, ">percentage"); + __protocolPercentage = _percentage; + emit ParameterUpdated("protocolPercentage"); + } + + /** + * @dev Internal: Set the max time allowed for indexers stake on allocations. + * @param _maxAllocationEpochs Allocation duration limit in epochs + */ + function _setMaxAllocationEpochs(uint32 _maxAllocationEpochs) private { + __maxAllocationEpochs = _maxAllocationEpochs; + emit ParameterUpdated("maxAllocationEpochs"); + } + + /** + * @dev Set the rebate parameters. + * @param _alphaNumerator Numerator of `alpha` in the rebates function + * @param _alphaDenominator Denominator of `alpha` in the rebates function + * @param _lambdaNumerator Numerator of `lambda` in the rebates function + * @param _lambdaDenominator Denominator of `lambda` in the rebates function + */ + function _setRebateParameters( + uint32 _alphaNumerator, + uint32 _alphaDenominator, + uint32 _lambdaNumerator, + uint32 _lambdaDenominator + ) private { + require(_alphaDenominator > 0, "!alphaDenominator"); + require(_lambdaNumerator > 0, "!lambdaNumerator"); + require(_lambdaDenominator > 0, "!lambdaDenominator"); + __alphaNumerator = _alphaNumerator; + __alphaDenominator = _alphaDenominator; + __lambdaNumerator = _lambdaNumerator; + __lambdaDenominator = _lambdaDenominator; + emit ParameterUpdated("rebateParameters"); + } + + /** + * @dev Set the delegation parameters for a particular indexer. + * @param _indexer Indexer to set delegation parameters + * @param _indexingRewardCut Percentage of indexing rewards left for delegators + * @param _queryFeeCut Percentage of query fees left for delegators + * @param _cooldownBlocks Period that need to pass to update delegation parameters + */ + function _setDelegationParameters( + address _indexer, + uint32 _indexingRewardCut, + uint32 _queryFeeCut, + uint32 _cooldownBlocks + ) internal { + // Incentives must be within bounds + require(_queryFeeCut <= MAX_PPM, ">queryFeeCut"); + require(_indexingRewardCut <= MAX_PPM, ">indexingRewardCut"); + + // Cooldown period set by indexer cannot be below protocol global setting + require(_cooldownBlocks >= __delegationParametersCooldown, "= __minimumIndexerStake, + "!minimumIndexerStake" + ); + + // Deposit tokens into the indexer stake + __stakes[_indexer].deposit(_tokens); + + // Initialize the delegation pool the first time + if (__delegationPools[_indexer].updatedAtBlock == 0) { + _setDelegationParameters(_indexer, MAX_PPM, MAX_PPM, __delegationParametersCooldown); + } + + emit StakeDeposited(_indexer, _tokens); + } + + /** + * @dev Withdraw indexer tokens once the thawing period has passed. + * @param _indexer Address of indexer to withdraw funds from + */ + function _withdraw(address _indexer) private { + // Get tokens available for withdraw and update balance + uint256 tokensToWithdraw = __stakes[_indexer].withdrawTokens(); + require(tokensToWithdraw > 0, "!tokens"); + + // Return tokens to the indexer + TokenUtils.pushTokens(graphToken(), _indexer, tokensToWithdraw); + + emit StakeWithdrawn(_indexer, tokensToWithdraw); + } + + /** + * @dev Allocate available tokens to a subgraph deployment. + * @param _indexer Indexer address to allocate funds from. + * @param _subgraphDeploymentID ID of the SubgraphDeployment where tokens will be allocated + * @param _tokens Amount of tokens to allocate + * @param _allocationID The allocationID will work to identify collected funds related to this allocation + * @param _metadata Metadata related to the allocation + * @param _proof A 65-bytes Ethereum signed message of `keccak256(indexerAddress,allocationID)` + */ + function _allocate( + address _indexer, + bytes32 _subgraphDeploymentID, + uint256 _tokens, + address _allocationID, + bytes32 _metadata, + bytes calldata _proof + ) private { + require(_isAuth(_indexer), "!auth"); + + // Check allocation + require(_allocationID != address(0), "!alloc"); + require(_getAllocationState(_allocationID) == AllocationState.Null, "!null"); + + // Caller must prove that they own the private key for the allocationID address + // The proof is an Ethereum signed message of KECCAK256(indexerAddress,allocationID) + bytes32 messageHash = keccak256(abi.encodePacked(_indexer, _allocationID)); + bytes32 digest = ECDSA.toEthSignedMessageHash(messageHash); + require(ECDSA.recover(digest, _proof) == _allocationID, "!proof"); + + require( + __stakes[_indexer].tokensSecureStake() >= __minimumIndexerStake, + "!minimumIndexerStake" + ); + if (_tokens > 0) { + // Needs to have free capacity not used for other purposes to allocate + require(getIndexerCapacity(_indexer) >= _tokens, "!capacity"); + } + + // Creates an allocation + // Allocation identifiers are not reused + // The assetHolder address can send collected funds to the allocation + Allocation memory alloc = Allocation( + _indexer, + _subgraphDeploymentID, + _tokens, // Tokens allocated + epochManager().currentEpoch(), // createdAtEpoch + 0, // closedAtEpoch + 0, // Initialize collected fees + 0, // Initialize effective allocation (DEPRECATED) + (_tokens > 0) ? _updateRewards(_subgraphDeploymentID) : 0, // Initialize accumulated rewards per stake allocated + 0 // Initialize distributed rebates + ); + __allocations[_allocationID] = alloc; + + // -- Rewards Distribution -- + + // Process non-zero-allocation rewards tracking + if (_tokens > 0) { + // Mark allocated tokens as used + __stakes[_indexer].allocate(alloc.tokens); + + // Track total allocations per subgraph + // Used for rewards calculations + __subgraphAllocations[alloc.subgraphDeploymentID] = __subgraphAllocations[ + alloc.subgraphDeploymentID + ].add(alloc.tokens); + } + + emit AllocationCreated( + _indexer, + _subgraphDeploymentID, + alloc.createdAtEpoch, + alloc.tokens, + _allocationID, + _metadata + ); + } + + /** + * @dev Close an allocation and free the staked tokens. + * @param _allocationID The allocation identifier + * @param _poi Proof of indexing submitted for the allocated period + */ + function _closeAllocation(address _allocationID, bytes32 _poi) private { + // Allocation must exist and be active + AllocationState allocState = _getAllocationState(_allocationID); + require(allocState == AllocationState.Active, "!active"); + + // Get allocation + Allocation memory alloc = __allocations[_allocationID]; + + // Validate that an allocation cannot be closed before one epoch + alloc.closedAtEpoch = epochManager().currentEpoch(); + uint256 epochs = MathUtils.diffOrZero(alloc.closedAtEpoch, alloc.createdAtEpoch); + require(epochs > 0, " 0) { + // Distribute rewards if proof of indexing was presented by the indexer or operator + if (isIndexer && _poi != 0) { + _distributeRewards(_allocationID, alloc.indexer); + } else { + _updateRewards(alloc.subgraphDeploymentID); + } + + // Free allocated tokens from use + __stakes[alloc.indexer].unallocate(alloc.tokens); + + // Track total allocations per subgraph + // Used for rewards calculations + __subgraphAllocations[alloc.subgraphDeploymentID] = __subgraphAllocations[ + alloc.subgraphDeploymentID + ].sub(alloc.tokens); + } + + emit AllocationClosed( + alloc.indexer, + alloc.subgraphDeploymentID, + alloc.closedAtEpoch, + alloc.tokens, + _allocationID, + msg.sender, + _poi, + !isIndexer + ); + } + + /** + * @dev Collect the delegation rewards for query fees. + * This function will assign the collected fees to the delegation pool. + * @param _indexer Indexer to which the tokens to distribute are related + * @param _tokens Total tokens received used to calculate the amount of fees to collect + * @return Amount of delegation rewards + */ + function _collectDelegationQueryRewards(address _indexer, uint256 _tokens) + private + returns (uint256) + { + uint256 delegationRewards = 0; + DelegationPool storage pool = __delegationPools[_indexer]; + if (pool.tokens > 0 && pool.queryFeeCut < MAX_PPM) { + uint256 indexerCut = uint256(pool.queryFeeCut).mul(_tokens).div(MAX_PPM); + delegationRewards = _tokens.sub(indexerCut); + pool.tokens = pool.tokens.add(delegationRewards); + } + return delegationRewards; + } + + /** + * @dev Collect the delegation rewards for indexing. + * This function will assign the collected fees to the delegation pool. + * @param _indexer Indexer to which the tokens to distribute are related + * @param _tokens Total tokens received used to calculate the amount of fees to collect + * @return Amount of delegation rewards + */ + function _collectDelegationIndexingRewards(address _indexer, uint256 _tokens) + private + returns (uint256) + { + uint256 delegationRewards = 0; + DelegationPool storage pool = __delegationPools[_indexer]; + if (pool.tokens > 0 && pool.indexingRewardCut < MAX_PPM) { + uint256 indexerCut = uint256(pool.indexingRewardCut).mul(_tokens).div(MAX_PPM); + delegationRewards = _tokens.sub(indexerCut); + pool.tokens = pool.tokens.add(delegationRewards); + } + return delegationRewards; + } + + /** + * @dev Collect the curation fees for a subgraph deployment from an amount of tokens. + * This function transfer curation fees to the Curation contract by calling Curation.collect + * @param _graphToken Token to collect + * @param _subgraphDeploymentID Subgraph deployment to which the curation fees are related + * @param _tokens Total tokens received used to calculate the amount of fees to collect + * @param _curationPercentage Percentage of tokens to collect as fees + * @return Amount of curation fees + */ + function _collectCurationFees( + IGraphToken _graphToken, + bytes32 _subgraphDeploymentID, + uint256 _tokens, + uint256 _curationPercentage + ) private returns (uint256) { + if (_tokens == 0) { + return 0; + } + + ICuration curation = curation(); + bool isCurationEnabled = _curationPercentage > 0 && address(curation) != address(0); + + if (isCurationEnabled && curation.isCurated(_subgraphDeploymentID)) { + uint256 curationFees = uint256(_curationPercentage).mul(_tokens).div(MAX_PPM); + if (curationFees > 0) { + // Transfer and call collect() + // This function transfer tokens to a trusted protocol contracts + // Then we call collect() to do the transfer bookeeping + rewardsManager().onSubgraphSignalUpdate(_subgraphDeploymentID); + TokenUtils.pushTokens(_graphToken, address(curation), curationFees); + curation.collect(_subgraphDeploymentID, curationFees); + } + return curationFees; + } + return 0; + } + + /** + * @dev Collect tax to burn for an amount of tokens. + * @param _graphToken Token to burn + * @param _tokens Total tokens received used to calculate the amount of tax to collect + * @param _percentage Percentage of tokens to burn as tax + * @return Amount of tax charged + */ + function _collectTax( + IGraphToken _graphToken, + uint256 _tokens, + uint256 _percentage + ) private returns (uint256) { + uint256 tax = uint256(_percentage).mul(_tokens).div(MAX_PPM); + TokenUtils.burnTokens(_graphToken, tax); // Burn tax if any + return tax; + } + + /** + * @dev Triggers an update of rewards due to a change in allocations. + * @param _subgraphDeploymentID Subgraph deployment updated + * @return Accumulated rewards per allocated token for the subgraph deployment + */ + function _updateRewards(bytes32 _subgraphDeploymentID) private returns (uint256) { + IRewardsManager rewardsManager = rewardsManager(); + if (address(rewardsManager) == address(0)) { + return 0; + } + return rewardsManager.onSubgraphAllocationUpdate(_subgraphDeploymentID); + } + + /** + * @dev Assign rewards for the closed allocation to indexer and delegators. + * @param _allocationID Allocation + * @param _indexer Address of the indexer that did the allocation + */ + function _distributeRewards(address _allocationID, address _indexer) private { + IRewardsManager rewardsManager = rewardsManager(); + if (address(rewardsManager) == address(0)) { + return; + } + + // Automatically triggers update of rewards snapshot as allocation will change + // after this call. Take rewards mint tokens for the Staking contract to distribute + // between indexer and delegators + uint256 totalRewards = rewardsManager.takeRewards(_allocationID); + if (totalRewards == 0) { + return; + } + + // Calculate delegation rewards and add them to the delegation pool + uint256 delegationRewards = _collectDelegationIndexingRewards(_indexer, totalRewards); + uint256 indexerRewards = totalRewards.sub(delegationRewards); + + // Send the indexer rewards + _sendRewards( + graphToken(), + indexerRewards, + _indexer, + __rewardsDestination[_indexer] == address(0) + ); + } + + /** + * @dev Send rewards to the appropiate destination. + * @param _graphToken Graph token + * @param _amount Number of rewards tokens + * @param _beneficiary Address of the beneficiary of rewards + * @param _restake Whether to restake or not + */ + function _sendRewards( + IGraphToken _graphToken, + uint256 _amount, + address _beneficiary, + bool _restake + ) private { + if (_amount == 0) return; + + if (_restake) { + // Restake to place fees into the indexer stake + _stake(_beneficiary, _amount); + } else { + // Transfer funds to the beneficiary's designated rewards destination if set + address destination = __rewardsDestination[_beneficiary]; + TokenUtils.pushTokens( + _graphToken, + destination == address(0) ? _beneficiary : destination, + _amount + ); + } + } + + /** + * @dev Check if the caller is authorized to operate on behalf of + * an indexer (i.e. the caller is the indexer or an operator) + * @param _indexer Indexer address + * @return True if the caller is authorized to operate on behalf of the indexer + */ + function _isAuth(address _indexer) private view returns (bool) { + return msg.sender == _indexer || isOperator(msg.sender, _indexer) == true; + } + + /** + * @dev Return the current state of an allocation + * @param _allocationID Allocation identifier + * @return AllocationState enum with the state of the allocation + */ + function _getAllocationState(address _allocationID) private view returns (AllocationState) { + Allocation storage alloc = __allocations[_allocationID]; + + if (alloc.indexer == address(0)) { + return AllocationState.Null; + } + + if (alloc.createdAtEpoch != 0 && alloc.closedAtEpoch == 0) { + return AllocationState.Active; + } + + return AllocationState.Closed; + } +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/staking/StakingExtension.sol b/packages/legacy/verified/L1Staking/sources/contracts/staking/StakingExtension.sol new file mode 100644 index 000000000..0667b89fb --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/staking/StakingExtension.sol @@ -0,0 +1,682 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; +pragma abicoder v2; + +import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; +import { StakingV4Storage } from "./StakingStorage.sol"; +import { IStakingExtension } from "./IStakingExtension.sol"; +import { TokenUtils } from "../utils/TokenUtils.sol"; +import { IGraphToken } from "../token/IGraphToken.sol"; +import { GraphUpgradeable } from "../upgrades/GraphUpgradeable.sol"; +import { Stakes } from "./libs/Stakes.sol"; +import { IStakingData } from "./IStakingData.sol"; +import { MathUtils } from "./libs/MathUtils.sol"; + +/** + * @title StakingExtension contract + * @dev This contract provides the logic to manage delegations and other Staking + * extension features (e.g. storage getters). It is meant to be called through delegatecall from the + * Staking contract, and is only kept separate to keep the Staking contract size + * within limits. + */ +contract StakingExtension is StakingV4Storage, GraphUpgradeable, IStakingExtension { + using SafeMath for uint256; + using Stakes for Stakes.Indexer; + + /// @dev 100% in parts per million + uint32 private constant MAX_PPM = 1000000; + + /** + * @dev Check if the caller is the slasher. + */ + modifier onlySlasher() { + require(__slashers[msg.sender] == true, "!slasher"); + _; + } + + /** + * @notice Initialize the StakingExtension contract + * @dev This function is meant to be delegatecalled from the Staking contract's + * initialize() function, so it uses the same access control check to ensure it is + * being called by the Staking implementation as part of the proxy upgrade process. + * @param _delegationUnbondingPeriod Delegation unbonding period in blocks + * @param _cooldownBlocks Minimum time between changes to delegation parameters, in blocks + * @param _delegationRatio Delegation capacity multiplier (e.g. 10 means 10x the indexer stake) + * @param _delegationTaxPercentage Percentage of delegated tokens to burn as delegation tax, expressed in parts per million + */ + function initialize( + uint32 _delegationUnbondingPeriod, + uint32 _cooldownBlocks, + uint32 _delegationRatio, + uint32 _delegationTaxPercentage + ) external onlyImpl { + _setDelegationUnbondingPeriod(_delegationUnbondingPeriod); + _setDelegationParametersCooldown(_cooldownBlocks); + _setDelegationRatio(_delegationRatio); + _setDelegationTaxPercentage(_delegationTaxPercentage); + } + + /** + * @notice Set a delegation tax percentage to burn when delegated funds are deposited. + * @dev This function is only callable by the governor + * @param _percentage Percentage of delegated tokens to burn as delegation tax, expressed in parts per million + */ + function setDelegationTaxPercentage(uint32 _percentage) external override onlyGovernor { + _setDelegationTaxPercentage(_percentage); + } + + /** + * @notice Set the delegation ratio. + * If set to 10 it means the indexer can use up to 10x the indexer staked amount + * from their delegated tokens + * @dev This function is only callable by the governor + * @param _delegationRatio Delegation capacity multiplier + */ + function setDelegationRatio(uint32 _delegationRatio) external override onlyGovernor { + _setDelegationRatio(_delegationRatio); + } + + /** + * @notice Set the minimum time in blocks an indexer needs to wait to change delegation parameters. + * Indexers can set a custom amount time for their own cooldown, but it must be greater than this. + * @dev This function is only callable by the governor + * @param _blocks Number of blocks to set the delegation parameters cooldown period + */ + function setDelegationParametersCooldown(uint32 _blocks) external override onlyGovernor { + _setDelegationParametersCooldown(_blocks); + } + + /** + * @notice Set the time, in epochs, a Delegator needs to wait to withdraw tokens after undelegating. + * @dev This function is only callable by the governor + * @param _delegationUnbondingPeriod Period in epochs to wait for token withdrawals after undelegating + */ + function setDelegationUnbondingPeriod(uint32 _delegationUnbondingPeriod) + external + override + onlyGovernor + { + _setDelegationUnbondingPeriod(_delegationUnbondingPeriod); + } + + /** + * @notice Set or unset an address as allowed slasher. + * @param _slasher Address of the party allowed to slash indexers + * @param _allowed True if slasher is allowed + */ + function setSlasher(address _slasher, bool _allowed) external override onlyGovernor { + require(_slasher != address(0), "!slasher"); + __slashers[_slasher] = _allowed; + emit SlasherUpdate(msg.sender, _slasher, _allowed); + } + + /** + * @notice Delegate tokens to an indexer. + * @param _indexer Address of the indexer to which tokens are delegated + * @param _tokens Amount of tokens to delegate + * @return Amount of shares issued from the delegation pool + */ + function delegate(address _indexer, uint256 _tokens) + external + override + notPartialPaused + returns (uint256) + { + address delegator = msg.sender; + + // Transfer tokens to delegate to this contract + TokenUtils.pullTokens(graphToken(), delegator, _tokens); + + // Update state + return _delegate(delegator, _indexer, _tokens); + } + + /** + * @notice Undelegate tokens from an indexer. Tokens will be locked for the unbonding period. + * @param _indexer Address of the indexer to which tokens had been delegated + * @param _shares Amount of shares to return and undelegate tokens + * @return Amount of tokens returned for the shares of the delegation pool + */ + function undelegate(address _indexer, uint256 _shares) + external + override + notPartialPaused + returns (uint256) + { + return _undelegate(msg.sender, _indexer, _shares); + } + + /** + * @notice Withdraw undelegated tokens once the unbonding period has passed, and optionally + * re-delegate to a new indexer. + * @param _indexer Withdraw available tokens delegated to indexer + * @param _newIndexer Re-delegate to indexer address if non-zero, withdraw if zero address + */ + function withdrawDelegated(address _indexer, address _newIndexer) + external + override + notPaused + returns (uint256) + { + return _withdrawDelegated(msg.sender, _indexer, _newIndexer); + } + + /** + * @notice Slash the indexer stake. Delegated tokens are not subject to slashing. + * @dev Can only be called by the slasher role. + * @param _indexer Address of indexer to slash + * @param _tokens Amount of tokens to slash from the indexer stake + * @param _reward Amount of reward tokens to send to a beneficiary + * @param _beneficiary Address of a beneficiary to receive a reward for the slashing + */ + function slash( + address _indexer, + uint256 _tokens, + uint256 _reward, + address _beneficiary + ) external override onlySlasher notPartialPaused { + Stakes.Indexer storage indexerStake = __stakes[_indexer]; + + // Only able to slash a non-zero number of tokens + require(_tokens > 0, "!tokens"); + + // Rewards comes from tokens slashed balance + require(_tokens >= _reward, "rewards>slash"); + + // Cannot slash stake of an indexer without any or enough stake + require(indexerStake.tokensStaked > 0, "!stake"); + require(_tokens <= indexerStake.tokensStaked, "slash>stake"); + + // Validate beneficiary of slashed tokens + require(_beneficiary != address(0), "!beneficiary"); + + // Slashing more tokens than freely available (over allocation condition) + // Unlock locked tokens to avoid the indexer to withdraw them + if (_tokens > indexerStake.tokensAvailable() && indexerStake.tokensLocked > 0) { + uint256 tokensOverAllocated = _tokens.sub(indexerStake.tokensAvailable()); + uint256 tokensToUnlock = MathUtils.min(tokensOverAllocated, indexerStake.tokensLocked); + indexerStake.unlockTokens(tokensToUnlock); + } + + // Remove tokens to slash from the stake + indexerStake.release(_tokens); + + // -- Interactions -- + + IGraphToken graphToken = graphToken(); + + // Set apart the reward for the beneficiary and burn remaining slashed stake + TokenUtils.burnTokens(graphToken, _tokens.sub(_reward)); + + // Give the beneficiary a reward for slashing + TokenUtils.pushTokens(graphToken, _beneficiary, _reward); + + emit StakeSlashed(_indexer, _tokens, _reward, _beneficiary); + } + + /** + * @notice Return the delegation from a delegator to an indexer. + * @param _indexer Address of the indexer where funds have been delegated + * @param _delegator Address of the delegator + * @return Delegation data + */ + function getDelegation(address _indexer, address _delegator) + external + view + override + returns (Delegation memory) + { + return __delegationPools[_indexer].delegators[_delegator]; + } + + /** + * @notice Getter for the delegationRatio, i.e. the delegation capacity multiplier: + * If delegation ratio is 100, and an Indexer has staked 5 GRT, + * then they can use up to 500 GRT from the delegated stake + * @return Delegation ratio + */ + function delegationRatio() external view override returns (uint32) { + return __delegationRatio; + } + + /** + * @notice Getter for delegationParametersCooldown: + * Minimum time in blocks an indexer needs to wait to change delegation parameters + * @return Delegation parameters cooldown in blocks + */ + function delegationParametersCooldown() external view override returns (uint32) { + return __delegationParametersCooldown; + } + + /** + * @notice Getter for delegationUnbondingPeriod: + * Time in epochs a delegator needs to wait to withdraw delegated stake + * @return Delegation unbonding period in epochs + */ + function delegationUnbondingPeriod() external view override returns (uint32) { + return __delegationUnbondingPeriod; + } + + /** + * @notice Getter for delegationTaxPercentage: + * Percentage of tokens to tax a delegation deposit, expressed in parts per million + * @return Delegation tax percentage in parts per million + */ + function delegationTaxPercentage() external view override returns (uint32) { + return __delegationTaxPercentage; + } + + /** + * @notice Getter for delegationPools[_indexer]: + * gets the delegation pool structure for a particular indexer. + * @param _indexer Address of the indexer for which to query the delegation pool + * @return Delegation pool as a DelegationPoolReturn struct + */ + function delegationPools(address _indexer) + external + view + override + returns (DelegationPoolReturn memory) + { + DelegationPool storage pool = __delegationPools[_indexer]; + return + DelegationPoolReturn( + pool.cooldownBlocks, // Blocks to wait before updating parameters + pool.indexingRewardCut, // in PPM + pool.queryFeeCut, // in PPM + pool.updatedAtBlock, // Block when the pool was last updated + pool.tokens, // Total tokens as pool reserves + pool.shares // Total shares minted in the pool + ); + } + + /** + * @notice Getter for rewardsDestination[_indexer]: + * returns the address where the indexer's rewards are sent. + * @param _indexer The indexer address for which to query the rewards destination + * @return The address where the indexer's rewards are sent, zero if none is set in which case rewards are re-staked + */ + function rewardsDestination(address _indexer) external view override returns (address) { + return __rewardsDestination[_indexer]; + } + + /** + * @notice Getter for assetHolders[_maybeAssetHolder]: + * returns true if the address is an asset holder, i.e. an entity that can collect + * query fees into the Staking contract. + * @param _maybeAssetHolder The address that may or may not be an asset holder + * @return True if the address is an asset holder + */ + function assetHolders(address _maybeAssetHolder) external view override returns (bool) { + return __assetHolders[_maybeAssetHolder]; + } + + /** + * @notice Getter for operatorAuth[_indexer][_maybeOperator]: + * returns true if the operator is authorized to operate on behalf of the indexer. + * @param _indexer The indexer address for which to query authorization + * @param _maybeOperator The address that may or may not be an operator + * @return True if the operator is authorized to operate on behalf of the indexer + */ + function operatorAuth(address _indexer, address _maybeOperator) + external + view + override + returns (bool) + { + return __operatorAuth[_indexer][_maybeOperator]; + } + + /** + * @notice Getter for subgraphAllocations[_subgraphDeploymentId]: + * returns the amount of tokens allocated to a subgraph deployment. + * @param _subgraphDeploymentId The subgraph deployment for which to query the allocations + * @return The amount of tokens allocated to the subgraph deployment + */ + function subgraphAllocations(bytes32 _subgraphDeploymentId) + external + view + override + returns (uint256) + { + return __subgraphAllocations[_subgraphDeploymentId]; + } + + /** + * @notice Getter for slashers[_maybeSlasher]: + * returns true if the address is a slasher, i.e. an entity that can slash indexers + * @param _maybeSlasher Address for which to check the slasher role + * @return True if the address is a slasher + */ + function slashers(address _maybeSlasher) external view override returns (bool) { + return __slashers[_maybeSlasher]; + } + + /** + * @notice Getter for minimumIndexerStake: the minimum + * amount of GRT that an indexer needs to stake. + * @return Minimum indexer stake in GRT + */ + function minimumIndexerStake() external view override returns (uint256) { + return __minimumIndexerStake; + } + + /** + * @notice Getter for thawingPeriod: the time in blocks an + * indexer needs to wait to unstake tokens. + * @return Thawing period in blocks + */ + function thawingPeriod() external view override returns (uint32) { + return __thawingPeriod; + } + + /** + * @notice Getter for curationPercentage: the percentage of + * query fees that are distributed to curators. + * @return Curation percentage in parts per million + */ + function curationPercentage() external view override returns (uint32) { + return __curationPercentage; + } + + /** + * @notice Getter for protocolPercentage: the percentage of + * query fees that are burned as protocol fees. + * @return Protocol percentage in parts per million + */ + function protocolPercentage() external view override returns (uint32) { + return __protocolPercentage; + } + + /** + * @notice Getter for maxAllocationEpochs: the maximum time in epochs + * that an allocation can be open before anyone is allowed to close it. This + * also caps the effective allocation when sending the allocation's query fees + * to the rebate pool. + * @return Maximum allocation period in epochs + */ + function maxAllocationEpochs() external view override returns (uint32) { + return __maxAllocationEpochs; + } + + /** + * @notice Getter for the numerator of the rebates alpha parameter + * @return Alpha numerator + */ + function alphaNumerator() external view override returns (uint32) { + return __alphaNumerator; + } + + /** + * @notice Getter for the denominator of the rebates alpha parameter + * @return Alpha denominator + */ + function alphaDenominator() external view override returns (uint32) { + return __alphaDenominator; + } + + /** + * @notice Getter for the numerator of the rebates lambda parameter + * @return Lambda numerator + */ + function lambdaNumerator() external view override returns (uint32) { + return __lambdaNumerator; + } + + /** + * @notice Getter for the denominator of the rebates lambda parameter + * @return Lambda denominator + */ + function lambdaDenominator() external view override returns (uint32) { + return __lambdaDenominator; + } + + /** + * @notice Getter for stakes[_indexer]: + * gets the stake information for an indexer as a Stakes.Indexer struct. + * @param _indexer Indexer address for which to query the stake information + * @return Stake information for the specified indexer, as a Stakes.Indexer struct + */ + function stakes(address _indexer) external view override returns (Stakes.Indexer memory) { + return __stakes[_indexer]; + } + + /** + * @notice Getter for allocations[_allocationID]: + * gets an allocation's information as an IStakingData.Allocation struct. + * @param _allocationID Allocation ID for which to query the allocation information + * @return The specified allocation, as an IStakingData.Allocation struct + */ + function allocations(address _allocationID) + external + view + override + returns (IStakingData.Allocation memory) + { + return __allocations[_allocationID]; + } + + /** + * @notice Return whether the delegator has delegated to the indexer. + * @param _indexer Address of the indexer where funds have been delegated + * @param _delegator Address of the delegator + * @return True if delegator has tokens delegated to the indexer + */ + function isDelegator(address _indexer, address _delegator) public view override returns (bool) { + return __delegationPools[_indexer].delegators[_delegator].shares > 0; + } + + /** + * @notice Returns amount of delegated tokens ready to be withdrawn after unbonding period. + * @param _delegation Delegation of tokens from delegator to indexer + * @return Amount of tokens to withdraw + */ + function getWithdraweableDelegatedTokens(Delegation memory _delegation) + public + view + override + returns (uint256) + { + // There must be locked tokens and period passed + uint256 currentEpoch = epochManager().currentEpoch(); + if (_delegation.tokensLockedUntil > 0 && currentEpoch >= _delegation.tokensLockedUntil) { + return _delegation.tokensLocked; + } + return 0; + } + + /** + * @dev Internal: Set a delegation tax percentage to burn when delegated funds are deposited. + * @param _percentage Percentage of delegated tokens to burn as delegation tax + */ + function _setDelegationTaxPercentage(uint32 _percentage) private { + // Must be within 0% to 100% (inclusive) + require(_percentage <= MAX_PPM, ">percentage"); + __delegationTaxPercentage = _percentage; + emit ParameterUpdated("delegationTaxPercentage"); + } + + /** + * @dev Internal: Set the delegation ratio. + * If set to 10 it means the indexer can use up to 10x the indexer staked amount + * from their delegated tokens + * @param _delegationRatio Delegation capacity multiplier + */ + function _setDelegationRatio(uint32 _delegationRatio) private { + __delegationRatio = _delegationRatio; + emit ParameterUpdated("delegationRatio"); + } + + /** + * @dev Internal: Set the time in blocks an indexer needs to wait to change delegation parameters. + * @param _blocks Number of blocks to set the delegation parameters cooldown period + */ + function _setDelegationParametersCooldown(uint32 _blocks) private { + __delegationParametersCooldown = _blocks; + emit ParameterUpdated("delegationParametersCooldown"); + } + + /** + * @dev Internal: Set the period for undelegation of stake from indexer. + * @param _delegationUnbondingPeriod Period in epochs to wait for token withdrawals after undelegating + */ + function _setDelegationUnbondingPeriod(uint32 _delegationUnbondingPeriod) private { + require(_delegationUnbondingPeriod > 0, "!delegationUnbondingPeriod"); + __delegationUnbondingPeriod = _delegationUnbondingPeriod; + emit ParameterUpdated("delegationUnbondingPeriod"); + } + + /** + * @dev Delegate tokens to an indexer. + * @param _delegator Address of the delegator + * @param _indexer Address of the indexer to delegate tokens to + * @param _tokens Amount of tokens to delegate + * @return Amount of shares issued of the delegation pool + */ + function _delegate( + address _delegator, + address _indexer, + uint256 _tokens + ) private returns (uint256) { + // Only delegate a non-zero amount of tokens + require(_tokens > 0, "!tokens"); + // Only delegate to non-empty address + require(_indexer != address(0), "!indexer"); + // Only delegate to staked indexer + require(__stakes[_indexer].tokensStaked > 0, "!stake"); + + // Get the delegation pool of the indexer + DelegationPool storage pool = __delegationPools[_indexer]; + Delegation storage delegation = pool.delegators[_delegator]; + + // Collect delegation tax + uint256 delegationTax = _collectTax(graphToken(), _tokens, __delegationTaxPercentage); + uint256 delegatedTokens = _tokens.sub(delegationTax); + + // Calculate shares to issue + uint256 shares = (pool.tokens == 0) + ? delegatedTokens + : delegatedTokens.mul(pool.shares).div(pool.tokens); + require(shares > 0, "!shares"); + + // Update the delegation pool + pool.tokens = pool.tokens.add(delegatedTokens); + pool.shares = pool.shares.add(shares); + + // Update the individual delegation + delegation.shares = delegation.shares.add(shares); + + emit StakeDelegated(_indexer, _delegator, delegatedTokens, shares); + + return shares; + } + + /** + * @dev Undelegate tokens from an indexer. + * @param _delegator Address of the delegator + * @param _indexer Address of the indexer where tokens had been delegated + * @param _shares Amount of shares to return and undelegate tokens + * @return Amount of tokens returned for the shares of the delegation pool + */ + function _undelegate( + address _delegator, + address _indexer, + uint256 _shares + ) private returns (uint256) { + // Can only undelegate a non-zero amount of shares + require(_shares > 0, "!shares"); + + // Get the delegation pool of the indexer + DelegationPool storage pool = __delegationPools[_indexer]; + Delegation storage delegation = pool.delegators[_delegator]; + + // Delegator need to have enough shares in the pool to undelegate + require(delegation.shares >= _shares, "!shares-avail"); + + // Withdraw tokens if available + if (getWithdraweableDelegatedTokens(delegation) > 0) { + _withdrawDelegated(_delegator, _indexer, address(0)); + } + + // Calculate tokens to get in exchange for the shares + uint256 tokens = _shares.mul(pool.tokens).div(pool.shares); + + // Update the delegation pool + pool.tokens = pool.tokens.sub(tokens); + pool.shares = pool.shares.sub(_shares); + + // Update the delegation + delegation.shares = delegation.shares.sub(_shares); + delegation.tokensLocked = delegation.tokensLocked.add(tokens); + delegation.tokensLockedUntil = epochManager().currentEpoch().add( + __delegationUnbondingPeriod + ); + + emit StakeDelegatedLocked( + _indexer, + _delegator, + tokens, + _shares, + delegation.tokensLockedUntil + ); + + return tokens; + } + + /** + * @dev Withdraw delegated tokens once the unbonding period has passed. + * @param _delegator Delegator that is withdrawing tokens + * @param _indexer Withdraw available tokens delegated to indexer + * @param _delegateToIndexer Re-delegate to indexer address if non-zero, withdraw if zero address + * @return Amount of tokens withdrawn or re-delegated + */ + function _withdrawDelegated( + address _delegator, + address _indexer, + address _delegateToIndexer + ) private returns (uint256) { + // Get the delegation pool of the indexer + DelegationPool storage pool = __delegationPools[_indexer]; + Delegation storage delegation = pool.delegators[_delegator]; + + // Validation + uint256 tokensToWithdraw = getWithdraweableDelegatedTokens(delegation); + require(tokensToWithdraw > 0, "!tokens"); + + // Reset lock + delegation.tokensLocked = 0; + delegation.tokensLockedUntil = 0; + + emit StakeDelegatedWithdrawn(_indexer, _delegator, tokensToWithdraw); + + // -- Interactions -- + + if (_delegateToIndexer != address(0)) { + // Re-delegate tokens to a new indexer + _delegate(_delegator, _delegateToIndexer, tokensToWithdraw); + } else { + // Return tokens to the delegator + TokenUtils.pushTokens(graphToken(), _delegator, tokensToWithdraw); + } + + return tokensToWithdraw; + } + + /** + * @dev Collect tax to burn for an amount of tokens. + * @param _graphToken Token to burn + * @param _tokens Total tokens received used to calculate the amount of tax to collect + * @param _percentage Percentage of tokens to burn as tax + * @return Amount of tax charged + */ + function _collectTax( + IGraphToken _graphToken, + uint256 _tokens, + uint256 _percentage + ) private returns (uint256) { + uint256 tax = uint256(_percentage).mul(_tokens).div(MAX_PPM); + TokenUtils.burnTokens(_graphToken, tax); // Burn tax if any + return tax; + } +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/staking/StakingStorage.sol b/packages/legacy/verified/L1Staking/sources/contracts/staking/StakingStorage.sol new file mode 100644 index 000000000..88c149e22 --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/staking/StakingStorage.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import { Managed } from "../governance/Managed.sol"; + +import { IStakingData } from "./IStakingData.sol"; +import { Stakes } from "./libs/Stakes.sol"; + +/** + * @title StakingV1Storage + * @notice This contract holds all the storage variables for the Staking contract, version 1 + * @dev Note that we use a double underscore prefix for variable names; this prefix identifies + * variables that used to be public but are now internal, getters can be found on StakingExtension.sol. + */ +// solhint-disable-next-line max-states-count +contract StakingV1Storage is Managed { + // -- Staking -- + + /// @dev Minimum amount of tokens an indexer needs to stake + uint256 internal __minimumIndexerStake; + + /// @dev Time in blocks to unstake + uint32 internal __thawingPeriod; // in blocks + + /// @dev Percentage of fees going to curators + /// Parts per million. (Allows for 4 decimal points, 999,999 = 99.9999%) + uint32 internal __curationPercentage; + + /// @dev Percentage of fees burned as protocol fee + /// Parts per million. (Allows for 4 decimal points, 999,999 = 99.9999%) + uint32 internal __protocolPercentage; + + /// @dev Period for allocation to be finalized + uint32 private __DEPRECATED_channelDisputeEpochs; + + /// @dev Maximum allocation time + uint32 internal __maxAllocationEpochs; + + /// @dev Rebate alpha numerator + // Originally used for Cobb-Douglas rebates, now used for exponential rebates + uint32 internal __alphaNumerator; + + /// @dev Rebate alpha denominator + // Originally used for Cobb-Douglas rebates, now used for exponential rebates + uint32 internal __alphaDenominator; + + /// @dev Indexer stakes : indexer => Stake + mapping(address => Stakes.Indexer) internal __stakes; + + /// @dev Allocations : allocationID => Allocation + mapping(address => IStakingData.Allocation) internal __allocations; + + /// @dev Subgraph Allocations: subgraphDeploymentID => tokens + mapping(bytes32 => uint256) internal __subgraphAllocations; + + // Rebate pools : epoch => Pool + mapping(uint256 => uint256) private __DEPRECATED_rebates; + + // -- Slashing -- + + /// @dev List of addresses allowed to slash stakes + mapping(address => bool) internal __slashers; + + // -- Delegation -- + + /// @dev Set the delegation capacity multiplier defined by the delegation ratio + /// If delegation ratio is 100, and an Indexer has staked 5 GRT, + /// then they can use up to 500 GRT from the delegated stake + uint32 internal __delegationRatio; + + /// @dev Time in blocks an indexer needs to wait to change delegation parameters + uint32 internal __delegationParametersCooldown; + + /// @dev Time in epochs a delegator needs to wait to withdraw delegated stake + uint32 internal __delegationUnbondingPeriod; // in epochs + + /// @dev Percentage of tokens to tax a delegation deposit + /// Parts per million. (Allows for 4 decimal points, 999,999 = 99.9999%) + uint32 internal __delegationTaxPercentage; + + /// @dev Delegation pools : indexer => DelegationPool + mapping(address => IStakingData.DelegationPool) internal __delegationPools; + + // -- Operators -- + + /// @dev Operator auth : indexer => operator => is authorized + mapping(address => mapping(address => bool)) internal __operatorAuth; + + // -- Asset Holders -- + + /// @dev Allowed AssetHolders that can collect query fees: assetHolder => is allowed + mapping(address => bool) internal __assetHolders; +} + +/** + * @title StakingV2Storage + * @notice This contract holds all the storage variables for the Staking contract, version 2 + * @dev Note that we use a double underscore prefix for variable names; this prefix identifies + * variables that used to be public but are now internal, getters can be found on StakingExtension.sol. + */ +contract StakingV2Storage is StakingV1Storage { + /// @dev Destination of accrued rewards : beneficiary => rewards destination + mapping(address => address) internal __rewardsDestination; +} + +/** + * @title StakingV3Storage + * @notice This contract holds all the storage variables for the base Staking contract, version 3. + */ +contract StakingV3Storage is StakingV2Storage { + /// @dev Address of the counterpart Staking contract on L1/L2 + address internal counterpartStakingAddress; + /// @dev Address of the StakingExtension implementation + address internal extensionImpl; +} + +/** + * @title StakingV4Storage + * @notice This contract holds all the storage variables for the base Staking contract, version 4. + * @dev Note that it includes a storage gap - if adding future versions, make sure to move the gap + * to the new version and reduce the size of the gap accordingly. + */ +contract StakingV4Storage is StakingV3Storage { + // Additional rebate parameters for exponential rebates + uint32 internal __lambdaNumerator; + uint32 internal __lambdaDenominator; + + /// @dev Gap to allow adding variables in future upgrades (since L1Staking and L2Staking can have their own storage as well) + uint256[50] private __gap; +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/staking/libs/Exponential.sol b/packages/legacy/verified/L1Staking/sources/contracts/staking/libs/Exponential.sol new file mode 100644 index 000000000..303b6790b --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/staking/libs/Exponential.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import { LibFixedMath } from "./LibFixedMath.sol"; + +/** + * @title LibExponential library + * @notice A library to compute query fee rebates using an exponential formula + */ +library LibExponential { + /// @dev Maximum value of the exponent for which to compute the exponential before clamping to zero. + uint32 private constant MAX_EXPONENT = 15; + + /// @dev The exponential formula used to compute fee-based rewards for + /// staking pools in a given epoch. This function does not perform + /// bounds checking on the inputs, but the following conditions + /// need to be true: + /// 0 <= alphaNumerator / alphaDenominator <= 1 + /// 0 < lambdaNumerator / lambdaDenominator + /// The exponential rebates function has the form: + /// `(1 - alpha * exp ^ (-lambda * stake / fees)) * fees` + /// @param fees Fees generated by indexer in the staking pool. + /// @param stake Stake attributed to the indexer in the staking pool. + /// @param alphaNumerator Numerator of `alpha` in the rebates function. + /// @param alphaDenominator Denominator of `alpha` in the rebates function. + /// @param lambdaNumerator Numerator of `lambda` in the rebates function. + /// @param lambdaDenominator Denominator of `lambda` in the rebates function. + /// @return rewards Rewards owed to the staking pool. + function exponentialRebates( + uint256 fees, + uint256 stake, + uint32 alphaNumerator, + uint32 alphaDenominator, + uint32 lambdaNumerator, + uint32 lambdaDenominator + ) public pure returns (uint256) { + // If alpha is zero indexer gets 100% fees rebate + int256 alpha = LibFixedMath.toFixed(int32(alphaNumerator), int32(alphaDenominator)); + if (alpha == 0) { + return fees; + } + + // No rebates if no fees... + if (fees == 0) { + return 0; + } + + // Award all fees as rebate if the exponent is too large + int256 lambda = LibFixedMath.toFixed(int32(lambdaNumerator), int32(lambdaDenominator)); + int256 exponent = LibFixedMath.mulDiv(lambda, int256(stake), int256(fees)); + if (LibFixedMath.toInteger(exponent) > MAX_EXPONENT) { + return fees; + } + + // Compute `1 - alpha * exp ^(-exponent)` + int256 factor = LibFixedMath.sub( + LibFixedMath.one(), + LibFixedMath.mul(alpha, LibFixedMath.exp(-exponent)) + ); + + // Weight the fees by the factor + return LibFixedMath.uintMul(factor, fees); + } +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/staking/libs/LibFixedMath.sol b/packages/legacy/verified/L1Staking/sources/contracts/staking/libs/LibFixedMath.sol new file mode 100644 index 000000000..b8e5e39a5 --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/staking/libs/LibFixedMath.sol @@ -0,0 +1,408 @@ +/* + + Copyright 2017 Bprotocol Foundation, 2019 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.7.6; + +// solhint-disable indent +/// @dev Signed, fixed-point, 127-bit precision math library. +library LibFixedMath { + // 1 + int256 private constant FIXED_1 = + int256(0x0000000000000000000000000000000080000000000000000000000000000000); + // 2**255 + int256 private constant MIN_FIXED_VAL = + int256(0x8000000000000000000000000000000000000000000000000000000000000000); + // 1^2 (in fixed-point) + int256 private constant FIXED_1_SQUARED = + int256(0x4000000000000000000000000000000000000000000000000000000000000000); + // 1 + int256 private constant LN_MAX_VAL = FIXED_1; + // e ^ -63.875 + int256 private constant LN_MIN_VAL = + int256(0x0000000000000000000000000000000000000000000000000000000733048c5a); + // 0 + int256 private constant EXP_MAX_VAL = 0; + // -63.875 + int256 private constant EXP_MIN_VAL = + -int256(0x0000000000000000000000000000001ff0000000000000000000000000000000); + + /// @dev Get one as a fixed-point number. + function one() internal pure returns (int256 f) { + f = FIXED_1; + } + + /// @dev Returns the addition of two fixed point numbers, reverting on overflow. + function add(int256 a, int256 b) internal pure returns (int256 c) { + c = _add(a, b); + } + + /// @dev Returns the addition of two fixed point numbers, reverting on overflow. + function sub(int256 a, int256 b) internal pure returns (int256 c) { + if (b == MIN_FIXED_VAL) { + revert("out-of-bounds"); + } + c = _add(a, -b); + } + + /// @dev Returns the multiplication of two fixed point numbers, reverting on overflow. + function mul(int256 a, int256 b) internal pure returns (int256 c) { + c = _mul(a, b) / FIXED_1; + } + + /// @dev Returns the division of two fixed point numbers. + function div(int256 a, int256 b) internal pure returns (int256 c) { + c = _div(_mul(a, FIXED_1), b); + } + + /// @dev Performs (a * n) / d, without scaling for precision. + function mulDiv( + int256 a, + int256 n, + int256 d + ) internal pure returns (int256 c) { + c = _div(_mul(a, n), d); + } + + /// @dev Returns the unsigned integer result of multiplying a fixed-point + /// number with an integer, reverting if the multiplication overflows. + /// Negative results are clamped to zero. + function uintMul(int256 f, uint256 u) internal pure returns (uint256) { + if (int256(u) < int256(0)) { + revert("out-of-bounds"); + } + int256 c = _mul(f, int256(u)); + if (c <= 0) { + return 0; + } + return uint256(uint256(c) >> 127); + } + + /// @dev Returns the absolute value of a fixed point number. + function abs(int256 f) internal pure returns (int256 c) { + if (f == MIN_FIXED_VAL) { + revert("out-of-bounds"); + } + if (f >= 0) { + c = f; + } else { + c = -f; + } + } + + /// @dev Returns 1 / `x`, where `x` is a fixed-point number. + function invert(int256 f) internal pure returns (int256 c) { + c = _div(FIXED_1_SQUARED, f); + } + + /// @dev Convert signed `n` / 1 to a fixed-point number. + function toFixed(int256 n) internal pure returns (int256 f) { + f = _mul(n, FIXED_1); + } + + /// @dev Convert signed `n` / `d` to a fixed-point number. + function toFixed(int256 n, int256 d) internal pure returns (int256 f) { + f = _div(_mul(n, FIXED_1), d); + } + + /// @dev Convert unsigned `n` / 1 to a fixed-point number. + /// Reverts if `n` is too large to fit in a fixed-point number. + function toFixed(uint256 n) internal pure returns (int256 f) { + if (int256(n) < int256(0)) { + revert("out-of-bounds"); + } + f = _mul(int256(n), FIXED_1); + } + + /// @dev Convert unsigned `n` / `d` to a fixed-point number. + /// Reverts if `n` / `d` is too large to fit in a fixed-point number. + function toFixed(uint256 n, uint256 d) internal pure returns (int256 f) { + if (int256(n) < int256(0)) { + revert("out-of-bounds"); + } + if (int256(d) < int256(0)) { + revert("out-of-bounds"); + } + f = _div(_mul(int256(n), FIXED_1), int256(d)); + } + + /// @dev Convert a fixed-point number to an integer. + function toInteger(int256 f) internal pure returns (int256 n) { + return f / FIXED_1; + } + + /// @dev Get the natural logarithm of a fixed-point number 0 < `x` <= LN_MAX_VAL + function ln(int256 x) internal pure returns (int256 r) { + if (x > LN_MAX_VAL) { + revert("out-of-bounds"); + } + if (x <= 0) { + revert("too-small"); + } + if (x == FIXED_1) { + return 0; + } + if (x <= LN_MIN_VAL) { + return EXP_MIN_VAL; + } + + int256 y; + int256 z; + int256 w; + + // Rewrite the input as a quotient of negative natural exponents and a single residual q, such that 1 < q < 2 + // For example: log(0.3) = log(e^-1 * e^-0.25 * 1.0471028872385522) + // = 1 - 0.25 - log(1 + 0.0471028872385522) + // e ^ -32 + if (x <= int256(0x00000000000000000000000000000000000000000001c8464f76164760000000)) { + r -= int256(0x0000000000000000000000000000001000000000000000000000000000000000); // - 32 + x = + (x * FIXED_1) / + int256(0x00000000000000000000000000000000000000000001c8464f76164760000000); // / e ^ -32 + } + // e ^ -16 + if (x <= int256(0x00000000000000000000000000000000000000f1aaddd7742e90000000000000)) { + r -= int256(0x0000000000000000000000000000000800000000000000000000000000000000); // - 16 + x = + (x * FIXED_1) / + int256(0x00000000000000000000000000000000000000f1aaddd7742e90000000000000); // / e ^ -16 + } + // e ^ -8 + if (x <= int256(0x00000000000000000000000000000000000afe10820813d78000000000000000)) { + r -= int256(0x0000000000000000000000000000000400000000000000000000000000000000); // - 8 + x = + (x * FIXED_1) / + int256(0x00000000000000000000000000000000000afe10820813d78000000000000000); // / e ^ -8 + } + // e ^ -4 + if (x <= int256(0x0000000000000000000000000000000002582ab704279ec00000000000000000)) { + r -= int256(0x0000000000000000000000000000000200000000000000000000000000000000); // - 4 + x = + (x * FIXED_1) / + int256(0x0000000000000000000000000000000002582ab704279ec00000000000000000); // / e ^ -4 + } + // e ^ -2 + if (x <= int256(0x000000000000000000000000000000001152aaa3bf81cc000000000000000000)) { + r -= int256(0x0000000000000000000000000000000100000000000000000000000000000000); // - 2 + x = + (x * FIXED_1) / + int256(0x000000000000000000000000000000001152aaa3bf81cc000000000000000000); // / e ^ -2 + } + // e ^ -1 + if (x <= int256(0x000000000000000000000000000000002f16ac6c59de70000000000000000000)) { + r -= int256(0x0000000000000000000000000000000080000000000000000000000000000000); // - 1 + x = + (x * FIXED_1) / + int256(0x000000000000000000000000000000002f16ac6c59de70000000000000000000); // / e ^ -1 + } + // e ^ -0.5 + if (x <= int256(0x000000000000000000000000000000004da2cbf1be5828000000000000000000)) { + r -= int256(0x0000000000000000000000000000000040000000000000000000000000000000); // - 0.5 + x = + (x * FIXED_1) / + int256(0x000000000000000000000000000000004da2cbf1be5828000000000000000000); // / e ^ -0.5 + } + // e ^ -0.25 + if (x <= int256(0x0000000000000000000000000000000063afbe7ab2082c000000000000000000)) { + r -= int256(0x0000000000000000000000000000000020000000000000000000000000000000); // - 0.25 + x = + (x * FIXED_1) / + int256(0x0000000000000000000000000000000063afbe7ab2082c000000000000000000); // / e ^ -0.25 + } + // e ^ -0.125 + if (x <= int256(0x0000000000000000000000000000000070f5a893b608861e1f58934f97aea57d)) { + r -= int256(0x0000000000000000000000000000000010000000000000000000000000000000); // - 0.125 + x = + (x * FIXED_1) / + int256(0x0000000000000000000000000000000070f5a893b608861e1f58934f97aea57d); // / e ^ -0.125 + } + // `x` is now our residual in the range of 1 <= x <= 2 (or close enough). + + // Add the taylor series for log(1 + z), where z = x - 1 + z = y = x - FIXED_1; + w = (y * y) / FIXED_1; + r += (z * (0x100000000000000000000000000000000 - y)) / 0x100000000000000000000000000000000; + z = (z * w) / FIXED_1; // add y^01 / 01 - y^02 / 02 + r += (z * (0x0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - y)) / 0x200000000000000000000000000000000; + z = (z * w) / FIXED_1; // add y^03 / 03 - y^04 / 04 + r += (z * (0x099999999999999999999999999999999 - y)) / 0x300000000000000000000000000000000; + z = (z * w) / FIXED_1; // add y^05 / 05 - y^06 / 06 + r += (z * (0x092492492492492492492492492492492 - y)) / 0x400000000000000000000000000000000; + z = (z * w) / FIXED_1; // add y^07 / 07 - y^08 / 08 + r += (z * (0x08e38e38e38e38e38e38e38e38e38e38e - y)) / 0x500000000000000000000000000000000; + z = (z * w) / FIXED_1; // add y^09 / 09 - y^10 / 10 + r += (z * (0x08ba2e8ba2e8ba2e8ba2e8ba2e8ba2e8b - y)) / 0x600000000000000000000000000000000; + z = (z * w) / FIXED_1; // add y^11 / 11 - y^12 / 12 + r += (z * (0x089d89d89d89d89d89d89d89d89d89d89 - y)) / 0x700000000000000000000000000000000; + z = (z * w) / FIXED_1; // add y^13 / 13 - y^14 / 14 + r += (z * (0x088888888888888888888888888888888 - y)) / 0x800000000000000000000000000000000; // add y^15 / 15 - y^16 / 16 + } + + /// @dev Compute the natural exponent for a fixed-point number EXP_MIN_VAL <= `x` <= 1 + function exp(int256 x) internal pure returns (int256 r) { + if (x < EXP_MIN_VAL) { + // Saturate to zero below EXP_MIN_VAL. + return 0; + } + if (x == 0) { + return FIXED_1; + } + if (x > EXP_MAX_VAL) { + revert("out-of-bounds"); + } + + // Rewrite the input as a product of natural exponents and a + // single residual q, where q is a number of small magnitude. + // For example: e^-34.419 = e^(-32 - 2 - 0.25 - 0.125 - 0.044) + // = e^-32 * e^-2 * e^-0.25 * e^-0.125 * e^-0.044 + // -> q = -0.044 + + // Multiply with the taylor series for e^q + int256 y; + int256 z; + // q = x % 0.125 (the residual) + z = y = x % 0x0000000000000000000000000000000010000000000000000000000000000000; + z = (z * y) / FIXED_1; + r += z * 0x10e1b3be415a0000; // add y^02 * (20! / 02!) + z = (z * y) / FIXED_1; + r += z * 0x05a0913f6b1e0000; // add y^03 * (20! / 03!) + z = (z * y) / FIXED_1; + r += z * 0x0168244fdac78000; // add y^04 * (20! / 04!) + z = (z * y) / FIXED_1; + r += z * 0x004807432bc18000; // add y^05 * (20! / 05!) + z = (z * y) / FIXED_1; + r += z * 0x000c0135dca04000; // add y^06 * (20! / 06!) + z = (z * y) / FIXED_1; + r += z * 0x0001b707b1cdc000; // add y^07 * (20! / 07!) + z = (z * y) / FIXED_1; + r += z * 0x000036e0f639b800; // add y^08 * (20! / 08!) + z = (z * y) / FIXED_1; + r += z * 0x00000618fee9f800; // add y^09 * (20! / 09!) + z = (z * y) / FIXED_1; + r += z * 0x0000009c197dcc00; // add y^10 * (20! / 10!) + z = (z * y) / FIXED_1; + r += z * 0x0000000e30dce400; // add y^11 * (20! / 11!) + z = (z * y) / FIXED_1; + r += z * 0x000000012ebd1300; // add y^12 * (20! / 12!) + z = (z * y) / FIXED_1; + r += z * 0x0000000017499f00; // add y^13 * (20! / 13!) + z = (z * y) / FIXED_1; + r += z * 0x0000000001a9d480; // add y^14 * (20! / 14!) + z = (z * y) / FIXED_1; + r += z * 0x00000000001c6380; // add y^15 * (20! / 15!) + z = (z * y) / FIXED_1; + r += z * 0x000000000001c638; // add y^16 * (20! / 16!) + z = (z * y) / FIXED_1; + r += z * 0x0000000000001ab8; // add y^17 * (20! / 17!) + z = (z * y) / FIXED_1; + r += z * 0x000000000000017c; // add y^18 * (20! / 18!) + z = (z * y) / FIXED_1; + r += z * 0x0000000000000014; // add y^19 * (20! / 19!) + z = (z * y) / FIXED_1; + r += z * 0x0000000000000001; // add y^20 * (20! / 20!) + r = r / 0x21c3677c82b40000 + y + FIXED_1; // divide by 20! and then add y^1 / 1! + y^0 / 0! + + // Multiply with the non-residual terms. + x = -x; + // e ^ -32 + if ((x & int256(0x0000000000000000000000000000001000000000000000000000000000000000)) != 0) { + r = + (r * int256(0x00000000000000000000000000000000000000f1aaddd7742e56d32fb9f99744)) / + int256(0x0000000000000000000000000043cbaf42a000812488fc5c220ad7b97bf6e99e); // * e ^ -32 + } + // e ^ -16 + if ((x & int256(0x0000000000000000000000000000000800000000000000000000000000000000)) != 0) { + r = + (r * int256(0x00000000000000000000000000000000000afe10820813d65dfe6a33c07f738f)) / + int256(0x000000000000000000000000000005d27a9f51c31b7c2f8038212a0574779991); // * e ^ -16 + } + // e ^ -8 + if ((x & int256(0x0000000000000000000000000000000400000000000000000000000000000000)) != 0) { + r = + (r * int256(0x0000000000000000000000000000000002582ab704279e8efd15e0265855c47a)) / + int256(0x0000000000000000000000000000001b4c902e273a58678d6d3bfdb93db96d02); // * e ^ -8 + } + // e ^ -4 + if ((x & int256(0x0000000000000000000000000000000200000000000000000000000000000000)) != 0) { + r = + (r * int256(0x000000000000000000000000000000001152aaa3bf81cb9fdb76eae12d029571)) / + int256(0x00000000000000000000000000000003b1cc971a9bb5b9867477440d6d157750); // * e ^ -4 + } + // e ^ -2 + if ((x & int256(0x0000000000000000000000000000000100000000000000000000000000000000)) != 0) { + r = + (r * int256(0x000000000000000000000000000000002f16ac6c59de6f8d5d6f63c1482a7c86)) / + int256(0x000000000000000000000000000000015bf0a8b1457695355fb8ac404e7a79e3); // * e ^ -2 + } + // e ^ -1 + if ((x & int256(0x0000000000000000000000000000000080000000000000000000000000000000)) != 0) { + r = + (r * int256(0x000000000000000000000000000000004da2cbf1be5827f9eb3ad1aa9866ebb3)) / + int256(0x00000000000000000000000000000000d3094c70f034de4b96ff7d5b6f99fcd8); // * e ^ -1 + } + // e ^ -0.5 + if ((x & int256(0x0000000000000000000000000000000040000000000000000000000000000000)) != 0) { + r = + (r * int256(0x0000000000000000000000000000000063afbe7ab2082ba1a0ae5e4eb1b479dc)) / + int256(0x00000000000000000000000000000000a45af1e1f40c333b3de1db4dd55f29a7); // * e ^ -0.5 + } + // e ^ -0.25 + if ((x & int256(0x0000000000000000000000000000000020000000000000000000000000000000)) != 0) { + r = + (r * int256(0x0000000000000000000000000000000070f5a893b608861e1f58934f97aea57d)) / + int256(0x00000000000000000000000000000000910b022db7ae67ce76b441c27035c6a1); // * e ^ -0.25 + } + // e ^ -0.125 + if ((x & int256(0x0000000000000000000000000000000010000000000000000000000000000000)) != 0) { + r = + (r * int256(0x00000000000000000000000000000000783eafef1c0a8f3978c7f81824d62ebf)) / + int256(0x0000000000000000000000000000000088415abbe9a76bead8d00cf112e4d4a8); // * e ^ -0.125 + } + } + + /// @dev Returns the multiplication two numbers, reverting on overflow. + function _mul(int256 a, int256 b) private pure returns (int256 c) { + if (a == 0 || b == 0) { + return 0; + } + c = a * b; + if (c / a != b || c / b != a) { + revert("overflow"); + } + } + + /// @dev Returns the division of two numbers, reverting on division by zero. + function _div(int256 a, int256 b) private pure returns (int256 c) { + if (b == 0) { + revert("overflow"); + } + if (a == MIN_FIXED_VAL && b == -1) { + revert("overflow"); + } + c = a / b; + } + + /// @dev Adds two numbers, reverting on overflow. + function _add(int256 a, int256 b) private pure returns (int256 c) { + c = a + b; + if ((a < 0 && b < 0 && c > a) || (a > 0 && b > 0 && c < a)) { + revert("overflow"); + } + } +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/staking/libs/MathUtils.sol b/packages/legacy/verified/L1Staking/sources/contracts/staking/libs/MathUtils.sol new file mode 100644 index 000000000..4194b6da8 --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/staking/libs/MathUtils.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import "@openzeppelin/contracts/math/SafeMath.sol"; + +/** + * @title MathUtils Library + * @notice A collection of functions to perform math operations + */ +library MathUtils { + using SafeMath for uint256; + + /** + * @dev Calculates the weighted average of two values pondering each of these + * values based on configured weights. The contribution of each value N is + * weightN/(weightA + weightB). + * @param valueA The amount for value A + * @param weightA The weight to use for value A + * @param valueB The amount for value B + * @param weightB The weight to use for value B + */ + function weightedAverage( + uint256 valueA, + uint256 weightA, + uint256 valueB, + uint256 weightB + ) internal pure returns (uint256) { + return valueA.mul(weightA).add(valueB.mul(weightB)).div(weightA.add(weightB)); + } + + /** + * @dev Returns the minimum of two numbers. + */ + function min(uint256 x, uint256 y) internal pure returns (uint256) { + return x <= y ? x : y; + } + + /** + * @dev Returns the difference between two numbers or zero if negative. + */ + function diffOrZero(uint256 x, uint256 y) internal pure returns (uint256) { + return (x > y) ? x.sub(y) : 0; + } +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/staking/libs/Stakes.sol b/packages/legacy/verified/L1Staking/sources/contracts/staking/libs/Stakes.sol new file mode 100644 index 000000000..5882944d8 --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/staking/libs/Stakes.sol @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; +pragma abicoder v2; + +import "@openzeppelin/contracts/math/SafeMath.sol"; + +import "./MathUtils.sol"; + +/** + * @title A collection of data structures and functions to manage the Indexer Stake state. + * Used for low-level state changes, require() conditions should be evaluated + * at the caller function scope. + */ +library Stakes { + using SafeMath for uint256; + using Stakes for Stakes.Indexer; + + struct Indexer { + uint256 tokensStaked; // Tokens on the indexer stake (staked by the indexer) + uint256 tokensAllocated; // Tokens used in allocations + uint256 tokensLocked; // Tokens locked for withdrawal subject to thawing period + uint256 tokensLockedUntil; // Block when locked tokens can be withdrawn + } + + /** + * @dev Deposit tokens to the indexer stake. + * @param stake Stake data + * @param _tokens Amount of tokens to deposit + */ + function deposit(Stakes.Indexer storage stake, uint256 _tokens) internal { + stake.tokensStaked = stake.tokensStaked.add(_tokens); + } + + /** + * @dev Release tokens from the indexer stake. + * @param stake Stake data + * @param _tokens Amount of tokens to release + */ + function release(Stakes.Indexer storage stake, uint256 _tokens) internal { + stake.tokensStaked = stake.tokensStaked.sub(_tokens); + } + + /** + * @dev Allocate tokens from the main stack to a SubgraphDeployment. + * @param stake Stake data + * @param _tokens Amount of tokens to allocate + */ + function allocate(Stakes.Indexer storage stake, uint256 _tokens) internal { + stake.tokensAllocated = stake.tokensAllocated.add(_tokens); + } + + /** + * @dev Unallocate tokens from a SubgraphDeployment back to the main stack. + * @param stake Stake data + * @param _tokens Amount of tokens to unallocate + */ + function unallocate(Stakes.Indexer storage stake, uint256 _tokens) internal { + stake.tokensAllocated = stake.tokensAllocated.sub(_tokens); + } + + /** + * @dev Lock tokens until a thawing period pass. + * @param stake Stake data + * @param _tokens Amount of tokens to unstake + * @param _period Period in blocks that need to pass before withdrawal + */ + function lockTokens( + Stakes.Indexer storage stake, + uint256 _tokens, + uint256 _period + ) internal { + // Take into account period averaging for multiple unstake requests + uint256 lockingPeriod = _period; + if (stake.tokensLocked > 0) { + lockingPeriod = MathUtils.weightedAverage( + MathUtils.diffOrZero(stake.tokensLockedUntil, block.number), // Remaining thawing period + stake.tokensLocked, // Weighted by remaining unstaked tokens + _period, // Thawing period + _tokens // Weighted by new tokens to unstake + ); + } + + // Update balances + stake.tokensLocked = stake.tokensLocked.add(_tokens); + stake.tokensLockedUntil = block.number.add(lockingPeriod); + } + + /** + * @dev Unlock tokens. + * @param stake Stake data + * @param _tokens Amount of tokens to unkock + */ + function unlockTokens(Stakes.Indexer storage stake, uint256 _tokens) internal { + stake.tokensLocked = stake.tokensLocked.sub(_tokens); + if (stake.tokensLocked == 0) { + stake.tokensLockedUntil = 0; + } + } + + /** + * @dev Take all tokens out from the locked stake for withdrawal. + * @param stake Stake data + * @return Amount of tokens being withdrawn + */ + function withdrawTokens(Stakes.Indexer storage stake) internal returns (uint256) { + // Calculate tokens that can be released + uint256 tokensToWithdraw = stake.tokensWithdrawable(); + + if (tokensToWithdraw > 0) { + // Reset locked tokens + stake.unlockTokens(tokensToWithdraw); + + // Decrease indexer stake + stake.release(tokensToWithdraw); + } + + return tokensToWithdraw; + } + + /** + * @dev Return the amount of tokens used in allocations and locked for withdrawal. + * @param stake Stake data + * @return Token amount + */ + function tokensUsed(Stakes.Indexer memory stake) internal pure returns (uint256) { + return stake.tokensAllocated.add(stake.tokensLocked); + } + + /** + * @dev Return the amount of tokens staked not considering the ones that are already going + * through the thawing period or are ready for withdrawal. We call it secure stake because + * it is not subject to change by a withdraw call from the indexer. + * @param stake Stake data + * @return Token amount + */ + function tokensSecureStake(Stakes.Indexer memory stake) internal pure returns (uint256) { + return stake.tokensStaked.sub(stake.tokensLocked); + } + + /** + * @dev Tokens free balance on the indexer stake that can be used for any purpose. + * Any token that is allocated cannot be used as well as tokens that are going through the + * thawing period or are withdrawable + * Calc: tokensStaked - tokensAllocated - tokensLocked + * @param stake Stake data + * @return Token amount + */ + function tokensAvailable(Stakes.Indexer memory stake) internal pure returns (uint256) { + return stake.tokensAvailableWithDelegation(0); + } + + /** + * @dev Tokens free balance on the indexer stake that can be used for allocations. + * This function accepts a parameter for extra delegated capacity that takes into + * account delegated tokens + * @param stake Stake data + * @param _delegatedCapacity Amount of tokens used from delegators to calculate availability + * @return Token amount + */ + function tokensAvailableWithDelegation(Stakes.Indexer memory stake, uint256 _delegatedCapacity) + internal + pure + returns (uint256) + { + uint256 tokensCapacity = stake.tokensStaked.add(_delegatedCapacity); + uint256 _tokensUsed = stake.tokensUsed(); + // If more tokens are used than the current capacity, the indexer is overallocated. + // This means the indexer doesn't have available capacity to create new allocations. + // We can reach this state when the indexer has funds allocated and then any + // of these conditions happen: + // - The delegationCapacity ratio is reduced. + // - The indexer stake is slashed. + // - A delegator removes enough stake. + if (_tokensUsed > tokensCapacity) { + // Indexer stake is over allocated: return 0 to avoid stake to be used until + // the overallocation is restored by staking more tokens, unallocating tokens + // or using more delegated funds + return 0; + } + return tokensCapacity.sub(_tokensUsed); + } + + /** + * @dev Tokens available for withdrawal after thawing period. + * @param stake Stake data + * @return Token amount + */ + function tokensWithdrawable(Stakes.Indexer memory stake) internal view returns (uint256) { + // No tokens to withdraw before locking period + if (stake.tokensLockedUntil == 0 || block.number < stake.tokensLockedUntil) { + return 0; + } + return stake.tokensLocked; + } +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/token/IGraphToken.sol b/packages/legacy/verified/L1Staking/sources/contracts/token/IGraphToken.sol new file mode 100644 index 000000000..8255e18d5 --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/token/IGraphToken.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface IGraphToken is IERC20 { + // -- Mint and Burn -- + + function burn(uint256 amount) external; + + function burnFrom(address _from, uint256 amount) external; + + function mint(address _to, uint256 _amount) external; + + // -- Mint Admin -- + + function addMinter(address _account) external; + + function removeMinter(address _account) external; + + function renounceMinter() external; + + function isMinter(address _account) external view returns (bool); + + // -- Permit -- + + function permit( + address _owner, + address _spender, + uint256 _value, + uint256 _deadline, + uint8 _v, + bytes32 _r, + bytes32 _s + ) external; + + // -- Allowance -- + + function increaseAllowance(address spender, uint256 addedValue) external returns (bool); + + function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool); +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/upgrades/GraphUpgradeable.sol b/packages/legacy/verified/L1Staking/sources/contracts/upgrades/GraphUpgradeable.sol new file mode 100644 index 000000000..92ce80ad7 --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/upgrades/GraphUpgradeable.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import { IGraphProxy } from "./IGraphProxy.sol"; + +/** + * @title Graph Upgradeable + * @dev This contract is intended to be inherited from upgradeable contracts. + */ +abstract contract GraphUpgradeable { + /** + * @dev Storage slot with the address of the current implementation. + * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is + * validated in the constructor. + */ + bytes32 internal constant IMPLEMENTATION_SLOT = + 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + /** + * @dev Check if the caller is the proxy admin. + */ + modifier onlyProxyAdmin(IGraphProxy _proxy) { + require(msg.sender == _proxy.admin(), "Caller must be the proxy admin"); + _; + } + + /** + * @dev Check if the caller is the implementation. + */ + modifier onlyImpl() { + require(msg.sender == _implementation(), "Only implementation"); + _; + } + + /** + * @dev Returns the current implementation. + * @return impl Address of the current implementation + */ + function _implementation() internal view returns (address impl) { + bytes32 slot = IMPLEMENTATION_SLOT; + // solhint-disable-next-line no-inline-assembly + assembly { + impl := sload(slot) + } + } + + /** + * @notice Accept to be an implementation of proxy. + * @param _proxy Proxy to accept + */ + function acceptProxy(IGraphProxy _proxy) external onlyProxyAdmin(_proxy) { + _proxy.acceptUpgrade(); + } + + /** + * @notice Accept to be an implementation of proxy and then call a function from the new + * implementation as specified by `_data`, which should be an encoded function call. This is + * useful to initialize new storage variables in the proxied contract. + * @param _proxy Proxy to accept + * @param _data Calldata for the initialization function call (including selector) + */ + function acceptProxyAndCall(IGraphProxy _proxy, bytes calldata _data) + external + onlyProxyAdmin(_proxy) + { + _proxy.acceptUpgradeAndCall(_data); + } +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/upgrades/IGraphProxy.sol b/packages/legacy/verified/L1Staking/sources/contracts/upgrades/IGraphProxy.sol new file mode 100644 index 000000000..61946e948 --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/upgrades/IGraphProxy.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +interface IGraphProxy { + function admin() external returns (address); + + function setAdmin(address _newAdmin) external; + + function implementation() external returns (address); + + function pendingImplementation() external returns (address); + + function upgradeTo(address _newImplementation) external; + + function acceptUpgrade() external; + + function acceptUpgradeAndCall(bytes calldata data) external; +} diff --git a/packages/legacy/verified/L1Staking/sources/contracts/utils/TokenUtils.sol b/packages/legacy/verified/L1Staking/sources/contracts/utils/TokenUtils.sol new file mode 100644 index 000000000..cbb23ce97 --- /dev/null +++ b/packages/legacy/verified/L1Staking/sources/contracts/utils/TokenUtils.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import "../token/IGraphToken.sol"; + +library TokenUtils { + /** + * @dev Pull tokens from an address to this contract. + * @param _graphToken Token to transfer + * @param _from Address sending the tokens + * @param _amount Amount of tokens to transfer + */ + function pullTokens( + IGraphToken _graphToken, + address _from, + uint256 _amount + ) internal { + if (_amount > 0) { + require(_graphToken.transferFrom(_from, address(this), _amount), "!transfer"); + } + } + + /** + * @dev Push tokens from this contract to a receiving address. + * @param _graphToken Token to transfer + * @param _to Address receiving the tokens + * @param _amount Amount of tokens to transfer + */ + function pushTokens( + IGraphToken _graphToken, + address _to, + uint256 _amount + ) internal { + if (_amount > 0) { + require(_graphToken.transfer(_to, _amount), "!transfer"); + } + } + + /** + * @dev Burn tokens held by this contract. + * @param _graphToken Token to burn + * @param _amount Amount of tokens to burn + */ + function burnTokens(IGraphToken _graphToken, uint256 _amount) internal { + if (_amount > 0) { + _graphToken.burn(_amount); + } + } +} diff --git a/packages/legacy/verified/StakingExtension/compiler.json b/packages/legacy/verified/StakingExtension/compiler.json new file mode 100644 index 000000000..7e14e94ee --- /dev/null +++ b/packages/legacy/verified/StakingExtension/compiler.json @@ -0,0 +1,46 @@ +{ + "version": "0.7.6+commit.7338295f", + "settings": { + "metadata": { + "bytecodeHash": "ipfs", + "useLiteralContent": true + }, + "libraries": {}, + "optimizer": { + "runs": 200, + "enabled": true + }, + "evmVersion": "istanbul", + "remappings": [] + }, + "target": { + "source": "contracts/staking/StakingExtension.sol", + "name": "StakingExtension" + }, + "sourcifyMatch": "exact_match", + "sourceHashes": { + "contracts/discovery/IGNS.sol": "0a4c68898721a068313a7fa32b99ebe9da1a432067dcb968a4d7a11e5d72e619", + "contracts/base/IMulticall.sol": "bbc79b9531ebaf4245bab4b7fed363d2e479cc9ce24d579e56a4d8ab44094dea", + "contracts/staking/IStaking.sol": "b6e6066ed39cb7fa2c2ce5d525accd36f35a503fa0e61a446147b9f210198b7e", + "contracts/utils/TokenUtils.sol": "f1069ba54bdf449002414f051d99538ca02d8253a1670ec1398727002ffff45b", + "contracts/token/IGraphToken.sol": "244ca084fd409c9ad3969e7ac5548346495466442ff7f802288b112d687705d2", + "contracts/curation/ICuration.sol": "34624b8df89549050f23bb3c606c9c60440330ab23551f4d4f1f0b94aef6afc9", + "contracts/governance/Managed.sol": "99e5aa75c463e7fd401e6f2805afb45201fbdbfc26c21c0cb0bb38da59a12cf5", + "contracts/governance/IManaged.sol": "2f4a25095e90d6b08f1789dde49cb87d46888090ccc4e6ab1459e316c746cd29", + "contracts/staking/libs/Stakes.sol": "d2954b67e9bf2681a3d958ab2d3eda8d8c7942e4fade92cd4670e33458f6d676", + "contracts/epochs/IEpochManager.sol": "cb45047682865d6ed215af7d05d505b8fa7a1e66b1e54b45c1a47798281f47ab", + "contracts/staking/IStakingBase.sol": "9acf13c60dca513be4999d12d79c5ed20cce1a5dd3ae985c2c22448ea7e9557f", + "contracts/staking/IStakingData.sol": "27faec50cd81bfeba67ac3a3fb35eaa0cd8d1912ec05e9647fae8746f46e1301", + "contracts/upgrades/IGraphProxy.sol": "d7b89c0be1a05c93facc640a08d0b4f1e0b4557cb268d9143e4c42ce24bbd1a9", + "contracts/arbitrum/ITokenGateway.sol": "55bd317bf1ca12a2308cc51ad4ec6c370dcb6d1cdd1631ec2308e57528ad7600", + "contracts/governance/IController.sol": "beb591bb70a3b94f025a5f64bbb3fc6788043fb8d5f1cdffb21acb8cbbce8f8d", + "contracts/staking/StakingStorage.sol": "82b7a295372eed804f79965db4c5b8701c2aa8841cd215a037964da95c78793c", + "contracts/staking/libs/MathUtils.sol": "721f4b3dbde3170e895a45c09905972db388321a2d7f61408ad4e2fd8d5a34a8", + "contracts/rewards/IRewardsManager.sol": "5cf8c3593f475719620e6a01b478fa3b5644d3848f82e7768bb36d43069478dd", + "contracts/staking/StakingExtension.sol": "ce00170ae467f6d2471b755eb62750d0a374010594d77f9361d9f0790a2ec93f", + "contracts/staking/IStakingExtension.sol": "cd244dd27c74c51948020f31724e896c13f3750211761c3c5549c61cce45f30f", + "contracts/upgrades/GraphUpgradeable.sol": "fdb340dbeb5de52ea8243fbfab9d2ef8fdfb916e909dc4e1ae6b314cde0ea083", + "@openzeppelin/contracts/math/SafeMath.sol": "4a04d0a20a19e3ef1dcabae9cad9ba006430a4e7eec4d9b519db87999722c98a", + "@openzeppelin/contracts/token/ERC20/IERC20.sol": "0573c2961569aa4906845d0cd428b5b7394956170054ceeaa8f8af96cd44875c" + } +} diff --git a/packages/legacy/verified/StakingExtension/sources/@openzeppelin/contracts/math/SafeMath.sol b/packages/legacy/verified/StakingExtension/sources/@openzeppelin/contracts/math/SafeMath.sol new file mode 100644 index 000000000..3e81f295b --- /dev/null +++ b/packages/legacy/verified/StakingExtension/sources/@openzeppelin/contracts/math/SafeMath.sol @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + + /** + * @dev Returns the substraction of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b > a) return (false, 0); + return (true, a - b); + } + + /** + * @dev Returns the multiplication of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + + /** + * @dev Returns the division of two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b == 0) return (false, 0); + return (true, a / b); + } + + /** + * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b == 0) return (false, 0); + return (true, a % b); + } + + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + require(b <= a, "SafeMath: subtraction overflow"); + return a - b; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) return 0; + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + require(b > 0, "SafeMath: division by zero"); + return a / b; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + require(b > 0, "SafeMath: modulo by zero"); + return a % b; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {trySub}. + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + return a - b; + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting with custom message on + * division by zero. The result is rounded towards zero. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryDiv}. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + return a / b; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting with custom message when dividing by zero. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryMod}. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + return a % b; + } +} diff --git a/packages/legacy/verified/StakingExtension/sources/@openzeppelin/contracts/token/ERC20/IERC20.sol b/packages/legacy/verified/StakingExtension/sources/@openzeppelin/contracts/token/ERC20/IERC20.sol new file mode 100644 index 000000000..9b877d119 --- /dev/null +++ b/packages/legacy/verified/StakingExtension/sources/@openzeppelin/contracts/token/ERC20/IERC20.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} diff --git a/packages/legacy/verified/StakingExtension/sources/contracts/arbitrum/ITokenGateway.sol b/packages/legacy/verified/StakingExtension/sources/contracts/arbitrum/ITokenGateway.sol new file mode 100644 index 000000000..977fe07f2 --- /dev/null +++ b/packages/legacy/verified/StakingExtension/sources/contracts/arbitrum/ITokenGateway.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2020, Offchain Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Originally copied from: + * https://github.com/OffchainLabs/arbitrum/tree/e3a6307ad8a2dc2cad35728a2a9908cfd8dd8ef9/packages/arb-bridge-peripherals + * + * MODIFIED from Offchain Labs' implementation: + * - Changed solidity version to 0.7.6 (pablo@edgeandnode.com) + * + */ + +pragma solidity ^0.7.6; + +interface ITokenGateway { + /// @notice event deprecated in favor of DepositInitiated and WithdrawalInitiated + // event OutboundTransferInitiated( + // address token, + // address indexed _from, + // address indexed _to, + // uint256 indexed _transferId, + // uint256 _amount, + // bytes _data + // ); + + /// @notice event deprecated in favor of DepositFinalized and WithdrawalFinalized + // event InboundTransferFinalized( + // address token, + // address indexed _from, + // address indexed _to, + // uint256 indexed _transferId, + // uint256 _amount, + // bytes _data + // ); + + function outboundTransfer( + address _token, + address _to, + uint256 _amount, + uint256 _maxGas, + uint256 _gasPriceBid, + bytes calldata _data + ) external payable returns (bytes memory); + + function finalizeInboundTransfer( + address _token, + address _from, + address _to, + uint256 _amount, + bytes calldata _data + ) external payable; + + /** + * @notice Calculate the address used when bridging an ERC20 token + * @dev the L1 and L2 address oracles may not always be in sync. + * For example, a custom token may have been registered but not deployed or the contract self destructed. + * @param l1ERC20 address of L1 token + * @return L2 address of a bridged ERC20 token + */ + function calculateL2TokenAddress(address l1ERC20) external view returns (address); +} diff --git a/packages/legacy/verified/StakingExtension/sources/contracts/base/IMulticall.sol b/packages/legacy/verified/StakingExtension/sources/contracts/base/IMulticall.sol new file mode 100644 index 000000000..3269f694b --- /dev/null +++ b/packages/legacy/verified/StakingExtension/sources/contracts/base/IMulticall.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; +pragma abicoder v2; + +/** + * @title Multicall interface + * @notice Enables calling multiple methods in a single call to the contract + */ +interface IMulticall { + /** + * @notice Call multiple functions in the current contract and return the data from all of them if they all succeed + * @param data The encoded function data for each of the calls to make to this contract + * @return results The results from each of the calls passed in via data + */ + function multicall(bytes[] calldata data) external returns (bytes[] memory results); +} diff --git a/packages/legacy/verified/StakingExtension/sources/contracts/curation/ICuration.sol b/packages/legacy/verified/StakingExtension/sources/contracts/curation/ICuration.sol new file mode 100644 index 000000000..dffff46cd --- /dev/null +++ b/packages/legacy/verified/StakingExtension/sources/contracts/curation/ICuration.sol @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +/** + * @title Curation Interface + * @dev Interface for the Curation contract (and L2Curation too) + */ +interface ICuration { + // -- Configuration -- + + /** + * @notice Update the default reserve ratio to `_defaultReserveRatio` + * @param _defaultReserveRatio Reserve ratio (in PPM) + */ + function setDefaultReserveRatio(uint32 _defaultReserveRatio) external; + + /** + * @notice Update the minimum deposit amount needed to intialize a new subgraph + * @param _minimumCurationDeposit Minimum amount of tokens required deposit + */ + function setMinimumCurationDeposit(uint256 _minimumCurationDeposit) external; + + /** + * @notice Set the curation tax percentage to charge when a curator deposits GRT tokens. + * @param _percentage Curation tax percentage charged when depositing GRT tokens + */ + function setCurationTaxPercentage(uint32 _percentage) external; + + /** + * @notice Set the master copy to use as clones for the curation token. + * @param _curationTokenMaster Address of implementation contract to use for curation tokens + */ + function setCurationTokenMaster(address _curationTokenMaster) external; + + // -- Curation -- + + /** + * @notice Deposit Graph Tokens in exchange for signal of a SubgraphDeployment curation pool. + * @param _subgraphDeploymentID Subgraph deployment pool from where to mint signal + * @param _tokensIn Amount of Graph Tokens to deposit + * @param _signalOutMin Expected minimum amount of signal to receive + * @return Amount of signal minted + * @return Amount of curation tax burned + */ + function mint( + bytes32 _subgraphDeploymentID, + uint256 _tokensIn, + uint256 _signalOutMin + ) external returns (uint256, uint256); + + /** + * @notice Burn _signal from the SubgraphDeployment curation pool + * @param _subgraphDeploymentID SubgraphDeployment the curator is returning signal + * @param _signalIn Amount of signal to return + * @param _tokensOutMin Expected minimum amount of tokens to receive + * @return Tokens returned + */ + function burn( + bytes32 _subgraphDeploymentID, + uint256 _signalIn, + uint256 _tokensOutMin + ) external returns (uint256); + + /** + * @notice Assign Graph Tokens collected as curation fees to the curation pool reserve. + * @param _subgraphDeploymentID SubgraphDeployment where funds should be allocated as reserves + * @param _tokens Amount of Graph Tokens to add to reserves + */ + function collect(bytes32 _subgraphDeploymentID, uint256 _tokens) external; + + // -- Getters -- + + /** + * @notice Check if any GRT tokens are deposited for a SubgraphDeployment. + * @param _subgraphDeploymentID SubgraphDeployment to check if curated + * @return True if curated, false otherwise + */ + function isCurated(bytes32 _subgraphDeploymentID) external view returns (bool); + + /** + * @notice Get the amount of signal a curator has in a curation pool. + * @param _curator Curator owning the signal tokens + * @param _subgraphDeploymentID Subgraph deployment curation pool + * @return Amount of signal owned by a curator for the subgraph deployment + */ + function getCuratorSignal(address _curator, bytes32 _subgraphDeploymentID) + external + view + returns (uint256); + + /** + * @notice Get the amount of signal in a curation pool. + * @param _subgraphDeploymentID Subgraph deployment curation poool + * @return Amount of signal minted for the subgraph deployment + */ + function getCurationPoolSignal(bytes32 _subgraphDeploymentID) external view returns (uint256); + + /** + * @notice Get the amount of token reserves in a curation pool. + * @param _subgraphDeploymentID Subgraph deployment curation poool + * @return Amount of token reserves in the curation pool + */ + function getCurationPoolTokens(bytes32 _subgraphDeploymentID) external view returns (uint256); + + /** + * @notice Calculate amount of signal that can be bought with tokens in a curation pool. + * This function considers and excludes the deposit tax. + * @param _subgraphDeploymentID Subgraph deployment to mint signal + * @param _tokensIn Amount of tokens used to mint signal + * @return Amount of signal that can be bought + * @return Amount of tokens that will be burned as curation tax + */ + function tokensToSignal(bytes32 _subgraphDeploymentID, uint256 _tokensIn) + external + view + returns (uint256, uint256); + + /** + * @notice Calculate number of tokens to get when burning signal from a curation pool. + * @param _subgraphDeploymentID Subgraph deployment to burn signal + * @param _signalIn Amount of signal to burn + * @return Amount of tokens to get for the specified amount of signal + */ + function signalToTokens(bytes32 _subgraphDeploymentID, uint256 _signalIn) + external + view + returns (uint256); + + /** + * @notice Tax charged when curators deposit funds. + * Parts per million. (Allows for 4 decimal points, 999,999 = 99.9999%) + * @return Curation tax percentage expressed in PPM + */ + function curationTaxPercentage() external view returns (uint32); +} diff --git a/packages/legacy/verified/StakingExtension/sources/contracts/discovery/IGNS.sol b/packages/legacy/verified/StakingExtension/sources/contracts/discovery/IGNS.sol new file mode 100644 index 000000000..80cc99820 --- /dev/null +++ b/packages/legacy/verified/StakingExtension/sources/contracts/discovery/IGNS.sol @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +/** + * @title Interface for GNS + */ +interface IGNS { + // -- Pool -- + + /** + * @dev The SubgraphData struct holds information about subgraphs + * and their signal; both nSignal (i.e. name signal at the GNS level) + * and vSignal (i.e. version signal at the Curation contract level) + */ + struct SubgraphData { + uint256 vSignal; // The token of the subgraph-deployment bonding curve + uint256 nSignal; // The token of the subgraph bonding curve + mapping(address => uint256) curatorNSignal; + bytes32 subgraphDeploymentID; + uint32 reserveRatioDeprecated; // Ratio for the bonding curve, always 1 in PPM, deprecated. + bool disabled; + uint256 withdrawableGRT; + } + + /** + * @dev The LegacySubgraphKey struct holds the account and sequence ID + * used to generate subgraph IDs in legacy subgraphs. + */ + struct LegacySubgraphKey { + address account; + uint256 accountSeqID; + } + + // -- Configuration -- + + /** + * @notice Approve curation contract to pull funds. + */ + function approveAll() external; + + /** + * @notice Set the owner fee percentage. This is used to prevent a subgraph owner to drain all + * the name curators tokens while upgrading or deprecating and is configurable in parts per million. + * @param _ownerTaxPercentage Owner tax percentage + */ + function setOwnerTaxPercentage(uint32 _ownerTaxPercentage) external; + + // -- Publishing -- + + /** + * @notice Allows a graph account to set a default name + * @param _graphAccount Account that is setting its name + * @param _nameSystem Name system account already has ownership of a name in + * @param _nameIdentifier The unique identifier that is used to identify the name in the system + * @param _name The name being set as default + */ + function setDefaultName( + address _graphAccount, + uint8 _nameSystem, + bytes32 _nameIdentifier, + string calldata _name + ) external; + + /** + * @notice Allows a subgraph owner to update the metadata of a subgraph they have published + * @param _subgraphID Subgraph ID + * @param _subgraphMetadata IPFS hash for the subgraph metadata + */ + function updateSubgraphMetadata(uint256 _subgraphID, bytes32 _subgraphMetadata) external; + + /** + * @notice Publish a new subgraph. + * @param _subgraphDeploymentID Subgraph deployment for the subgraph + * @param _versionMetadata IPFS hash for the subgraph version metadata + * @param _subgraphMetadata IPFS hash for the subgraph metadata + */ + function publishNewSubgraph( + bytes32 _subgraphDeploymentID, + bytes32 _versionMetadata, + bytes32 _subgraphMetadata + ) external; + + /** + * @notice Publish a new version of an existing subgraph. + * @param _subgraphID Subgraph ID + * @param _subgraphDeploymentID Subgraph deployment ID of the new version + * @param _versionMetadata IPFS hash for the subgraph version metadata + */ + function publishNewVersion( + uint256 _subgraphID, + bytes32 _subgraphDeploymentID, + bytes32 _versionMetadata + ) external; + + /** + * @notice Deprecate a subgraph. The bonding curve is destroyed, the vSignal is burned, and the GNS + * contract holds the GRT from burning the vSignal, which all curators can withdraw manually. + * Can only be done by the subgraph owner. + * @param _subgraphID Subgraph ID + */ + function deprecateSubgraph(uint256 _subgraphID) external; + + // -- Curation -- + + /** + * @notice Deposit GRT into a subgraph and mint signal. + * @param _subgraphID Subgraph ID + * @param _tokensIn The amount of tokens the nameCurator wants to deposit + * @param _nSignalOutMin Expected minimum amount of name signal to receive + */ + function mintSignal( + uint256 _subgraphID, + uint256 _tokensIn, + uint256 _nSignalOutMin + ) external; + + /** + * @notice Burn signal for a subgraph and return the GRT. + * @param _subgraphID Subgraph ID + * @param _nSignal The amount of nSignal the nameCurator wants to burn + * @param _tokensOutMin Expected minimum amount of tokens to receive + */ + function burnSignal( + uint256 _subgraphID, + uint256 _nSignal, + uint256 _tokensOutMin + ) external; + + /** + * @notice Move subgraph signal from sender to `_recipient` + * @param _subgraphID Subgraph ID + * @param _recipient Address to send the signal to + * @param _amount The amount of nSignal to transfer + */ + function transferSignal( + uint256 _subgraphID, + address _recipient, + uint256 _amount + ) external; + + /** + * @notice Withdraw tokens from a deprecated subgraph. + * When the subgraph is deprecated, any curator can call this function and + * withdraw the GRT they are entitled for its original deposit + * @param _subgraphID Subgraph ID + */ + function withdraw(uint256 _subgraphID) external; + + // -- Getters -- + + /** + * @notice Return the owner of a subgraph. + * @param _tokenID Subgraph ID + * @return Owner address + */ + function ownerOf(uint256 _tokenID) external view returns (address); + + /** + * @notice Return the total signal on the subgraph. + * @param _subgraphID Subgraph ID + * @return Total signal on the subgraph + */ + function subgraphSignal(uint256 _subgraphID) external view returns (uint256); + + /** + * @notice Return the total tokens on the subgraph at current value. + * @param _subgraphID Subgraph ID + * @return Total tokens on the subgraph + */ + function subgraphTokens(uint256 _subgraphID) external view returns (uint256); + + /** + * @notice Calculate subgraph signal to be returned for an amount of tokens. + * @param _subgraphID Subgraph ID + * @param _tokensIn Tokens being exchanged for subgraph signal + * @return Amount of subgraph signal and curation tax + */ + function tokensToNSignal(uint256 _subgraphID, uint256 _tokensIn) + external + view + returns ( + uint256, + uint256, + uint256 + ); + + /** + * @notice Calculate tokens returned for an amount of subgraph signal. + * @param _subgraphID Subgraph ID + * @param _nSignalIn Subgraph signal being exchanged for tokens + * @return Amount of tokens returned for an amount of subgraph signal + */ + function nSignalToTokens(uint256 _subgraphID, uint256 _nSignalIn) + external + view + returns (uint256, uint256); + + /** + * @notice Calculate subgraph signal to be returned for an amount of subgraph deployment signal. + * @param _subgraphID Subgraph ID + * @param _vSignalIn Amount of subgraph deployment signal to exchange for subgraph signal + * @return Amount of subgraph signal that can be bought + */ + function vSignalToNSignal(uint256 _subgraphID, uint256 _vSignalIn) + external + view + returns (uint256); + + /** + * @notice Calculate subgraph deployment signal to be returned for an amount of subgraph signal. + * @param _subgraphID Subgraph ID + * @param _nSignalIn Subgraph signal being exchanged for subgraph deployment signal + * @return Amount of subgraph deployment signal that can be returned + */ + function nSignalToVSignal(uint256 _subgraphID, uint256 _nSignalIn) + external + view + returns (uint256); + + /** + * @notice Get the amount of subgraph signal a curator has. + * @param _subgraphID Subgraph ID + * @param _curator Curator address + * @return Amount of subgraph signal owned by a curator + */ + function getCuratorSignal(uint256 _subgraphID, address _curator) + external + view + returns (uint256); + + /** + * @notice Return whether a subgraph is published. + * @param _subgraphID Subgraph ID + * @return Return true if subgraph is currently published + */ + function isPublished(uint256 _subgraphID) external view returns (bool); + + /** + * @notice Return whether a subgraph is a legacy subgraph (created before subgraph NFTs). + * @param _subgraphID Subgraph ID + * @return Return true if subgraph is a legacy subgraph + */ + function isLegacySubgraph(uint256 _subgraphID) external view returns (bool); + + /** + * @notice Returns account and sequence ID for a legacy subgraph (created before subgraph NFTs). + * @param _subgraphID Subgraph ID + * @return account Account that created the subgraph (or 0 if it's not a legacy subgraph) + * @return seqID Sequence number for the subgraph + */ + function getLegacySubgraphKey(uint256 _subgraphID) + external + view + returns (address account, uint256 seqID); +} diff --git a/packages/legacy/verified/StakingExtension/sources/contracts/epochs/IEpochManager.sol b/packages/legacy/verified/StakingExtension/sources/contracts/epochs/IEpochManager.sol new file mode 100644 index 000000000..36b1f47a3 --- /dev/null +++ b/packages/legacy/verified/StakingExtension/sources/contracts/epochs/IEpochManager.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +interface IEpochManager { + // -- Configuration -- + + function setEpochLength(uint256 _epochLength) external; + + // -- Epochs + + function runEpoch() external; + + // -- Getters -- + + function isCurrentEpochRun() external view returns (bool); + + function blockNum() external view returns (uint256); + + function blockHash(uint256 _block) external view returns (bytes32); + + function currentEpoch() external view returns (uint256); + + function currentEpochBlock() external view returns (uint256); + + function currentEpochBlockSinceStart() external view returns (uint256); + + function epochsSince(uint256 _epoch) external view returns (uint256); + + function epochsSinceUpdate() external view returns (uint256); +} diff --git a/packages/legacy/verified/StakingExtension/sources/contracts/governance/IController.sol b/packages/legacy/verified/StakingExtension/sources/contracts/governance/IController.sol new file mode 100644 index 000000000..7df3d94ee --- /dev/null +++ b/packages/legacy/verified/StakingExtension/sources/contracts/governance/IController.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; + +interface IController { + function getGovernor() external view returns (address); + + // -- Registry -- + + function setContractProxy(bytes32 _id, address _contractAddress) external; + + function unsetContractProxy(bytes32 _id) external; + + function updateController(bytes32 _id, address _controller) external; + + function getContractProxy(bytes32 _id) external view returns (address); + + // -- Pausing -- + + function setPartialPaused(bool _partialPaused) external; + + function setPaused(bool _paused) external; + + function setPauseGuardian(address _newPauseGuardian) external; + + function paused() external view returns (bool); + + function partialPaused() external view returns (bool); +} diff --git a/packages/legacy/verified/StakingExtension/sources/contracts/governance/IManaged.sol b/packages/legacy/verified/StakingExtension/sources/contracts/governance/IManaged.sol new file mode 100644 index 000000000..76f05e0fb --- /dev/null +++ b/packages/legacy/verified/StakingExtension/sources/contracts/governance/IManaged.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import { IController } from "./IController.sol"; + +/** + * @title Managed Interface + * @dev Interface for contracts that can be managed by a controller. + */ +interface IManaged { + /** + * @notice Set the controller that manages this contract + * @dev Only the current controller can set a new controller + * @param _controller Address of the new controller + */ + function setController(address _controller) external; + + /** + * @notice Sync protocol contract addresses from the Controller registry + * @dev This function will cache all the contracts using the latest addresses. + * Anyone can call the function whenever a Proxy contract change in the + * controller to ensure the protocol is using the latest version. + */ + function syncAllContracts() external; + + /** + * @notice Get the Controller that manages this contract + * @return The Controller as an IController interface + */ + function controller() external view returns (IController); +} diff --git a/packages/legacy/verified/StakingExtension/sources/contracts/governance/Managed.sol b/packages/legacy/verified/StakingExtension/sources/contracts/governance/Managed.sol new file mode 100644 index 000000000..fb65e71b9 --- /dev/null +++ b/packages/legacy/verified/StakingExtension/sources/contracts/governance/Managed.sol @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import { IController } from "./IController.sol"; + +import { ICuration } from "../curation/ICuration.sol"; +import { IEpochManager } from "../epochs/IEpochManager.sol"; +import { IRewardsManager } from "../rewards/IRewardsManager.sol"; +import { IStaking } from "../staking/IStaking.sol"; +import { IStakingBase } from "../staking/IStakingBase.sol"; +import { IGraphToken } from "../token/IGraphToken.sol"; +import { ITokenGateway } from "../arbitrum/ITokenGateway.sol"; +import { IGNS } from "../discovery/IGNS.sol"; + +import { IManaged } from "./IManaged.sol"; + +/** + * @title Graph Managed contract + * @dev The Managed contract provides an interface to interact with the Controller. + * It also provides local caching for contract addresses. This mechanism relies on calling the + * public `syncAllContracts()` function whenever a contract changes in the controller. + * + * Inspired by Livepeer: + * https://github.com/livepeer/protocol/blob/streamflow/contracts/Controller.sol + */ +abstract contract Managed is IManaged { + // -- State -- + + /// Controller that manages this contract + IController public override controller; + /// @dev Cache for the addresses of the contracts retrieved from the controller + mapping(bytes32 => address) private _addressCache; + /// @dev Gap for future storage variables + uint256[10] private __gap; + + // Immutables + bytes32 private immutable CURATION = keccak256("Curation"); + bytes32 private immutable EPOCH_MANAGER = keccak256("EpochManager"); + bytes32 private immutable REWARDS_MANAGER = keccak256("RewardsManager"); + bytes32 private immutable STAKING = keccak256("Staking"); + bytes32 private immutable GRAPH_TOKEN = keccak256("GraphToken"); + bytes32 private immutable GRAPH_TOKEN_GATEWAY = keccak256("GraphTokenGateway"); + bytes32 private immutable GNS = keccak256("GNS"); + + // -- Events -- + + /// Emitted when a contract parameter has been updated + event ParameterUpdated(string param); + /// Emitted when the controller address has been set + event SetController(address controller); + + /// Emitted when contract with `nameHash` is synced to `contractAddress`. + event ContractSynced(bytes32 indexed nameHash, address contractAddress); + + // -- Modifiers -- + + /** + * @dev Revert if the controller is paused or partially paused + */ + function _notPartialPaused() internal view { + require(!controller.paused(), "Paused"); + require(!controller.partialPaused(), "Partial-paused"); + } + + /** + * @dev Revert if the controller is paused + */ + function _notPaused() internal view virtual { + require(!controller.paused(), "Paused"); + } + + /** + * @dev Revert if the caller is not the governor + */ + function _onlyGovernor() internal view { + require(msg.sender == controller.getGovernor(), "Only Controller governor"); + } + + /** + * @dev Revert if the caller is not the Controller + */ + function _onlyController() internal view { + require(msg.sender == address(controller), "Caller must be Controller"); + } + + /** + * @dev Revert if the controller is paused or partially paused + */ + modifier notPartialPaused() { + _notPartialPaused(); + _; + } + + /** + * @dev Revert if the controller is paused + */ + modifier notPaused() { + _notPaused(); + _; + } + + /** + * @dev Revert if the caller is not the Controller + */ + modifier onlyController() { + _onlyController(); + _; + } + + /** + * @dev Revert if the caller is not the governor + */ + modifier onlyGovernor() { + _onlyGovernor(); + _; + } + + // -- Functions -- + + /** + * @dev Initialize a Managed contract + * @param _controller Address for the Controller that manages this contract + */ + function _initialize(address _controller) internal { + _setController(_controller); + } + + /** + * @notice Set Controller. Only callable by current controller. + * @param _controller Controller contract address + */ + function setController(address _controller) external override onlyController { + _setController(_controller); + } + + /** + * @dev Set controller. + * @param _controller Controller contract address + */ + function _setController(address _controller) internal { + require(_controller != address(0), "Controller must be set"); + controller = IController(_controller); + emit SetController(_controller); + } + + /** + * @dev Return Curation interface + * @return Curation contract registered with Controller + */ + function curation() internal view returns (ICuration) { + return ICuration(_resolveContract(CURATION)); + } + + /** + * @dev Return EpochManager interface + * @return Epoch manager contract registered with Controller + */ + function epochManager() internal view returns (IEpochManager) { + return IEpochManager(_resolveContract(EPOCH_MANAGER)); + } + + /** + * @dev Return RewardsManager interface + * @return Rewards manager contract registered with Controller + */ + function rewardsManager() internal view returns (IRewardsManager) { + return IRewardsManager(_resolveContract(REWARDS_MANAGER)); + } + + /** + * @dev Return Staking interface + * @return Staking contract registered with Controller + */ + function staking() internal view returns (IStaking) { + return IStaking(_resolveContract(STAKING)); + } + + /** + * @dev Return GraphToken interface + * @return Graph token contract registered with Controller + */ + function graphToken() internal view returns (IGraphToken) { + return IGraphToken(_resolveContract(GRAPH_TOKEN)); + } + + /** + * @dev Return GraphTokenGateway (L1 or L2) interface + * @return Graph token gateway contract registered with Controller + */ + function graphTokenGateway() internal view returns (ITokenGateway) { + return ITokenGateway(_resolveContract(GRAPH_TOKEN_GATEWAY)); + } + + /** + * @dev Return GNS (L1 or L2) interface. + * @return Address of the GNS contract registered with Controller, as an IGNS interface. + */ + function gns() internal view returns (IGNS) { + return IGNS(_resolveContract(GNS)); + } + + /** + * @dev Resolve a contract address from the cache or the Controller if not found. + * @param _nameHash keccak256 hash of the contract name + * @return Address of the contract + */ + function _resolveContract(bytes32 _nameHash) internal view returns (address) { + address contractAddress = _addressCache[_nameHash]; + if (contractAddress == address(0)) { + contractAddress = controller.getContractProxy(_nameHash); + } + return contractAddress; + } + + /** + * @dev Cache a contract address from the Controller registry. + * @param _nameHash keccak256 hash of the name of the contract to sync into the cache + */ + function _syncContract(bytes32 _nameHash) internal { + address contractAddress = controller.getContractProxy(_nameHash); + if (_addressCache[_nameHash] != contractAddress) { + _addressCache[_nameHash] = contractAddress; + emit ContractSynced(_nameHash, contractAddress); + } + } + + /** + * @notice Sync protocol contract addresses from the Controller registry + * @dev This function will cache all the contracts using the latest addresses + * Anyone can call the function whenever a Proxy contract change in the + * controller to ensure the protocol is using the latest version + */ + function syncAllContracts() external override { + _syncContract(CURATION); + _syncContract(EPOCH_MANAGER); + _syncContract(REWARDS_MANAGER); + _syncContract(STAKING); + _syncContract(GRAPH_TOKEN); + _syncContract(GRAPH_TOKEN_GATEWAY); + _syncContract(GNS); + } +} diff --git a/packages/legacy/verified/StakingExtension/sources/contracts/rewards/IRewardsManager.sol b/packages/legacy/verified/StakingExtension/sources/contracts/rewards/IRewardsManager.sol new file mode 100644 index 000000000..87d3c2455 --- /dev/null +++ b/packages/legacy/verified/StakingExtension/sources/contracts/rewards/IRewardsManager.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +interface IRewardsManager { + /** + * @dev Stores accumulated rewards and snapshots related to a particular SubgraphDeployment. + */ + struct Subgraph { + uint256 accRewardsForSubgraph; + uint256 accRewardsForSubgraphSnapshot; + uint256 accRewardsPerSignalSnapshot; + uint256 accRewardsPerAllocatedToken; + } + + // -- Config -- + + function setIssuancePerBlock(uint256 _issuancePerBlock) external; + + function setMinimumSubgraphSignal(uint256 _minimumSubgraphSignal) external; + + // -- Denylist -- + + function setSubgraphAvailabilityOracle(address _subgraphAvailabilityOracle) external; + + function setDenied(bytes32 _subgraphDeploymentID, bool _deny) external; + + function setDeniedMany(bytes32[] calldata _subgraphDeploymentID, bool[] calldata _deny) + external; + + function isDenied(bytes32 _subgraphDeploymentID) external view returns (bool); + + // -- Getters -- + + function getNewRewardsPerSignal() external view returns (uint256); + + function getAccRewardsPerSignal() external view returns (uint256); + + function getAccRewardsForSubgraph(bytes32 _subgraphDeploymentID) + external + view + returns (uint256); + + function getAccRewardsPerAllocatedToken(bytes32 _subgraphDeploymentID) + external + view + returns (uint256, uint256); + + function getRewards(address _allocationID) external view returns (uint256); + + // -- Updates -- + + function updateAccRewardsPerSignal() external returns (uint256); + + function takeRewards(address _allocationID) external returns (uint256); + + // -- Hooks -- + + function onSubgraphSignalUpdate(bytes32 _subgraphDeploymentID) external returns (uint256); + + function onSubgraphAllocationUpdate(bytes32 _subgraphDeploymentID) external returns (uint256); +} diff --git a/packages/legacy/verified/StakingExtension/sources/contracts/staking/IStaking.sol b/packages/legacy/verified/StakingExtension/sources/contracts/staking/IStaking.sol new file mode 100644 index 000000000..d730ef806 --- /dev/null +++ b/packages/legacy/verified/StakingExtension/sources/contracts/staking/IStaking.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; +pragma abicoder v2; + +import { IStakingBase } from "./IStakingBase.sol"; +import { IStakingExtension } from "./IStakingExtension.sol"; +import { IMulticall } from "../base/IMulticall.sol"; +import { IManaged } from "../governance/IManaged.sol"; + +/** + * @title Interface for the Staking contract + * @notice This is the interface that should be used when interacting with the Staking contract. + * @dev Note that Staking doesn't actually inherit this interface. This is because of + * the custom setup of the Staking contract where part of the functionality is implemented + * in a separate contract (StakingExtension) to which calls are delegated through the fallback function. + */ +interface IStaking is IStakingBase, IStakingExtension, IMulticall, IManaged { + // Nothing to see here +} diff --git a/packages/legacy/verified/StakingExtension/sources/contracts/staking/IStakingBase.sol b/packages/legacy/verified/StakingExtension/sources/contracts/staking/IStakingBase.sol new file mode 100644 index 000000000..acc8ac678 --- /dev/null +++ b/packages/legacy/verified/StakingExtension/sources/contracts/staking/IStakingBase.sol @@ -0,0 +1,402 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; +pragma abicoder v2; + +import { IStakingData } from "./IStakingData.sol"; + +/** + * @title Base interface for the Staking contract. + * @dev This interface includes only what's implemented in the base Staking contract. + * It does not include the L1 and L2 specific functionality. It also does not include + * several functions that are implemented in the StakingExtension contract, and are called + * via delegatecall through the fallback function. See IStaking.sol for an interface + * that includes the full functionality. + */ +interface IStakingBase is IStakingData { + /** + * @dev Emitted when `indexer` stakes `tokens` amount. + */ + event StakeDeposited(address indexed indexer, uint256 tokens); + + /** + * @dev Emitted when `indexer` unstaked and locked `tokens` amount until `until` block. + */ + event StakeLocked(address indexed indexer, uint256 tokens, uint256 until); + + /** + * @dev Emitted when `indexer` withdrew `tokens` staked. + */ + event StakeWithdrawn(address indexed indexer, uint256 tokens); + + /** + * @dev Emitted when `indexer` allocated `tokens` amount to `subgraphDeploymentID` + * during `epoch`. + * `allocationID` indexer derived address used to identify the allocation. + * `metadata` additional information related to the allocation. + */ + event AllocationCreated( + address indexed indexer, + bytes32 indexed subgraphDeploymentID, + uint256 epoch, + uint256 tokens, + address indexed allocationID, + bytes32 metadata + ); + + /** + * @dev Emitted when `indexer` close an allocation in `epoch` for `allocationID`. + * An amount of `tokens` get unallocated from `subgraphDeploymentID`. + * This event also emits the POI (proof of indexing) submitted by the indexer. + * `isPublic` is true if the sender was someone other than the indexer. + */ + event AllocationClosed( + address indexed indexer, + bytes32 indexed subgraphDeploymentID, + uint256 epoch, + uint256 tokens, + address indexed allocationID, + address sender, + bytes32 poi, + bool isPublic + ); + + /** + * @dev Emitted when `indexer` collects a rebate on `subgraphDeploymentID` for `allocationID`. + * `epoch` is the protocol epoch the rebate was collected on + * The rebate is for `tokens` amount which are being provided by `assetHolder`; `queryFees` + * is the amount up for rebate after `curationFees` are distributed and `protocolTax` is burnt. + * `queryRebates` is the amount distributed to the `indexer` with `delegationFees` collected + * and sent to the delegation pool. + */ + event RebateCollected( + address assetHolder, + address indexed indexer, + bytes32 indexed subgraphDeploymentID, + address indexed allocationID, + uint256 epoch, + uint256 tokens, + uint256 protocolTax, + uint256 curationFees, + uint256 queryFees, + uint256 queryRebates, + uint256 delegationRewards + ); + + /** + * @dev Emitted when `indexer` update the delegation parameters for its delegation pool. + */ + event DelegationParametersUpdated( + address indexed indexer, + uint32 indexingRewardCut, + uint32 queryFeeCut, + uint32 cooldownBlocks + ); + + /** + * @dev Emitted when `caller` set `assetHolder` address as `allowed` to send funds + * to staking contract. + */ + event AssetHolderUpdate(address indexed caller, address indexed assetHolder, bool allowed); + + /** + * @dev Emitted when `indexer` set `operator` access. + */ + event SetOperator(address indexed indexer, address indexed operator, bool allowed); + + /** + * @dev Emitted when `indexer` set an address to receive rewards. + */ + event SetRewardsDestination(address indexed indexer, address indexed destination); + + /** + * @dev Emitted when `extensionImpl` was set as the address of the StakingExtension contract + * to which extended functionality is delegated. + */ + event ExtensionImplementationSet(address indexed extensionImpl); + + /** + * @dev Possible states an allocation can be. + * States: + * - Null = indexer == address(0) + * - Active = not Null && tokens > 0 + * - Closed = Active && closedAtEpoch != 0 + */ + enum AllocationState { + Null, + Active, + Closed + } + + /** + * @notice Initialize this contract. + * @param _controller Address of the controller that manages this contract + * @param _minimumIndexerStake Minimum amount of tokens that an indexer must stake + * @param _thawingPeriod Number of blocks that tokens get locked after unstaking + * @param _protocolPercentage Percentage of query fees that are burned as protocol fee (in PPM) + * @param _curationPercentage Percentage of query fees that are given to curators (in PPM) + * @param _maxAllocationEpochs The maximum number of epochs that an allocation can be active + * @param _delegationUnbondingPeriod The period in epochs that tokens get locked after undelegating + * @param _delegationRatio The ratio between an indexer's own stake and the delegation they can use + * @param _rebatesParameters Alpha and lambda parameters for rebates function + * @param _extensionImpl Address of the StakingExtension implementation + */ + function initialize( + address _controller, + uint256 _minimumIndexerStake, + uint32 _thawingPeriod, + uint32 _protocolPercentage, + uint32 _curationPercentage, + uint32 _maxAllocationEpochs, + uint32 _delegationUnbondingPeriod, + uint32 _delegationRatio, + RebatesParameters calldata _rebatesParameters, + address _extensionImpl + ) external; + + /** + * @notice Set the address of the StakingExtension implementation. + * @dev This function can only be called by the governor. + * @param _extensionImpl Address of the StakingExtension implementation + */ + function setExtensionImpl(address _extensionImpl) external; + + /** + * @notice Set the address of the counterpart (L1 or L2) staking contract. + * @dev This function can only be called by the governor. + * @param _counterpart Address of the counterpart staking contract in the other chain, without any aliasing. + */ + function setCounterpartStakingAddress(address _counterpart) external; + + /** + * @notice Set the minimum stake needed to be an Indexer + * @dev This function can only be called by the governor. + * @param _minimumIndexerStake Minimum amount of tokens that an indexer must stake + */ + function setMinimumIndexerStake(uint256 _minimumIndexerStake) external; + + /** + * @notice Set the number of blocks that tokens get locked after unstaking + * @dev This function can only be called by the governor. + * @param _thawingPeriod Number of blocks that tokens get locked after unstaking + */ + function setThawingPeriod(uint32 _thawingPeriod) external; + + /** + * @notice Set the curation percentage of query fees sent to curators. + * @dev This function can only be called by the governor. + * @param _percentage Percentage of query fees sent to curators + */ + function setCurationPercentage(uint32 _percentage) external; + + /** + * @notice Set a protocol percentage to burn when collecting query fees. + * @dev This function can only be called by the governor. + * @param _percentage Percentage of query fees to burn as protocol fee + */ + function setProtocolPercentage(uint32 _percentage) external; + + /** + * @notice Set the max time allowed for indexers to allocate on a subgraph + * before others are allowed to close the allocation. + * @dev This function can only be called by the governor. + * @param _maxAllocationEpochs Allocation duration limit in epochs + */ + function setMaxAllocationEpochs(uint32 _maxAllocationEpochs) external; + + /** + * @notice Set the rebate parameters + * @dev This function can only be called by the governor. + * @param _alphaNumerator Numerator of `alpha` + * @param _alphaDenominator Denominator of `alpha` + * @param _lambdaNumerator Numerator of `lambda` + * @param _lambdaDenominator Denominator of `lambda` + */ + function setRebateParameters( + uint32 _alphaNumerator, + uint32 _alphaDenominator, + uint32 _lambdaNumerator, + uint32 _lambdaDenominator + ) external; + + /** + * @notice Set an address as allowed asset holder. + * @dev This function can only be called by the governor. + * @param _assetHolder Address of allowed source for state channel funds + * @param _allowed True if asset holder is allowed + */ + function setAssetHolder(address _assetHolder, bool _allowed) external; + + /** + * @notice Authorize or unauthorize an address to be an operator for the caller. + * @param _operator Address to authorize or unauthorize + * @param _allowed Whether the operator is authorized or not + */ + function setOperator(address _operator, bool _allowed) external; + + /** + * @notice Deposit tokens on the indexer's stake. + * The amount staked must be over the minimumIndexerStake. + * @param _tokens Amount of tokens to stake + */ + function stake(uint256 _tokens) external; + + /** + * @notice Deposit tokens on the Indexer stake, on behalf of the Indexer. + * The amount staked must be over the minimumIndexerStake. + * @param _indexer Address of the indexer + * @param _tokens Amount of tokens to stake + */ + function stakeTo(address _indexer, uint256 _tokens) external; + + /** + * @notice Unstake tokens from the indexer stake, lock them until the thawing period expires. + * @dev NOTE: The function accepts an amount greater than the currently staked tokens. + * If that happens, it will try to unstake the max amount of tokens it can. + * The reason for this behaviour is to avoid time conditions while the transaction + * is in flight. + * @param _tokens Amount of tokens to unstake + */ + function unstake(uint256 _tokens) external; + + /** + * @notice Withdraw indexer tokens once the thawing period has passed. + */ + function withdraw() external; + + /** + * @notice Set the destination where to send rewards for an indexer. + * @param _destination Rewards destination address. If set to zero, rewards will be restaked + */ + function setRewardsDestination(address _destination) external; + + /** + * @notice Set the delegation parameters for the caller. + * @param _indexingRewardCut Percentage of indexing rewards left for the indexer + * @param _queryFeeCut Percentage of query fees left for the indexer + * @param _cooldownBlocks Period that need to pass to update delegation parameters + */ + function setDelegationParameters( + uint32 _indexingRewardCut, + uint32 _queryFeeCut, + uint32 _cooldownBlocks + ) external; + + /** + * @notice Allocate available tokens to a subgraph deployment. + * @param _subgraphDeploymentID ID of the SubgraphDeployment where tokens will be allocated + * @param _tokens Amount of tokens to allocate + * @param _allocationID The allocation identifier + * @param _metadata IPFS hash for additional information about the allocation + * @param _proof A 65-bytes Ethereum signed message of `keccak256(indexerAddress,allocationID)` + */ + function allocate( + bytes32 _subgraphDeploymentID, + uint256 _tokens, + address _allocationID, + bytes32 _metadata, + bytes calldata _proof + ) external; + + /** + * @notice Allocate available tokens to a subgraph deployment from and indexer's stake. + * The caller must be the indexer or the indexer's operator. + * @param _indexer Indexer address to allocate funds from. + * @param _subgraphDeploymentID ID of the SubgraphDeployment where tokens will be allocated + * @param _tokens Amount of tokens to allocate + * @param _allocationID The allocation identifier + * @param _metadata IPFS hash for additional information about the allocation + * @param _proof A 65-bytes Ethereum signed message of `keccak256(indexerAddress,allocationID)` + */ + function allocateFrom( + address _indexer, + bytes32 _subgraphDeploymentID, + uint256 _tokens, + address _allocationID, + bytes32 _metadata, + bytes calldata _proof + ) external; + + /** + * @notice Close an allocation and free the staked tokens. + * To be eligible for rewards a proof of indexing must be presented. + * Presenting a bad proof is subject to slashable condition. + * To opt out of rewards set _poi to 0x0 + * @param _allocationID The allocation identifier + * @param _poi Proof of indexing submitted for the allocated period + */ + function closeAllocation(address _allocationID, bytes32 _poi) external; + + /** + * @notice Collect query fees from state channels and assign them to an allocation. + * Funds received are only accepted from a valid sender. + * @dev To avoid reverting on the withdrawal from channel flow this function will: + * 1) Accept calls with zero tokens. + * 2) Accept calls after an allocation passed the dispute period, in that case, all + * the received tokens are burned. + * @param _tokens Amount of tokens to collect + * @param _allocationID Allocation where the tokens will be assigned + */ + function collect(uint256 _tokens, address _allocationID) external; + + /** + * @notice Return true if operator is allowed for indexer. + * @param _operator Address of the operator + * @param _indexer Address of the indexer + * @return True if operator is allowed for indexer, false otherwise + */ + function isOperator(address _operator, address _indexer) external view returns (bool); + + /** + * @notice Getter that returns if an indexer has any stake. + * @param _indexer Address of the indexer + * @return True if indexer has staked tokens + */ + function hasStake(address _indexer) external view returns (bool); + + /** + * @notice Get the total amount of tokens staked by the indexer. + * @param _indexer Address of the indexer + * @return Amount of tokens staked by the indexer + */ + function getIndexerStakedTokens(address _indexer) external view returns (uint256); + + /** + * @notice Get the total amount of tokens available to use in allocations. + * This considers the indexer stake and delegated tokens according to delegation ratio + * @param _indexer Address of the indexer + * @return Amount of tokens available to allocate including delegation + */ + function getIndexerCapacity(address _indexer) external view returns (uint256); + + /** + * @notice Return the allocation by ID. + * @param _allocationID Address used as allocation identifier + * @return Allocation data + */ + function getAllocation(address _allocationID) external view returns (Allocation memory); + + /** + * @notice Return the current state of an allocation + * @param _allocationID Allocation identifier + * @return AllocationState enum with the state of the allocation + */ + function getAllocationState(address _allocationID) external view returns (AllocationState); + + /** + * @notice Return if allocationID is used. + * @param _allocationID Address used as signer by the indexer for an allocation + * @return True if allocationID already used + */ + function isAllocation(address _allocationID) external view returns (bool); + + /** + * @notice Return the total amount of tokens allocated to subgraph. + * @param _subgraphDeploymentID Deployment ID for the subgraph + * @return Total tokens allocated to subgraph + */ + function getSubgraphAllocatedTokens(bytes32 _subgraphDeploymentID) + external + view + returns (uint256); +} diff --git a/packages/legacy/verified/StakingExtension/sources/contracts/staking/IStakingData.sol b/packages/legacy/verified/StakingExtension/sources/contracts/staking/IStakingData.sol new file mode 100644 index 000000000..39ca8f3e9 --- /dev/null +++ b/packages/legacy/verified/StakingExtension/sources/contracts/staking/IStakingData.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; + +/** + * @title Staking Data interface + * @dev This interface defines some structures used by the Staking contract. + */ +interface IStakingData { + /** + * @dev Allocate GRT tokens for the purpose of serving queries of a subgraph deployment + * An allocation is created in the allocate() function and closed in closeAllocation() + */ + struct Allocation { + address indexer; + bytes32 subgraphDeploymentID; + uint256 tokens; // Tokens allocated to a SubgraphDeployment + uint256 createdAtEpoch; // Epoch when it was created + uint256 closedAtEpoch; // Epoch when it was closed + uint256 collectedFees; // Collected fees for the allocation + uint256 __DEPRECATED_effectiveAllocation; // Effective allocation when closed + uint256 accRewardsPerAllocatedToken; // Snapshot used for reward calc + uint256 distributedRebates; // Collected rebates that have been rebated + } + + // -- Delegation Data -- + + /** + * @dev Delegation pool information. One per indexer. + */ + struct DelegationPool { + uint32 cooldownBlocks; // Blocks to wait before updating parameters + uint32 indexingRewardCut; // in PPM + uint32 queryFeeCut; // in PPM + uint256 updatedAtBlock; // Block when the pool was last updated + uint256 tokens; // Total tokens as pool reserves + uint256 shares; // Total shares minted in the pool + mapping(address => Delegation) delegators; // Mapping of delegator => Delegation + } + + /** + * @dev Individual delegation data of a delegator in a pool. + */ + struct Delegation { + uint256 shares; // Shares owned by a delegator in the pool + uint256 tokensLocked; // Tokens locked for undelegation + uint256 tokensLockedUntil; // Block when locked tokens can be withdrawn + } + + /** + * @dev Rebates parameters. Used to avoid stack too deep errors in Staking initialize function. + */ + struct RebatesParameters { + uint32 alphaNumerator; + uint32 alphaDenominator; + uint32 lambdaNumerator; + uint32 lambdaDenominator; + } +} diff --git a/packages/legacy/verified/StakingExtension/sources/contracts/staking/IStakingExtension.sol b/packages/legacy/verified/StakingExtension/sources/contracts/staking/IStakingExtension.sol new file mode 100644 index 000000000..815577e2f --- /dev/null +++ b/packages/legacy/verified/StakingExtension/sources/contracts/staking/IStakingExtension.sol @@ -0,0 +1,344 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.6.12 <0.8.0; +pragma abicoder v2; + +import { IStakingData } from "./IStakingData.sol"; +import { Stakes } from "./libs/Stakes.sol"; + +/** + * @title Interface for the StakingExtension contract + * @dev This interface defines the events and functions implemented + * in the StakingExtension contract, which is used to extend the functionality + * of the Staking contract while keeping it within the 24kB mainnet size limit. + * In particular, this interface includes delegation functions and various storage + * getters. + */ +interface IStakingExtension is IStakingData { + /** + * @dev DelegationPool struct as returned by delegationPools(), since + * the original DelegationPool in IStakingData.sol contains a nested mapping. + */ + struct DelegationPoolReturn { + uint32 cooldownBlocks; // Blocks to wait before updating parameters + uint32 indexingRewardCut; // in PPM + uint32 queryFeeCut; // in PPM + uint256 updatedAtBlock; // Block when the pool was last updated + uint256 tokens; // Total tokens as pool reserves + uint256 shares; // Total shares minted in the pool + } + + /** + * @dev Emitted when `delegator` delegated `tokens` to the `indexer`, the delegator + * gets `shares` for the delegation pool proportionally to the tokens staked. + */ + event StakeDelegated( + address indexed indexer, + address indexed delegator, + uint256 tokens, + uint256 shares + ); + + /** + * @dev Emitted when `delegator` undelegated `tokens` from `indexer`. + * Tokens get locked for withdrawal after a period of time. + */ + event StakeDelegatedLocked( + address indexed indexer, + address indexed delegator, + uint256 tokens, + uint256 shares, + uint256 until + ); + + /** + * @dev Emitted when `delegator` withdrew delegated `tokens` from `indexer`. + */ + event StakeDelegatedWithdrawn( + address indexed indexer, + address indexed delegator, + uint256 tokens + ); + + /** + * @dev Emitted when `indexer` was slashed for a total of `tokens` amount. + * Tracks `reward` amount of tokens given to `beneficiary`. + */ + event StakeSlashed( + address indexed indexer, + uint256 tokens, + uint256 reward, + address beneficiary + ); + + /** + * @dev Emitted when `caller` set `slasher` address as `allowed` to slash stakes. + */ + event SlasherUpdate(address indexed caller, address indexed slasher, bool allowed); + + /** + * @notice Set the delegation ratio. + * If set to 10 it means the indexer can use up to 10x the indexer staked amount + * from their delegated tokens + * @dev This function is only callable by the governor + * @param _delegationRatio Delegation capacity multiplier + */ + function setDelegationRatio(uint32 _delegationRatio) external; + + /** + * @notice Set the minimum time in blocks an indexer needs to wait to change delegation parameters. + * Indexers can set a custom amount time for their own cooldown, but it must be greater than this. + * @dev This function is only callable by the governor + * @param _blocks Number of blocks to set the delegation parameters cooldown period + */ + function setDelegationParametersCooldown(uint32 _blocks) external; + + /** + * @notice Set the time, in epochs, a Delegator needs to wait to withdraw tokens after undelegating. + * @dev This function is only callable by the governor + * @param _delegationUnbondingPeriod Period in epochs to wait for token withdrawals after undelegating + */ + function setDelegationUnbondingPeriod(uint32 _delegationUnbondingPeriod) external; + + /** + * @notice Set a delegation tax percentage to burn when delegated funds are deposited. + * @dev This function is only callable by the governor + * @param _percentage Percentage of delegated tokens to burn as delegation tax, expressed in parts per million + */ + function setDelegationTaxPercentage(uint32 _percentage) external; + + /** + * @notice Set or unset an address as allowed slasher. + * @dev This function can only be called by the governor. + * @param _slasher Address of the party allowed to slash indexers + * @param _allowed True if slasher is allowed + */ + function setSlasher(address _slasher, bool _allowed) external; + + /** + * @notice Delegate tokens to an indexer. + * @param _indexer Address of the indexer to which tokens are delegated + * @param _tokens Amount of tokens to delegate + * @return Amount of shares issued from the delegation pool + */ + function delegate(address _indexer, uint256 _tokens) external returns (uint256); + + /** + * @notice Undelegate tokens from an indexer. Tokens will be locked for the unbonding period. + * @param _indexer Address of the indexer to which tokens had been delegated + * @param _shares Amount of shares to return and undelegate tokens + * @return Amount of tokens returned for the shares of the delegation pool + */ + function undelegate(address _indexer, uint256 _shares) external returns (uint256); + + /** + * @notice Withdraw undelegated tokens once the unbonding period has passed, and optionally + * re-delegate to a new indexer. + * @param _indexer Withdraw available tokens delegated to indexer + * @param _newIndexer Re-delegate to indexer address if non-zero, withdraw if zero address + */ + function withdrawDelegated(address _indexer, address _newIndexer) external returns (uint256); + + /** + * @notice Slash the indexer stake. Delegated tokens are not subject to slashing. + * @dev Can only be called by the slasher role. + * @param _indexer Address of indexer to slash + * @param _tokens Amount of tokens to slash from the indexer stake + * @param _reward Amount of reward tokens to send to a beneficiary + * @param _beneficiary Address of a beneficiary to receive a reward for the slashing + */ + function slash( + address _indexer, + uint256 _tokens, + uint256 _reward, + address _beneficiary + ) external; + + /** + * @notice Return the delegation from a delegator to an indexer. + * @param _indexer Address of the indexer where funds have been delegated + * @param _delegator Address of the delegator + * @return Delegation data + */ + function getDelegation(address _indexer, address _delegator) + external + view + returns (Delegation memory); + + /** + * @notice Return whether the delegator has delegated to the indexer. + * @param _indexer Address of the indexer where funds have been delegated + * @param _delegator Address of the delegator + * @return True if delegator has tokens delegated to the indexer + */ + function isDelegator(address _indexer, address _delegator) external view returns (bool); + + /** + * @notice Returns amount of delegated tokens ready to be withdrawn after unbonding period. + * @param _delegation Delegation of tokens from delegator to indexer + * @return Amount of tokens to withdraw + */ + function getWithdraweableDelegatedTokens(Delegation memory _delegation) + external + view + returns (uint256); + + /** + * @notice Getter for the delegationRatio, i.e. the delegation capacity multiplier: + * If delegation ratio is 100, and an Indexer has staked 5 GRT, + * then they can use up to 500 GRT from the delegated stake + * @return Delegation ratio + */ + function delegationRatio() external view returns (uint32); + + /** + * @notice Getter for delegationParametersCooldown: + * Minimum time in blocks an indexer needs to wait to change delegation parameters + * @return Delegation parameters cooldown in blocks + */ + function delegationParametersCooldown() external view returns (uint32); + + /** + * @notice Getter for delegationUnbondingPeriod: + * Time in epochs a delegator needs to wait to withdraw delegated stake + * @return Delegation unbonding period in epochs + */ + function delegationUnbondingPeriod() external view returns (uint32); + + /** + * @notice Getter for delegationTaxPercentage: + * Percentage of tokens to tax a delegation deposit, expressed in parts per million + * @return Delegation tax percentage in parts per million + */ + function delegationTaxPercentage() external view returns (uint32); + + /** + * @notice Getter for delegationPools[_indexer]: + * gets the delegation pool structure for a particular indexer. + * @param _indexer Address of the indexer for which to query the delegation pool + * @return Delegation pool as a DelegationPoolReturn struct + */ + function delegationPools(address _indexer) external view returns (DelegationPoolReturn memory); + + /** + * @notice Getter for operatorAuth[_indexer][_maybeOperator]: + * returns true if the operator is authorized to operate on behalf of the indexer. + * @param _indexer The indexer address for which to query authorization + * @param _maybeOperator The address that may or may not be an operator + * @return True if the operator is authorized to operate on behalf of the indexer + */ + function operatorAuth(address _indexer, address _maybeOperator) external view returns (bool); + + /** + * @notice Getter for rewardsDestination[_indexer]: + * returns the address where the indexer's rewards are sent. + * @param _indexer The indexer address for which to query the rewards destination + * @return The address where the indexer's rewards are sent, zero if none is set in which case rewards are re-staked + */ + function rewardsDestination(address _indexer) external view returns (address); + + /** + * @notice Getter for assetHolders[_maybeAssetHolder]: + * returns true if the address is an asset holder, i.e. an entity that can collect + * query fees into the Staking contract. + * @param _maybeAssetHolder The address that may or may not be an asset holder + * @return True if the address is an asset holder + */ + function assetHolders(address _maybeAssetHolder) external view returns (bool); + + /** + * @notice Getter for subgraphAllocations[_subgraphDeploymentId]: + * returns the amount of tokens allocated to a subgraph deployment. + * @param _subgraphDeploymentId The subgraph deployment for which to query the allocations + * @return The amount of tokens allocated to the subgraph deployment + */ + function subgraphAllocations(bytes32 _subgraphDeploymentId) external view returns (uint256); + + /** + * @notice Getter for slashers[_maybeSlasher]: + * returns true if the address is a slasher, i.e. an entity that can slash indexers + * @param _maybeSlasher Address for which to check the slasher role + * @return True if the address is a slasher + */ + function slashers(address _maybeSlasher) external view returns (bool); + + /** + * @notice Getter for minimumIndexerStake: the minimum + * amount of GRT that an indexer needs to stake. + * @return Minimum indexer stake in GRT + */ + function minimumIndexerStake() external view returns (uint256); + + /** + * @notice Getter for thawingPeriod: the time in blocks an + * indexer needs to wait to unstake tokens. + * @return Thawing period in blocks + */ + function thawingPeriod() external view returns (uint32); + + /** + * @notice Getter for curationPercentage: the percentage of + * query fees that are distributed to curators. + * @return Curation percentage in parts per million + */ + function curationPercentage() external view returns (uint32); + + /** + * @notice Getter for protocolPercentage: the percentage of + * query fees that are burned as protocol fees. + * @return Protocol percentage in parts per million + */ + function protocolPercentage() external view returns (uint32); + + /** + * @notice Getter for maxAllocationEpochs: the maximum time in epochs + * that an allocation can be open before anyone is allowed to close it. This + * also caps the effective allocation when sending the allocation's query fees + * to the rebate pool. + * @return Maximum allocation period in epochs + */ + function maxAllocationEpochs() external view returns (uint32); + + /** + * @notice Getter for the numerator of the rebates alpha parameter + * @return Alpha numerator + */ + function alphaNumerator() external view returns (uint32); + + /** + * @notice Getter for the denominator of the rebates alpha parameter + * @return Alpha denominator + */ + function alphaDenominator() external view returns (uint32); + + /** + * @notice Getter for the numerator of the rebates lambda parameter + * @return Lambda numerator + */ + function lambdaNumerator() external view returns (uint32); + + /** + * @notice Getter for the denominator of the rebates lambda parameter + * @return Lambda denominator + */ + function lambdaDenominator() external view returns (uint32); + + /** + * @notice Getter for stakes[_indexer]: + * gets the stake information for an indexer as a Stakes.Indexer struct. + * @param _indexer Indexer address for which to query the stake information + * @return Stake information for the specified indexer, as a Stakes.Indexer struct + */ + function stakes(address _indexer) external view returns (Stakes.Indexer memory); + + /** + * @notice Getter for allocations[_allocationID]: + * gets an allocation's information as an IStakingData.Allocation struct. + * @param _allocationID Allocation ID for which to query the allocation information + * @return The specified allocation, as an IStakingData.Allocation struct + */ + function allocations(address _allocationID) + external + view + returns (IStakingData.Allocation memory); +} diff --git a/packages/legacy/verified/StakingExtension/sources/contracts/staking/StakingExtension.sol b/packages/legacy/verified/StakingExtension/sources/contracts/staking/StakingExtension.sol new file mode 100644 index 000000000..0667b89fb --- /dev/null +++ b/packages/legacy/verified/StakingExtension/sources/contracts/staking/StakingExtension.sol @@ -0,0 +1,682 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; +pragma abicoder v2; + +import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; +import { StakingV4Storage } from "./StakingStorage.sol"; +import { IStakingExtension } from "./IStakingExtension.sol"; +import { TokenUtils } from "../utils/TokenUtils.sol"; +import { IGraphToken } from "../token/IGraphToken.sol"; +import { GraphUpgradeable } from "../upgrades/GraphUpgradeable.sol"; +import { Stakes } from "./libs/Stakes.sol"; +import { IStakingData } from "./IStakingData.sol"; +import { MathUtils } from "./libs/MathUtils.sol"; + +/** + * @title StakingExtension contract + * @dev This contract provides the logic to manage delegations and other Staking + * extension features (e.g. storage getters). It is meant to be called through delegatecall from the + * Staking contract, and is only kept separate to keep the Staking contract size + * within limits. + */ +contract StakingExtension is StakingV4Storage, GraphUpgradeable, IStakingExtension { + using SafeMath for uint256; + using Stakes for Stakes.Indexer; + + /// @dev 100% in parts per million + uint32 private constant MAX_PPM = 1000000; + + /** + * @dev Check if the caller is the slasher. + */ + modifier onlySlasher() { + require(__slashers[msg.sender] == true, "!slasher"); + _; + } + + /** + * @notice Initialize the StakingExtension contract + * @dev This function is meant to be delegatecalled from the Staking contract's + * initialize() function, so it uses the same access control check to ensure it is + * being called by the Staking implementation as part of the proxy upgrade process. + * @param _delegationUnbondingPeriod Delegation unbonding period in blocks + * @param _cooldownBlocks Minimum time between changes to delegation parameters, in blocks + * @param _delegationRatio Delegation capacity multiplier (e.g. 10 means 10x the indexer stake) + * @param _delegationTaxPercentage Percentage of delegated tokens to burn as delegation tax, expressed in parts per million + */ + function initialize( + uint32 _delegationUnbondingPeriod, + uint32 _cooldownBlocks, + uint32 _delegationRatio, + uint32 _delegationTaxPercentage + ) external onlyImpl { + _setDelegationUnbondingPeriod(_delegationUnbondingPeriod); + _setDelegationParametersCooldown(_cooldownBlocks); + _setDelegationRatio(_delegationRatio); + _setDelegationTaxPercentage(_delegationTaxPercentage); + } + + /** + * @notice Set a delegation tax percentage to burn when delegated funds are deposited. + * @dev This function is only callable by the governor + * @param _percentage Percentage of delegated tokens to burn as delegation tax, expressed in parts per million + */ + function setDelegationTaxPercentage(uint32 _percentage) external override onlyGovernor { + _setDelegationTaxPercentage(_percentage); + } + + /** + * @notice Set the delegation ratio. + * If set to 10 it means the indexer can use up to 10x the indexer staked amount + * from their delegated tokens + * @dev This function is only callable by the governor + * @param _delegationRatio Delegation capacity multiplier + */ + function setDelegationRatio(uint32 _delegationRatio) external override onlyGovernor { + _setDelegationRatio(_delegationRatio); + } + + /** + * @notice Set the minimum time in blocks an indexer needs to wait to change delegation parameters. + * Indexers can set a custom amount time for their own cooldown, but it must be greater than this. + * @dev This function is only callable by the governor + * @param _blocks Number of blocks to set the delegation parameters cooldown period + */ + function setDelegationParametersCooldown(uint32 _blocks) external override onlyGovernor { + _setDelegationParametersCooldown(_blocks); + } + + /** + * @notice Set the time, in epochs, a Delegator needs to wait to withdraw tokens after undelegating. + * @dev This function is only callable by the governor + * @param _delegationUnbondingPeriod Period in epochs to wait for token withdrawals after undelegating + */ + function setDelegationUnbondingPeriod(uint32 _delegationUnbondingPeriod) + external + override + onlyGovernor + { + _setDelegationUnbondingPeriod(_delegationUnbondingPeriod); + } + + /** + * @notice Set or unset an address as allowed slasher. + * @param _slasher Address of the party allowed to slash indexers + * @param _allowed True if slasher is allowed + */ + function setSlasher(address _slasher, bool _allowed) external override onlyGovernor { + require(_slasher != address(0), "!slasher"); + __slashers[_slasher] = _allowed; + emit SlasherUpdate(msg.sender, _slasher, _allowed); + } + + /** + * @notice Delegate tokens to an indexer. + * @param _indexer Address of the indexer to which tokens are delegated + * @param _tokens Amount of tokens to delegate + * @return Amount of shares issued from the delegation pool + */ + function delegate(address _indexer, uint256 _tokens) + external + override + notPartialPaused + returns (uint256) + { + address delegator = msg.sender; + + // Transfer tokens to delegate to this contract + TokenUtils.pullTokens(graphToken(), delegator, _tokens); + + // Update state + return _delegate(delegator, _indexer, _tokens); + } + + /** + * @notice Undelegate tokens from an indexer. Tokens will be locked for the unbonding period. + * @param _indexer Address of the indexer to which tokens had been delegated + * @param _shares Amount of shares to return and undelegate tokens + * @return Amount of tokens returned for the shares of the delegation pool + */ + function undelegate(address _indexer, uint256 _shares) + external + override + notPartialPaused + returns (uint256) + { + return _undelegate(msg.sender, _indexer, _shares); + } + + /** + * @notice Withdraw undelegated tokens once the unbonding period has passed, and optionally + * re-delegate to a new indexer. + * @param _indexer Withdraw available tokens delegated to indexer + * @param _newIndexer Re-delegate to indexer address if non-zero, withdraw if zero address + */ + function withdrawDelegated(address _indexer, address _newIndexer) + external + override + notPaused + returns (uint256) + { + return _withdrawDelegated(msg.sender, _indexer, _newIndexer); + } + + /** + * @notice Slash the indexer stake. Delegated tokens are not subject to slashing. + * @dev Can only be called by the slasher role. + * @param _indexer Address of indexer to slash + * @param _tokens Amount of tokens to slash from the indexer stake + * @param _reward Amount of reward tokens to send to a beneficiary + * @param _beneficiary Address of a beneficiary to receive a reward for the slashing + */ + function slash( + address _indexer, + uint256 _tokens, + uint256 _reward, + address _beneficiary + ) external override onlySlasher notPartialPaused { + Stakes.Indexer storage indexerStake = __stakes[_indexer]; + + // Only able to slash a non-zero number of tokens + require(_tokens > 0, "!tokens"); + + // Rewards comes from tokens slashed balance + require(_tokens >= _reward, "rewards>slash"); + + // Cannot slash stake of an indexer without any or enough stake + require(indexerStake.tokensStaked > 0, "!stake"); + require(_tokens <= indexerStake.tokensStaked, "slash>stake"); + + // Validate beneficiary of slashed tokens + require(_beneficiary != address(0), "!beneficiary"); + + // Slashing more tokens than freely available (over allocation condition) + // Unlock locked tokens to avoid the indexer to withdraw them + if (_tokens > indexerStake.tokensAvailable() && indexerStake.tokensLocked > 0) { + uint256 tokensOverAllocated = _tokens.sub(indexerStake.tokensAvailable()); + uint256 tokensToUnlock = MathUtils.min(tokensOverAllocated, indexerStake.tokensLocked); + indexerStake.unlockTokens(tokensToUnlock); + } + + // Remove tokens to slash from the stake + indexerStake.release(_tokens); + + // -- Interactions -- + + IGraphToken graphToken = graphToken(); + + // Set apart the reward for the beneficiary and burn remaining slashed stake + TokenUtils.burnTokens(graphToken, _tokens.sub(_reward)); + + // Give the beneficiary a reward for slashing + TokenUtils.pushTokens(graphToken, _beneficiary, _reward); + + emit StakeSlashed(_indexer, _tokens, _reward, _beneficiary); + } + + /** + * @notice Return the delegation from a delegator to an indexer. + * @param _indexer Address of the indexer where funds have been delegated + * @param _delegator Address of the delegator + * @return Delegation data + */ + function getDelegation(address _indexer, address _delegator) + external + view + override + returns (Delegation memory) + { + return __delegationPools[_indexer].delegators[_delegator]; + } + + /** + * @notice Getter for the delegationRatio, i.e. the delegation capacity multiplier: + * If delegation ratio is 100, and an Indexer has staked 5 GRT, + * then they can use up to 500 GRT from the delegated stake + * @return Delegation ratio + */ + function delegationRatio() external view override returns (uint32) { + return __delegationRatio; + } + + /** + * @notice Getter for delegationParametersCooldown: + * Minimum time in blocks an indexer needs to wait to change delegation parameters + * @return Delegation parameters cooldown in blocks + */ + function delegationParametersCooldown() external view override returns (uint32) { + return __delegationParametersCooldown; + } + + /** + * @notice Getter for delegationUnbondingPeriod: + * Time in epochs a delegator needs to wait to withdraw delegated stake + * @return Delegation unbonding period in epochs + */ + function delegationUnbondingPeriod() external view override returns (uint32) { + return __delegationUnbondingPeriod; + } + + /** + * @notice Getter for delegationTaxPercentage: + * Percentage of tokens to tax a delegation deposit, expressed in parts per million + * @return Delegation tax percentage in parts per million + */ + function delegationTaxPercentage() external view override returns (uint32) { + return __delegationTaxPercentage; + } + + /** + * @notice Getter for delegationPools[_indexer]: + * gets the delegation pool structure for a particular indexer. + * @param _indexer Address of the indexer for which to query the delegation pool + * @return Delegation pool as a DelegationPoolReturn struct + */ + function delegationPools(address _indexer) + external + view + override + returns (DelegationPoolReturn memory) + { + DelegationPool storage pool = __delegationPools[_indexer]; + return + DelegationPoolReturn( + pool.cooldownBlocks, // Blocks to wait before updating parameters + pool.indexingRewardCut, // in PPM + pool.queryFeeCut, // in PPM + pool.updatedAtBlock, // Block when the pool was last updated + pool.tokens, // Total tokens as pool reserves + pool.shares // Total shares minted in the pool + ); + } + + /** + * @notice Getter for rewardsDestination[_indexer]: + * returns the address where the indexer's rewards are sent. + * @param _indexer The indexer address for which to query the rewards destination + * @return The address where the indexer's rewards are sent, zero if none is set in which case rewards are re-staked + */ + function rewardsDestination(address _indexer) external view override returns (address) { + return __rewardsDestination[_indexer]; + } + + /** + * @notice Getter for assetHolders[_maybeAssetHolder]: + * returns true if the address is an asset holder, i.e. an entity that can collect + * query fees into the Staking contract. + * @param _maybeAssetHolder The address that may or may not be an asset holder + * @return True if the address is an asset holder + */ + function assetHolders(address _maybeAssetHolder) external view override returns (bool) { + return __assetHolders[_maybeAssetHolder]; + } + + /** + * @notice Getter for operatorAuth[_indexer][_maybeOperator]: + * returns true if the operator is authorized to operate on behalf of the indexer. + * @param _indexer The indexer address for which to query authorization + * @param _maybeOperator The address that may or may not be an operator + * @return True if the operator is authorized to operate on behalf of the indexer + */ + function operatorAuth(address _indexer, address _maybeOperator) + external + view + override + returns (bool) + { + return __operatorAuth[_indexer][_maybeOperator]; + } + + /** + * @notice Getter for subgraphAllocations[_subgraphDeploymentId]: + * returns the amount of tokens allocated to a subgraph deployment. + * @param _subgraphDeploymentId The subgraph deployment for which to query the allocations + * @return The amount of tokens allocated to the subgraph deployment + */ + function subgraphAllocations(bytes32 _subgraphDeploymentId) + external + view + override + returns (uint256) + { + return __subgraphAllocations[_subgraphDeploymentId]; + } + + /** + * @notice Getter for slashers[_maybeSlasher]: + * returns true if the address is a slasher, i.e. an entity that can slash indexers + * @param _maybeSlasher Address for which to check the slasher role + * @return True if the address is a slasher + */ + function slashers(address _maybeSlasher) external view override returns (bool) { + return __slashers[_maybeSlasher]; + } + + /** + * @notice Getter for minimumIndexerStake: the minimum + * amount of GRT that an indexer needs to stake. + * @return Minimum indexer stake in GRT + */ + function minimumIndexerStake() external view override returns (uint256) { + return __minimumIndexerStake; + } + + /** + * @notice Getter for thawingPeriod: the time in blocks an + * indexer needs to wait to unstake tokens. + * @return Thawing period in blocks + */ + function thawingPeriod() external view override returns (uint32) { + return __thawingPeriod; + } + + /** + * @notice Getter for curationPercentage: the percentage of + * query fees that are distributed to curators. + * @return Curation percentage in parts per million + */ + function curationPercentage() external view override returns (uint32) { + return __curationPercentage; + } + + /** + * @notice Getter for protocolPercentage: the percentage of + * query fees that are burned as protocol fees. + * @return Protocol percentage in parts per million + */ + function protocolPercentage() external view override returns (uint32) { + return __protocolPercentage; + } + + /** + * @notice Getter for maxAllocationEpochs: the maximum time in epochs + * that an allocation can be open before anyone is allowed to close it. This + * also caps the effective allocation when sending the allocation's query fees + * to the rebate pool. + * @return Maximum allocation period in epochs + */ + function maxAllocationEpochs() external view override returns (uint32) { + return __maxAllocationEpochs; + } + + /** + * @notice Getter for the numerator of the rebates alpha parameter + * @return Alpha numerator + */ + function alphaNumerator() external view override returns (uint32) { + return __alphaNumerator; + } + + /** + * @notice Getter for the denominator of the rebates alpha parameter + * @return Alpha denominator + */ + function alphaDenominator() external view override returns (uint32) { + return __alphaDenominator; + } + + /** + * @notice Getter for the numerator of the rebates lambda parameter + * @return Lambda numerator + */ + function lambdaNumerator() external view override returns (uint32) { + return __lambdaNumerator; + } + + /** + * @notice Getter for the denominator of the rebates lambda parameter + * @return Lambda denominator + */ + function lambdaDenominator() external view override returns (uint32) { + return __lambdaDenominator; + } + + /** + * @notice Getter for stakes[_indexer]: + * gets the stake information for an indexer as a Stakes.Indexer struct. + * @param _indexer Indexer address for which to query the stake information + * @return Stake information for the specified indexer, as a Stakes.Indexer struct + */ + function stakes(address _indexer) external view override returns (Stakes.Indexer memory) { + return __stakes[_indexer]; + } + + /** + * @notice Getter for allocations[_allocationID]: + * gets an allocation's information as an IStakingData.Allocation struct. + * @param _allocationID Allocation ID for which to query the allocation information + * @return The specified allocation, as an IStakingData.Allocation struct + */ + function allocations(address _allocationID) + external + view + override + returns (IStakingData.Allocation memory) + { + return __allocations[_allocationID]; + } + + /** + * @notice Return whether the delegator has delegated to the indexer. + * @param _indexer Address of the indexer where funds have been delegated + * @param _delegator Address of the delegator + * @return True if delegator has tokens delegated to the indexer + */ + function isDelegator(address _indexer, address _delegator) public view override returns (bool) { + return __delegationPools[_indexer].delegators[_delegator].shares > 0; + } + + /** + * @notice Returns amount of delegated tokens ready to be withdrawn after unbonding period. + * @param _delegation Delegation of tokens from delegator to indexer + * @return Amount of tokens to withdraw + */ + function getWithdraweableDelegatedTokens(Delegation memory _delegation) + public + view + override + returns (uint256) + { + // There must be locked tokens and period passed + uint256 currentEpoch = epochManager().currentEpoch(); + if (_delegation.tokensLockedUntil > 0 && currentEpoch >= _delegation.tokensLockedUntil) { + return _delegation.tokensLocked; + } + return 0; + } + + /** + * @dev Internal: Set a delegation tax percentage to burn when delegated funds are deposited. + * @param _percentage Percentage of delegated tokens to burn as delegation tax + */ + function _setDelegationTaxPercentage(uint32 _percentage) private { + // Must be within 0% to 100% (inclusive) + require(_percentage <= MAX_PPM, ">percentage"); + __delegationTaxPercentage = _percentage; + emit ParameterUpdated("delegationTaxPercentage"); + } + + /** + * @dev Internal: Set the delegation ratio. + * If set to 10 it means the indexer can use up to 10x the indexer staked amount + * from their delegated tokens + * @param _delegationRatio Delegation capacity multiplier + */ + function _setDelegationRatio(uint32 _delegationRatio) private { + __delegationRatio = _delegationRatio; + emit ParameterUpdated("delegationRatio"); + } + + /** + * @dev Internal: Set the time in blocks an indexer needs to wait to change delegation parameters. + * @param _blocks Number of blocks to set the delegation parameters cooldown period + */ + function _setDelegationParametersCooldown(uint32 _blocks) private { + __delegationParametersCooldown = _blocks; + emit ParameterUpdated("delegationParametersCooldown"); + } + + /** + * @dev Internal: Set the period for undelegation of stake from indexer. + * @param _delegationUnbondingPeriod Period in epochs to wait for token withdrawals after undelegating + */ + function _setDelegationUnbondingPeriod(uint32 _delegationUnbondingPeriod) private { + require(_delegationUnbondingPeriod > 0, "!delegationUnbondingPeriod"); + __delegationUnbondingPeriod = _delegationUnbondingPeriod; + emit ParameterUpdated("delegationUnbondingPeriod"); + } + + /** + * @dev Delegate tokens to an indexer. + * @param _delegator Address of the delegator + * @param _indexer Address of the indexer to delegate tokens to + * @param _tokens Amount of tokens to delegate + * @return Amount of shares issued of the delegation pool + */ + function _delegate( + address _delegator, + address _indexer, + uint256 _tokens + ) private returns (uint256) { + // Only delegate a non-zero amount of tokens + require(_tokens > 0, "!tokens"); + // Only delegate to non-empty address + require(_indexer != address(0), "!indexer"); + // Only delegate to staked indexer + require(__stakes[_indexer].tokensStaked > 0, "!stake"); + + // Get the delegation pool of the indexer + DelegationPool storage pool = __delegationPools[_indexer]; + Delegation storage delegation = pool.delegators[_delegator]; + + // Collect delegation tax + uint256 delegationTax = _collectTax(graphToken(), _tokens, __delegationTaxPercentage); + uint256 delegatedTokens = _tokens.sub(delegationTax); + + // Calculate shares to issue + uint256 shares = (pool.tokens == 0) + ? delegatedTokens + : delegatedTokens.mul(pool.shares).div(pool.tokens); + require(shares > 0, "!shares"); + + // Update the delegation pool + pool.tokens = pool.tokens.add(delegatedTokens); + pool.shares = pool.shares.add(shares); + + // Update the individual delegation + delegation.shares = delegation.shares.add(shares); + + emit StakeDelegated(_indexer, _delegator, delegatedTokens, shares); + + return shares; + } + + /** + * @dev Undelegate tokens from an indexer. + * @param _delegator Address of the delegator + * @param _indexer Address of the indexer where tokens had been delegated + * @param _shares Amount of shares to return and undelegate tokens + * @return Amount of tokens returned for the shares of the delegation pool + */ + function _undelegate( + address _delegator, + address _indexer, + uint256 _shares + ) private returns (uint256) { + // Can only undelegate a non-zero amount of shares + require(_shares > 0, "!shares"); + + // Get the delegation pool of the indexer + DelegationPool storage pool = __delegationPools[_indexer]; + Delegation storage delegation = pool.delegators[_delegator]; + + // Delegator need to have enough shares in the pool to undelegate + require(delegation.shares >= _shares, "!shares-avail"); + + // Withdraw tokens if available + if (getWithdraweableDelegatedTokens(delegation) > 0) { + _withdrawDelegated(_delegator, _indexer, address(0)); + } + + // Calculate tokens to get in exchange for the shares + uint256 tokens = _shares.mul(pool.tokens).div(pool.shares); + + // Update the delegation pool + pool.tokens = pool.tokens.sub(tokens); + pool.shares = pool.shares.sub(_shares); + + // Update the delegation + delegation.shares = delegation.shares.sub(_shares); + delegation.tokensLocked = delegation.tokensLocked.add(tokens); + delegation.tokensLockedUntil = epochManager().currentEpoch().add( + __delegationUnbondingPeriod + ); + + emit StakeDelegatedLocked( + _indexer, + _delegator, + tokens, + _shares, + delegation.tokensLockedUntil + ); + + return tokens; + } + + /** + * @dev Withdraw delegated tokens once the unbonding period has passed. + * @param _delegator Delegator that is withdrawing tokens + * @param _indexer Withdraw available tokens delegated to indexer + * @param _delegateToIndexer Re-delegate to indexer address if non-zero, withdraw if zero address + * @return Amount of tokens withdrawn or re-delegated + */ + function _withdrawDelegated( + address _delegator, + address _indexer, + address _delegateToIndexer + ) private returns (uint256) { + // Get the delegation pool of the indexer + DelegationPool storage pool = __delegationPools[_indexer]; + Delegation storage delegation = pool.delegators[_delegator]; + + // Validation + uint256 tokensToWithdraw = getWithdraweableDelegatedTokens(delegation); + require(tokensToWithdraw > 0, "!tokens"); + + // Reset lock + delegation.tokensLocked = 0; + delegation.tokensLockedUntil = 0; + + emit StakeDelegatedWithdrawn(_indexer, _delegator, tokensToWithdraw); + + // -- Interactions -- + + if (_delegateToIndexer != address(0)) { + // Re-delegate tokens to a new indexer + _delegate(_delegator, _delegateToIndexer, tokensToWithdraw); + } else { + // Return tokens to the delegator + TokenUtils.pushTokens(graphToken(), _delegator, tokensToWithdraw); + } + + return tokensToWithdraw; + } + + /** + * @dev Collect tax to burn for an amount of tokens. + * @param _graphToken Token to burn + * @param _tokens Total tokens received used to calculate the amount of tax to collect + * @param _percentage Percentage of tokens to burn as tax + * @return Amount of tax charged + */ + function _collectTax( + IGraphToken _graphToken, + uint256 _tokens, + uint256 _percentage + ) private returns (uint256) { + uint256 tax = uint256(_percentage).mul(_tokens).div(MAX_PPM); + TokenUtils.burnTokens(_graphToken, tax); // Burn tax if any + return tax; + } +} diff --git a/packages/legacy/verified/StakingExtension/sources/contracts/staking/StakingStorage.sol b/packages/legacy/verified/StakingExtension/sources/contracts/staking/StakingStorage.sol new file mode 100644 index 000000000..88c149e22 --- /dev/null +++ b/packages/legacy/verified/StakingExtension/sources/contracts/staking/StakingStorage.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import { Managed } from "../governance/Managed.sol"; + +import { IStakingData } from "./IStakingData.sol"; +import { Stakes } from "./libs/Stakes.sol"; + +/** + * @title StakingV1Storage + * @notice This contract holds all the storage variables for the Staking contract, version 1 + * @dev Note that we use a double underscore prefix for variable names; this prefix identifies + * variables that used to be public but are now internal, getters can be found on StakingExtension.sol. + */ +// solhint-disable-next-line max-states-count +contract StakingV1Storage is Managed { + // -- Staking -- + + /// @dev Minimum amount of tokens an indexer needs to stake + uint256 internal __minimumIndexerStake; + + /// @dev Time in blocks to unstake + uint32 internal __thawingPeriod; // in blocks + + /// @dev Percentage of fees going to curators + /// Parts per million. (Allows for 4 decimal points, 999,999 = 99.9999%) + uint32 internal __curationPercentage; + + /// @dev Percentage of fees burned as protocol fee + /// Parts per million. (Allows for 4 decimal points, 999,999 = 99.9999%) + uint32 internal __protocolPercentage; + + /// @dev Period for allocation to be finalized + uint32 private __DEPRECATED_channelDisputeEpochs; + + /// @dev Maximum allocation time + uint32 internal __maxAllocationEpochs; + + /// @dev Rebate alpha numerator + // Originally used for Cobb-Douglas rebates, now used for exponential rebates + uint32 internal __alphaNumerator; + + /// @dev Rebate alpha denominator + // Originally used for Cobb-Douglas rebates, now used for exponential rebates + uint32 internal __alphaDenominator; + + /// @dev Indexer stakes : indexer => Stake + mapping(address => Stakes.Indexer) internal __stakes; + + /// @dev Allocations : allocationID => Allocation + mapping(address => IStakingData.Allocation) internal __allocations; + + /// @dev Subgraph Allocations: subgraphDeploymentID => tokens + mapping(bytes32 => uint256) internal __subgraphAllocations; + + // Rebate pools : epoch => Pool + mapping(uint256 => uint256) private __DEPRECATED_rebates; + + // -- Slashing -- + + /// @dev List of addresses allowed to slash stakes + mapping(address => bool) internal __slashers; + + // -- Delegation -- + + /// @dev Set the delegation capacity multiplier defined by the delegation ratio + /// If delegation ratio is 100, and an Indexer has staked 5 GRT, + /// then they can use up to 500 GRT from the delegated stake + uint32 internal __delegationRatio; + + /// @dev Time in blocks an indexer needs to wait to change delegation parameters + uint32 internal __delegationParametersCooldown; + + /// @dev Time in epochs a delegator needs to wait to withdraw delegated stake + uint32 internal __delegationUnbondingPeriod; // in epochs + + /// @dev Percentage of tokens to tax a delegation deposit + /// Parts per million. (Allows for 4 decimal points, 999,999 = 99.9999%) + uint32 internal __delegationTaxPercentage; + + /// @dev Delegation pools : indexer => DelegationPool + mapping(address => IStakingData.DelegationPool) internal __delegationPools; + + // -- Operators -- + + /// @dev Operator auth : indexer => operator => is authorized + mapping(address => mapping(address => bool)) internal __operatorAuth; + + // -- Asset Holders -- + + /// @dev Allowed AssetHolders that can collect query fees: assetHolder => is allowed + mapping(address => bool) internal __assetHolders; +} + +/** + * @title StakingV2Storage + * @notice This contract holds all the storage variables for the Staking contract, version 2 + * @dev Note that we use a double underscore prefix for variable names; this prefix identifies + * variables that used to be public but are now internal, getters can be found on StakingExtension.sol. + */ +contract StakingV2Storage is StakingV1Storage { + /// @dev Destination of accrued rewards : beneficiary => rewards destination + mapping(address => address) internal __rewardsDestination; +} + +/** + * @title StakingV3Storage + * @notice This contract holds all the storage variables for the base Staking contract, version 3. + */ +contract StakingV3Storage is StakingV2Storage { + /// @dev Address of the counterpart Staking contract on L1/L2 + address internal counterpartStakingAddress; + /// @dev Address of the StakingExtension implementation + address internal extensionImpl; +} + +/** + * @title StakingV4Storage + * @notice This contract holds all the storage variables for the base Staking contract, version 4. + * @dev Note that it includes a storage gap - if adding future versions, make sure to move the gap + * to the new version and reduce the size of the gap accordingly. + */ +contract StakingV4Storage is StakingV3Storage { + // Additional rebate parameters for exponential rebates + uint32 internal __lambdaNumerator; + uint32 internal __lambdaDenominator; + + /// @dev Gap to allow adding variables in future upgrades (since L1Staking and L2Staking can have their own storage as well) + uint256[50] private __gap; +} diff --git a/packages/legacy/verified/StakingExtension/sources/contracts/staking/libs/MathUtils.sol b/packages/legacy/verified/StakingExtension/sources/contracts/staking/libs/MathUtils.sol new file mode 100644 index 000000000..4194b6da8 --- /dev/null +++ b/packages/legacy/verified/StakingExtension/sources/contracts/staking/libs/MathUtils.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import "@openzeppelin/contracts/math/SafeMath.sol"; + +/** + * @title MathUtils Library + * @notice A collection of functions to perform math operations + */ +library MathUtils { + using SafeMath for uint256; + + /** + * @dev Calculates the weighted average of two values pondering each of these + * values based on configured weights. The contribution of each value N is + * weightN/(weightA + weightB). + * @param valueA The amount for value A + * @param weightA The weight to use for value A + * @param valueB The amount for value B + * @param weightB The weight to use for value B + */ + function weightedAverage( + uint256 valueA, + uint256 weightA, + uint256 valueB, + uint256 weightB + ) internal pure returns (uint256) { + return valueA.mul(weightA).add(valueB.mul(weightB)).div(weightA.add(weightB)); + } + + /** + * @dev Returns the minimum of two numbers. + */ + function min(uint256 x, uint256 y) internal pure returns (uint256) { + return x <= y ? x : y; + } + + /** + * @dev Returns the difference between two numbers or zero if negative. + */ + function diffOrZero(uint256 x, uint256 y) internal pure returns (uint256) { + return (x > y) ? x.sub(y) : 0; + } +} diff --git a/packages/legacy/verified/StakingExtension/sources/contracts/staking/libs/Stakes.sol b/packages/legacy/verified/StakingExtension/sources/contracts/staking/libs/Stakes.sol new file mode 100644 index 000000000..5882944d8 --- /dev/null +++ b/packages/legacy/verified/StakingExtension/sources/contracts/staking/libs/Stakes.sol @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; +pragma abicoder v2; + +import "@openzeppelin/contracts/math/SafeMath.sol"; + +import "./MathUtils.sol"; + +/** + * @title A collection of data structures and functions to manage the Indexer Stake state. + * Used for low-level state changes, require() conditions should be evaluated + * at the caller function scope. + */ +library Stakes { + using SafeMath for uint256; + using Stakes for Stakes.Indexer; + + struct Indexer { + uint256 tokensStaked; // Tokens on the indexer stake (staked by the indexer) + uint256 tokensAllocated; // Tokens used in allocations + uint256 tokensLocked; // Tokens locked for withdrawal subject to thawing period + uint256 tokensLockedUntil; // Block when locked tokens can be withdrawn + } + + /** + * @dev Deposit tokens to the indexer stake. + * @param stake Stake data + * @param _tokens Amount of tokens to deposit + */ + function deposit(Stakes.Indexer storage stake, uint256 _tokens) internal { + stake.tokensStaked = stake.tokensStaked.add(_tokens); + } + + /** + * @dev Release tokens from the indexer stake. + * @param stake Stake data + * @param _tokens Amount of tokens to release + */ + function release(Stakes.Indexer storage stake, uint256 _tokens) internal { + stake.tokensStaked = stake.tokensStaked.sub(_tokens); + } + + /** + * @dev Allocate tokens from the main stack to a SubgraphDeployment. + * @param stake Stake data + * @param _tokens Amount of tokens to allocate + */ + function allocate(Stakes.Indexer storage stake, uint256 _tokens) internal { + stake.tokensAllocated = stake.tokensAllocated.add(_tokens); + } + + /** + * @dev Unallocate tokens from a SubgraphDeployment back to the main stack. + * @param stake Stake data + * @param _tokens Amount of tokens to unallocate + */ + function unallocate(Stakes.Indexer storage stake, uint256 _tokens) internal { + stake.tokensAllocated = stake.tokensAllocated.sub(_tokens); + } + + /** + * @dev Lock tokens until a thawing period pass. + * @param stake Stake data + * @param _tokens Amount of tokens to unstake + * @param _period Period in blocks that need to pass before withdrawal + */ + function lockTokens( + Stakes.Indexer storage stake, + uint256 _tokens, + uint256 _period + ) internal { + // Take into account period averaging for multiple unstake requests + uint256 lockingPeriod = _period; + if (stake.tokensLocked > 0) { + lockingPeriod = MathUtils.weightedAverage( + MathUtils.diffOrZero(stake.tokensLockedUntil, block.number), // Remaining thawing period + stake.tokensLocked, // Weighted by remaining unstaked tokens + _period, // Thawing period + _tokens // Weighted by new tokens to unstake + ); + } + + // Update balances + stake.tokensLocked = stake.tokensLocked.add(_tokens); + stake.tokensLockedUntil = block.number.add(lockingPeriod); + } + + /** + * @dev Unlock tokens. + * @param stake Stake data + * @param _tokens Amount of tokens to unkock + */ + function unlockTokens(Stakes.Indexer storage stake, uint256 _tokens) internal { + stake.tokensLocked = stake.tokensLocked.sub(_tokens); + if (stake.tokensLocked == 0) { + stake.tokensLockedUntil = 0; + } + } + + /** + * @dev Take all tokens out from the locked stake for withdrawal. + * @param stake Stake data + * @return Amount of tokens being withdrawn + */ + function withdrawTokens(Stakes.Indexer storage stake) internal returns (uint256) { + // Calculate tokens that can be released + uint256 tokensToWithdraw = stake.tokensWithdrawable(); + + if (tokensToWithdraw > 0) { + // Reset locked tokens + stake.unlockTokens(tokensToWithdraw); + + // Decrease indexer stake + stake.release(tokensToWithdraw); + } + + return tokensToWithdraw; + } + + /** + * @dev Return the amount of tokens used in allocations and locked for withdrawal. + * @param stake Stake data + * @return Token amount + */ + function tokensUsed(Stakes.Indexer memory stake) internal pure returns (uint256) { + return stake.tokensAllocated.add(stake.tokensLocked); + } + + /** + * @dev Return the amount of tokens staked not considering the ones that are already going + * through the thawing period or are ready for withdrawal. We call it secure stake because + * it is not subject to change by a withdraw call from the indexer. + * @param stake Stake data + * @return Token amount + */ + function tokensSecureStake(Stakes.Indexer memory stake) internal pure returns (uint256) { + return stake.tokensStaked.sub(stake.tokensLocked); + } + + /** + * @dev Tokens free balance on the indexer stake that can be used for any purpose. + * Any token that is allocated cannot be used as well as tokens that are going through the + * thawing period or are withdrawable + * Calc: tokensStaked - tokensAllocated - tokensLocked + * @param stake Stake data + * @return Token amount + */ + function tokensAvailable(Stakes.Indexer memory stake) internal pure returns (uint256) { + return stake.tokensAvailableWithDelegation(0); + } + + /** + * @dev Tokens free balance on the indexer stake that can be used for allocations. + * This function accepts a parameter for extra delegated capacity that takes into + * account delegated tokens + * @param stake Stake data + * @param _delegatedCapacity Amount of tokens used from delegators to calculate availability + * @return Token amount + */ + function tokensAvailableWithDelegation(Stakes.Indexer memory stake, uint256 _delegatedCapacity) + internal + pure + returns (uint256) + { + uint256 tokensCapacity = stake.tokensStaked.add(_delegatedCapacity); + uint256 _tokensUsed = stake.tokensUsed(); + // If more tokens are used than the current capacity, the indexer is overallocated. + // This means the indexer doesn't have available capacity to create new allocations. + // We can reach this state when the indexer has funds allocated and then any + // of these conditions happen: + // - The delegationCapacity ratio is reduced. + // - The indexer stake is slashed. + // - A delegator removes enough stake. + if (_tokensUsed > tokensCapacity) { + // Indexer stake is over allocated: return 0 to avoid stake to be used until + // the overallocation is restored by staking more tokens, unallocating tokens + // or using more delegated funds + return 0; + } + return tokensCapacity.sub(_tokensUsed); + } + + /** + * @dev Tokens available for withdrawal after thawing period. + * @param stake Stake data + * @return Token amount + */ + function tokensWithdrawable(Stakes.Indexer memory stake) internal view returns (uint256) { + // No tokens to withdraw before locking period + if (stake.tokensLockedUntil == 0 || block.number < stake.tokensLockedUntil) { + return 0; + } + return stake.tokensLocked; + } +} diff --git a/packages/legacy/verified/StakingExtension/sources/contracts/token/IGraphToken.sol b/packages/legacy/verified/StakingExtension/sources/contracts/token/IGraphToken.sol new file mode 100644 index 000000000..8255e18d5 --- /dev/null +++ b/packages/legacy/verified/StakingExtension/sources/contracts/token/IGraphToken.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface IGraphToken is IERC20 { + // -- Mint and Burn -- + + function burn(uint256 amount) external; + + function burnFrom(address _from, uint256 amount) external; + + function mint(address _to, uint256 _amount) external; + + // -- Mint Admin -- + + function addMinter(address _account) external; + + function removeMinter(address _account) external; + + function renounceMinter() external; + + function isMinter(address _account) external view returns (bool); + + // -- Permit -- + + function permit( + address _owner, + address _spender, + uint256 _value, + uint256 _deadline, + uint8 _v, + bytes32 _r, + bytes32 _s + ) external; + + // -- Allowance -- + + function increaseAllowance(address spender, uint256 addedValue) external returns (bool); + + function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool); +} diff --git a/packages/legacy/verified/StakingExtension/sources/contracts/upgrades/GraphUpgradeable.sol b/packages/legacy/verified/StakingExtension/sources/contracts/upgrades/GraphUpgradeable.sol new file mode 100644 index 000000000..92ce80ad7 --- /dev/null +++ b/packages/legacy/verified/StakingExtension/sources/contracts/upgrades/GraphUpgradeable.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import { IGraphProxy } from "./IGraphProxy.sol"; + +/** + * @title Graph Upgradeable + * @dev This contract is intended to be inherited from upgradeable contracts. + */ +abstract contract GraphUpgradeable { + /** + * @dev Storage slot with the address of the current implementation. + * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is + * validated in the constructor. + */ + bytes32 internal constant IMPLEMENTATION_SLOT = + 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + /** + * @dev Check if the caller is the proxy admin. + */ + modifier onlyProxyAdmin(IGraphProxy _proxy) { + require(msg.sender == _proxy.admin(), "Caller must be the proxy admin"); + _; + } + + /** + * @dev Check if the caller is the implementation. + */ + modifier onlyImpl() { + require(msg.sender == _implementation(), "Only implementation"); + _; + } + + /** + * @dev Returns the current implementation. + * @return impl Address of the current implementation + */ + function _implementation() internal view returns (address impl) { + bytes32 slot = IMPLEMENTATION_SLOT; + // solhint-disable-next-line no-inline-assembly + assembly { + impl := sload(slot) + } + } + + /** + * @notice Accept to be an implementation of proxy. + * @param _proxy Proxy to accept + */ + function acceptProxy(IGraphProxy _proxy) external onlyProxyAdmin(_proxy) { + _proxy.acceptUpgrade(); + } + + /** + * @notice Accept to be an implementation of proxy and then call a function from the new + * implementation as specified by `_data`, which should be an encoded function call. This is + * useful to initialize new storage variables in the proxied contract. + * @param _proxy Proxy to accept + * @param _data Calldata for the initialization function call (including selector) + */ + function acceptProxyAndCall(IGraphProxy _proxy, bytes calldata _data) + external + onlyProxyAdmin(_proxy) + { + _proxy.acceptUpgradeAndCall(_data); + } +} diff --git a/packages/legacy/verified/StakingExtension/sources/contracts/upgrades/IGraphProxy.sol b/packages/legacy/verified/StakingExtension/sources/contracts/upgrades/IGraphProxy.sol new file mode 100644 index 000000000..61946e948 --- /dev/null +++ b/packages/legacy/verified/StakingExtension/sources/contracts/upgrades/IGraphProxy.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +interface IGraphProxy { + function admin() external returns (address); + + function setAdmin(address _newAdmin) external; + + function implementation() external returns (address); + + function pendingImplementation() external returns (address); + + function upgradeTo(address _newImplementation) external; + + function acceptUpgrade() external; + + function acceptUpgradeAndCall(bytes calldata data) external; +} diff --git a/packages/legacy/verified/StakingExtension/sources/contracts/utils/TokenUtils.sol b/packages/legacy/verified/StakingExtension/sources/contracts/utils/TokenUtils.sol new file mode 100644 index 000000000..cbb23ce97 --- /dev/null +++ b/packages/legacy/verified/StakingExtension/sources/contracts/utils/TokenUtils.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import "../token/IGraphToken.sol"; + +library TokenUtils { + /** + * @dev Pull tokens from an address to this contract. + * @param _graphToken Token to transfer + * @param _from Address sending the tokens + * @param _amount Amount of tokens to transfer + */ + function pullTokens( + IGraphToken _graphToken, + address _from, + uint256 _amount + ) internal { + if (_amount > 0) { + require(_graphToken.transferFrom(_from, address(this), _amount), "!transfer"); + } + } + + /** + * @dev Push tokens from this contract to a receiving address. + * @param _graphToken Token to transfer + * @param _to Address receiving the tokens + * @param _amount Amount of tokens to transfer + */ + function pushTokens( + IGraphToken _graphToken, + address _to, + uint256 _amount + ) internal { + if (_amount > 0) { + require(_graphToken.transfer(_to, _amount), "!transfer"); + } + } + + /** + * @dev Burn tokens held by this contract. + * @param _graphToken Token to burn + * @param _amount Amount of tokens to burn + */ + function burnTokens(IGraphToken _graphToken, uint256 _amount) internal { + if (_amount > 0) { + _graphToken.burn(_amount); + } + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index af8a98d0e..b8d381611 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -913,6 +913,15 @@ importers: specifier: 'catalog:' version: 1.7.0 + packages/legacy: + devDependencies: + solc-0.7.4: + specifier: npm:solc@0.7.4 + version: solc@0.7.4 + solc-0.7.6: + specifier: npm:solc@0.7.6 + version: solc@0.7.6 + packages/subgraph-discovery: devDependencies: '@graphprotocol/contracts': @@ -5103,6 +5112,9 @@ packages: commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + commander@3.0.2: + resolution: {integrity: sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==} + commander@8.3.0: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} engines: {node: '>= 12'} @@ -9048,6 +9060,16 @@ packages: resolution: {integrity: sha512-o+c6FpkiHd+HPjmjEVpQgH7fqZ14tJpXhho+/bQXlXbliLIS/xjXb42Vxh+qQY1WCSTMQ0+a5vR9vi0MfhU6mA==} hasBin: true + solc@0.7.4: + resolution: {integrity: sha512-IVLqAfUkJqgTS0JIgFPeC50ehUeBXu2eE+iU+rqb6UeOyf6w/BB/EsNcTSTpjtUti8BTG/sCd2qVhrWVYy7p0g==} + engines: {node: '>=8.0.0'} + hasBin: true + + solc@0.7.6: + resolution: {integrity: sha512-WsR/W7CXwh2VnmZapB4JrsDeLlshoKBz5Pz/zYNulB6LBsOEHI2Zj/GeKLMFcvv57OHiXHvxq5ZOQB+EdqxlxQ==} + engines: {node: '>=8.0.0'} + hasBin: true + solc@0.8.15: resolution: {integrity: sha512-Riv0GNHNk/SddN/JyEuFKwbcWcEeho15iyupTSHw5Np6WuXA5D8kEHbyzDHi6sqmvLzu2l+8b1YmL8Ytple+8w==} engines: {node: '>=10.0.0'} @@ -15986,6 +16008,8 @@ snapshots: commander@2.20.3: {} + commander@3.0.2: {} + commander@8.3.0: {} commander@9.5.0: {} @@ -21023,6 +21047,34 @@ snapshots: semver: 5.7.2 yargs: 4.8.1 + solc@0.7.4: + dependencies: + command-exists: 1.2.9 + commander: 3.0.2 + follow-redirects: 1.16.0(debug@4.4.3) + fs-extra: 0.30.0 + js-sha3: 0.8.0 + memorystream: 0.3.1 + require-from-string: 2.0.2 + semver: 5.7.2 + tmp: 0.0.33 + transitivePeerDependencies: + - debug + + solc@0.7.6: + dependencies: + command-exists: 1.2.9 + commander: 3.0.2 + follow-redirects: 1.16.0(debug@4.4.3) + fs-extra: 0.30.0 + js-sha3: 0.8.0 + memorystream: 0.3.1 + require-from-string: 2.0.2 + semver: 5.7.2 + tmp: 0.0.33 + transitivePeerDependencies: + - debug + solc@0.8.15: dependencies: command-exists: 1.2.9 diff --git a/scripts/lint-staged-run.sh b/scripts/lint-staged-run.sh index abbd779be..a418c5ead 100755 --- a/scripts/lint-staged-run.sh +++ b/scripts/lint-staged-run.sh @@ -20,6 +20,7 @@ FILES=("$@") # Note: These are substrings to check for in the file path, not glob patterns IGNORE_SUBSTRINGS=( "/.graphclient-extracted/" + "/packages/legacy/verified/" ) # Function to check if a file should be ignored