diff --git a/.vscode/launch.json b/.vscode/launch.json index b5c39658c..e272405f9 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -39,6 +39,15 @@ "console": "integratedTerminal", "outputCapture": "std", }, + { + "type": "node", + "request": "attach", + "name": "Docker: Attach to Server", + "remoteRoot": "/home/app", + "localRoot": "${workspaceFolder}", + "port": 9229, + "address": "localhost", + }, { // Make sure the UI webserver is already running with `npm start` "name": "UI", diff --git a/environments/server.debug.linux-amd64.yaml b/environments/server.debug.linux-amd64.yaml new file mode 100644 index 000000000..6d082da17 --- /dev/null +++ b/environments/server.debug.linux-amd64.yaml @@ -0,0 +1,44 @@ +# Runs the server in a linux-amd64 container with debug port exposed +# Used in debugging solidity linux-amd64 binary executables +version: "3.7" +x-project-repository-mount: &project-repository-mount + type: bind + source: $REPOSITORY_PATH + +x-project-base: &project-base + env_file: + - .env + restart: always + networks: + - source-verify + +networks: + source-verify: + +services: + server: + <<: *project-base + # image: ethereum/source-verify:server-${TAG} + build: + context: ../ + dockerfile: src/Dockerfile.server.debug + container_name: server-${TAG} + platform: linux/amd64 + ports: + - "${SERVER_EXTERNAL_PORT}:${SERVER_PORT}" + - "9229:9229" # Debug port + volumes: + - ../:/home/app + - <<: *project-repository-mount + target: /home/data/repository + - type: bind + source: $SOLC_REPO_HOST + target: $SOLC_REPO + - type: bind + source: $SOLJSON_REPO_HOST + target: $SOLJSON_REPO + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:${SERVER_PORT}/health"] + interval: 30s + timeout: 10s + retries: 10 diff --git a/openapi.yaml b/openapi.yaml index dabd0cac8..6c5a950b8 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -47,7 +47,7 @@ paths: /files/contracts/{chain}: $ref: "src/server/controllers/repository/get-contract-addresses-all.stateless.paths.yaml#/paths/~1files~1contracts~1{chain}" /contracts/{full_match | partial_match}/{chain}/{address}/{filePath}: - $ref: "src/server/controllers/repository/get-file-static.stateless.paths.yaml#/paths/~1contracts~1{full_match | partial_match}~1{chain}~1{address}~1{filePath}" + $ref: "src/server/controllers/repository/get-file-static.stateless.paths.yaml#/paths/~1repository~1contracts~1{full_match | partial_match}~1{chain}~1{address}~1{filePath}" /files/tree/any/{chain}/{address}: $ref: "src/server/controllers/repository/get-file-tree-all.stateless.paths.yaml#/paths/~1files~1tree~1any~1{chain}~1{address}" /files/tree/{chain}/{address}: diff --git a/packages/lib-sourcify/package-lock.json b/packages/lib-sourcify/package-lock.json index 918f9a166..422faad8f 100644 --- a/packages/lib-sourcify/package-lock.json +++ b/packages/lib-sourcify/package-lock.json @@ -1,6 +1,6 @@ { "name": "@ethereum-sourcify/lib-sourcify", - "version": "1.2.1", + "version": "1.3.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/packages/lib-sourcify/package.json b/packages/lib-sourcify/package.json index a2b1fdbf5..5953397ff 100644 --- a/packages/lib-sourcify/package.json +++ b/packages/lib-sourcify/package.json @@ -1,6 +1,6 @@ { "name": "@ethereum-sourcify/lib-sourcify", - "version": "1.2.1", + "version": "1.3.0", "description": "Library for Sourcify's contract verification methods, contract validation, types, and interfaces.", "main": "build/main/index.js", "typings": "build/main/index.d.ts", diff --git a/packages/lib-sourcify/src/lib/compilerWorker.ts b/packages/lib-sourcify/src/lib/compilerWorker.ts new file mode 100644 index 000000000..9325998a4 --- /dev/null +++ b/packages/lib-sourcify/src/lib/compilerWorker.ts @@ -0,0 +1,13 @@ +import { workerData, parentPort } from 'worker_threads'; +import { getSolcJs } from './solidityCompiler'; + +async function runUseCompiler(version: string, inputStringified: string) { + const solJson = await getSolcJs(version); + const result = solJson.compile(inputStringified); + if (parentPort === null) { + throw new Error('Parent port is null; cannot send compilation result'); + } + parentPort.postMessage(result); +} + +runUseCompiler(workerData.version, workerData.inputStringified); diff --git a/packages/lib-sourcify/src/lib/solidityCompiler.ts b/packages/lib-sourcify/src/lib/solidityCompiler.ts index e0eebf50c..eec0993c1 100644 --- a/packages/lib-sourcify/src/lib/solidityCompiler.ts +++ b/packages/lib-sourcify/src/lib/solidityCompiler.ts @@ -1,17 +1,17 @@ // TODO: Handle nodejs only dependencies import path from 'path'; import fs from 'fs'; -import { spawnSync } from 'child_process'; +import { exec, spawnSync } from 'child_process'; import { fetchWithTimeout } from './utils'; import { StatusCodes } from 'http-status-codes'; import { JsonInput, PathBuffer } from './types'; -import { logError, logInfo, logWarn } from './logger'; +import { logDebug, logError, logInfo, logWarn } from './logger'; +import semver from 'semver'; +import { Worker, WorkerOptions } from 'worker_threads'; // eslint-disable-next-line @typescript-eslint/no-var-requires const solc = require('solc'); const GITHUB_SOLC_REPO = 'https://github.com/ethereum/solc-bin/raw/gh-pages/'; -const RECOMPILATION_ERR_MSG = - 'Recompilation error (probably caused by invalid metadata)'; export function findSolcPlatform(): string | false { if (process.platform === 'darwin' && process.arch === 'x64') { @@ -48,35 +48,45 @@ export async function useCompiler(version: string, solcJsonInput: JsonInput) { if (solcPlatform) { solcPath = await getSolcExecutable(solcPlatform, version); } - const startCompilation = Date.now(); + let startCompilation: number; if (solcPath) { - const shellOutputBuffer = spawnSync(solcPath, ['--standard-json'], { - input: inputStringified, - maxBuffer: 1000 * 1000 * 10, - }); - - // Handle errors. - let error: false | Error = false; - if (shellOutputBuffer.error) { - const typedError: NodeJS.ErrnoException = shellOutputBuffer.error; - // Handle compilation output size > stdout buffer - if (typedError.code === 'ENOBUFS') { - error = new Error('Compilation output size too large'); + logDebug(`Compiling with solc binary ${version} at ${solcPath}`); + startCompilation = Date.now(); + try { + compiled = await asyncExecSolc(inputStringified, solcPath); + } catch (error: any) { + if (error?.code === 'ENOBUFS') { + throw new Error('Compilation output size too large'); } - error = new Error('Compilation Error'); - } - if (!shellOutputBuffer.stdout) { - error = new Error(RECOMPILATION_ERR_MSG); - } - if (error) { logWarn(error.message); throw error; } - compiled = shellOutputBuffer.stdout.toString(); } else { - const soljson = await getSolcJs(version); - if (soljson) { - compiled = soljson.compile(inputStringified); + const solJson = await getSolcJs(version); + startCompilation = Date.now(); + logDebug(`Compiling with solc-js ${version}`); + if (solJson) { + const coercedVersion = + semver.coerce(new semver.SemVer(version))?.version ?? ''; + // Run Worker for solc versions < 0.4.0 for clean compiler context. See https://github.com/ethereum/sourcify/issues/1099 + if (semver.lt(coercedVersion, '0.4.0')) { + compiled = await new Promise((resolve, reject) => { + const worker = importWorker( + path.resolve(__dirname, './compilerWorker.ts'), + { + workerData: { version, inputStringified }, + } + ); + worker.once('message', (result) => { + resolve(result); + }); + worker.once('error', (error) => { + reject(error); + }); + }); + } else { + compiled = solJson.compile(inputStringified); + } } } @@ -108,6 +118,7 @@ export async function getAllMetadataAndSourcesFromSolcJson( throw new Error( 'Only Solidity is supported, the json has language: ' + solcJson.language ); + const outputSelection = { '*': { '*': ['metadata'], @@ -148,9 +159,17 @@ export async function getSolcExecutable( const repoPath = process.env.SOLC_REPO || path.join('/tmp', 'solc-repo'); const solcPath = path.join(repoPath, fileName); if (fs.existsSync(solcPath) && validateSolcPath(solcPath)) { + logDebug(`Found solc ${version} with platform ${platform} at ${solcPath}`); return solcPath; } + + logDebug( + `Downloading solc ${version} with platform ${platform} at ${solcPath}` + ); const success = await fetchAndSaveSolc(platform, solcPath, version, fileName); + logDebug( + `Downloaded solc ${version} with platform ${platform} at ${solcPath}` + ); if (success && !validateSolcPath(solcPath)) { logError(`Cannot validate solc ${version}.`); return null; @@ -187,6 +206,9 @@ async function fetchAndSaveSolc( ): Promise { const encodedURIFilename = encodeURIComponent(fileName); const githubSolcURI = `${GITHUB_SOLC_REPO}${platform}/${encodedURIFilename}`; + logDebug( + `Fetching solc ${version} on platform ${platform} from GitHub: ${githubSolcURI}` + ); let res = await fetchWithTimeout(githubSolcURI); let status = res.status; let buffer; @@ -206,6 +228,9 @@ async function fetchAndSaveSolc( } if (status === StatusCodes.OK && buffer) { + logDebug( + `Fetched solc ${version} on platform ${platform} from GitHub: ${githubSolcURI}` + ); fs.mkdirSync(path.dirname(solcPath), { recursive: true }); try { @@ -260,3 +285,48 @@ export async function getSolcJs(version = 'latest'): Promise { const solcjsImports = await import(soljsonPath); return solc.setupMethods(solcjsImports); } + +function asyncExecSolc( + inputStringified: string, + solcPath: string +): Promise { + // check if input is valid JSON. The input is untrusted and potentially cause arbitrary execution. + JSON.parse(inputStringified); + + return new Promise((resolve, reject) => { + const child = exec( + `${solcPath} --standard-json`, + { + maxBuffer: 1000 * 1000 * 10, + }, + (error, stdout, stderr) => { + if (error) { + reject(error); + } else if (stderr) { + reject( + new Error(`Compiler process returned with errors:\n ${stderr}`) + ); + } else { + resolve(stdout); + } + } + ); + if (!child.stdin) { + throw new Error('No stdin on child process'); + } + // Write input to child process's stdin + child.stdin.write(inputStringified); + child.stdin.end(); + }); +} + +// https://stackoverflow.com/questions/71795469/ts-node-using-worker-thread-cause-cannot-use-import-statement-outside-a-module +function importWorker(path: string, options: WorkerOptions) { + const resolvedPath = require.resolve(path); + return new Worker(resolvedPath, { + ...options, + execArgv: /\.ts$/.test(resolvedPath) + ? ['--require', 'ts-node/register'] + : undefined, + }); +} diff --git a/packages/lib-sourcify/test/functions.spec.ts b/packages/lib-sourcify/test/functions.spec.ts index e77ac686b..cd5247789 100644 --- a/packages/lib-sourcify/test/functions.spec.ts +++ b/packages/lib-sourcify/test/functions.spec.ts @@ -16,7 +16,8 @@ import storageMetadata from './sources/Storage/metadata.json'; import { Metadata, MissingSources } from '../src/lib/types'; import WrongMetadata from './sources/WrongMetadata/metadata.json'; import SimplyLog from './sources/WrongMetadata/SimplyLog.json'; - +import earlyCompilerInput from './sources/json-input/pre-v0.4.0/input.json'; +import { keccak256 } from 'ethers'; describe('Verify Solidity Compiler', () => { it('Should fetch latest SolcJS compiler', async () => { expect(await getSolcJs()).not.equals(null); @@ -111,6 +112,24 @@ describe('Verify Solidity Compiler', () => { }); } }); + + // See https://github.com/ethereum/sourcify/issues/1099 + it(`Should should use a clean compiler context with pre 0.4.0 versions`, async () => { + // Run compiler once to change compiler "context" + await useCompiler('0.1.5+commit.23865e3', earlyCompilerInput); + + // A second run needs to produce the same result + const compilerResult = await useCompiler( + '0.1.5+commit.23865e3', + earlyCompilerInput + ); + const compiledBytecode = compilerResult.contracts[''].GroveLib.evm + .deployedBytecode.object as string; + const compiledHash = keccak256('0x' + compiledBytecode); + expect(compiledHash).equals( + '0xc778f3d42ce4a7ee21a2e93d45265cf771e5970e0e36f882310f4491d0ca889d' + ); + }); }); describe('Checked contract', () => { diff --git a/packages/lib-sourcify/test/sources/json-input/pre-v0.4.0/input.json b/packages/lib-sourcify/test/sources/json-input/pre-v0.4.0/input.json new file mode 100644 index 000000000..8459b708e --- /dev/null +++ b/packages/lib-sourcify/test/sources/json-input/pre-v0.4.0/input.json @@ -0,0 +1,20 @@ +{ + "language": "Solidity", + "settings": { + "libraries": {}, + "optimizer": { + "enabled": true, + "runs": 200 + }, + "outputSelection": { + "*": { + "*": ["metadata", "evm.deployedBytecode.object"] + } + } + }, + "sources": { + "GroveLib.sol": { + "content": "// Grove v0.2\r\n\r\n\r\n/// @title GroveLib - Library for queriable indexed ordered data.\r\n/// @author PiperMerriam - \r\nlibrary GroveLib {\r\n /*\r\n * Indexes for ordered data\r\n *\r\n * Address: 0xd07ce4329b27eb8896c51458468d98a0e4c0394c\r\n */\r\n struct Index {\r\n bytes32 id;\r\n bytes32 name;\r\n bytes32 root;\r\n mapping (bytes32 => Node) nodes;\r\n }\r\n\r\n struct Node {\r\n bytes32 nodeId;\r\n bytes32 indexId;\r\n bytes32 id;\r\n int value;\r\n bytes32 parent;\r\n bytes32 left;\r\n bytes32 right;\r\n uint height;\r\n }\r\n\r\n /// @dev This is merely a shortcut for `sha3(owner, indexName)`\r\n /// @param owner The address of the owner of this index.\r\n /// @param indexName The human readable name for this index.\r\n function computeIndexId(address owner, bytes32 indexName) constant returns (bytes32) {\r\n return sha3(owner, indexName);\r\n }\r\n\r\n /// @dev This is merely a shortcut for `sha3(indexId, id)`\r\n /// @param indexId The id for the index the node belongs to.\r\n /// @param id The unique identifier for the data this node represents.\r\n function computeNodeId(bytes32 indexId, bytes32 id) constant returns (bytes32) {\r\n return sha3(indexId, id);\r\n }\r\n\r\n function max(uint a, uint b) internal returns (uint) {\r\n if (a >= b) {\r\n return a;\r\n }\r\n return b;\r\n }\r\n\r\n /*\r\n * Node getters\r\n */\r\n /// @dev Retrieve the unique identifier for the node.\r\n /// @param index The index that the node is part of.\r\n /// @param nodeId The id for the node to be looked up.\r\n function getNodeId(Index storage index, bytes32 nodeId) constant returns (bytes32) {\r\n return index.nodes[nodeId].id;\r\n }\r\n\r\n /// @dev Retrieve the index id for the node.\r\n /// @param index The index that the node is part of.\r\n /// @param nodeId The id for the node to be looked up.\r\n function getNodeIndexId(Index storage index, bytes32 nodeId) constant returns (bytes32) {\r\n return index.nodes[nodeId].indexId;\r\n }\r\n\r\n /// @dev Retrieve the value for the node.\r\n /// @param index The index that the node is part of.\r\n /// @param nodeId The id for the node to be looked up.\r\n function getNodeValue(Index storage index, bytes32 nodeId) constant returns (int) {\r\n return index.nodes[nodeId].value;\r\n }\r\n\r\n /// @dev Retrieve the height of the node.\r\n /// @param index The index that the node is part of.\r\n /// @param nodeId The id for the node to be looked up.\r\n function getNodeHeight(Index storage index, bytes32 nodeId) constant returns (uint) {\r\n return index.nodes[nodeId].height;\r\n }\r\n\r\n /// @dev Retrieve the parent id of the node.\r\n /// @param index The index that the node is part of.\r\n /// @param nodeId The id for the node to be looked up.\r\n function getNodeParent(Index storage index, bytes32 nodeId) constant returns (bytes32) {\r\n return index.nodes[nodeId].parent;\r\n }\r\n\r\n /// @dev Retrieve the left child id of the node.\r\n /// @param index The index that the node is part of.\r\n /// @param nodeId The id for the node to be looked up.\r\n function getNodeLeftChild(Index storage index, bytes32 nodeId) constant returns (bytes32) {\r\n return index.nodes[nodeId].left;\r\n }\r\n\r\n /// @dev Retrieve the right child id of the node.\r\n /// @param index The index that the node is part of.\r\n /// @param nodeId The id for the node to be looked up.\r\n function getNodeRightChild(Index storage index, bytes32 nodeId) constant returns (bytes32) {\r\n return index.nodes[nodeId].right;\r\n }\r\n\r\n /// @dev Retrieve the node id of the next node in the tree.\r\n /// @param index The index that the node is part of.\r\n /// @param nodeId The id for the node to be looked up.\r\n function getPreviousNode(Index storage index, bytes32 nodeId) constant returns (bytes32) {\r\n Node storage currentNode = index.nodes[nodeId];\r\n\r\n if (currentNode.nodeId == 0x0) {\r\n // Unknown node, just return 0x0;\r\n return 0x0;\r\n }\r\n\r\n Node memory child;\r\n\r\n if (currentNode.left != 0x0) {\r\n // Trace left to latest child in left tree.\r\n child = index.nodes[currentNode.left];\r\n\r\n while (child.right != 0) {\r\n child = index.nodes[child.right];\r\n }\r\n return child.nodeId;\r\n }\r\n\r\n if (currentNode.parent != 0x0) {\r\n // Now we trace back up through parent relationships, looking\r\n // for a link where the child is the right child of it's\r\n // parent.\r\n Node storage parent = index.nodes[currentNode.parent];\r\n child = currentNode;\r\n\r\n while (true) {\r\n if (parent.right == child.nodeId) {\r\n return parent.nodeId;\r\n }\r\n\r\n if (parent.parent == 0x0) {\r\n break;\r\n }\r\n child = parent;\r\n parent = index.nodes[parent.parent];\r\n }\r\n }\r\n\r\n // This is the first node, and has no previous node.\r\n return 0x0;\r\n }\r\n\r\n /// @dev Retrieve the node id of the previous node in the tree.\r\n /// @param index The index that the node is part of.\r\n /// @param nodeId The id for the node to be looked up.\r\n function getNextNode(Index storage index, bytes32 nodeId) constant returns (bytes32) {\r\n Node storage currentNode = index.nodes[nodeId];\r\n\r\n if (currentNode.nodeId == 0x0) {\r\n // Unknown node, just return 0x0;\r\n return 0x0;\r\n }\r\n\r\n Node memory child;\r\n\r\n if (currentNode.right != 0x0) {\r\n // Trace right to earliest child in right tree.\r\n child = index.nodes[currentNode.right];\r\n\r\n while (child.left != 0) {\r\n child = index.nodes[child.left];\r\n }\r\n return child.nodeId;\r\n }\r\n\r\n if (currentNode.parent != 0x0) {\r\n // if the node is the left child of it's parent, then the\r\n // parent is the next one.\r\n Node storage parent = index.nodes[currentNode.parent];\r\n child = currentNode;\r\n\r\n while (true) {\r\n if (parent.left == child.nodeId) {\r\n return parent.nodeId;\r\n }\r\n\r\n if (parent.parent == 0x0) {\r\n break;\r\n }\r\n child = parent;\r\n parent = index.nodes[parent.parent];\r\n }\r\n\r\n // Now we need to trace all the way up checking to see if any parent is the \r\n }\r\n\r\n // This is the final node.\r\n return 0x0;\r\n }\r\n\r\n\r\n /// @dev Updates or Inserts the id into the index at its appropriate location based on the value provided.\r\n /// @param index The index that the node is part of.\r\n /// @param id The unique identifier of the data element the index node will represent.\r\n /// @param value The value of the data element that represents it's total ordering with respect to other elementes.\r\n function insert(Index storage index, bytes32 id, int value) public {\r\n bytes32 nodeId = computeNodeId(index.id, id);\r\n\r\n if (index.nodes[nodeId].nodeId == nodeId) {\r\n // A node with this id already exists. If the value is\r\n // the same, then just return early, otherwise, remove it\r\n // and reinsert it.\r\n if (index.nodes[nodeId].value == value) {\r\n return;\r\n }\r\n remove(index, id);\r\n }\r\n\r\n uint leftHeight;\r\n uint rightHeight;\r\n\r\n bytes32 previousNodeId = 0x0;\r\n\r\n bytes32 rootNodeId = index.root;\r\n\r\n if (rootNodeId == 0x0) {\r\n rootNodeId = nodeId;\r\n index.root = nodeId;\r\n }\r\n Node storage currentNode = index.nodes[rootNodeId];\r\n\r\n // Do insertion\r\n while (true) {\r\n if (currentNode.indexId == 0x0) {\r\n // This is a new unpopulated node.\r\n currentNode.nodeId = nodeId;\r\n currentNode.parent = previousNodeId;\r\n currentNode.indexId = index.id;\r\n currentNode.id = id;\r\n currentNode.value = value;\r\n break;\r\n }\r\n\r\n // Set the previous node id.\r\n previousNodeId = currentNode.nodeId;\r\n\r\n // The new node belongs in the right subtree\r\n if (value >= currentNode.value) {\r\n if (currentNode.right == 0x0) {\r\n currentNode.right = nodeId;\r\n }\r\n currentNode = index.nodes[currentNode.right];\r\n continue;\r\n }\r\n\r\n // The new node belongs in the left subtree.\r\n if (currentNode.left == 0x0) {\r\n currentNode.left = nodeId;\r\n }\r\n currentNode = index.nodes[currentNode.left];\r\n }\r\n\r\n // Rebalance the tree\r\n _rebalanceTree(index, currentNode.nodeId);\r\n }\r\n\r\n /// @dev Checks whether a node for the given unique identifier exists within the given index.\r\n /// @param index The index that should be searched\r\n /// @param id The unique identifier of the data element to check for.\r\n function exists(Index storage index, bytes32 id) constant returns (bool) {\r\n bytes32 nodeId = computeNodeId(index.id, id);\r\n return (index.nodes[nodeId].nodeId == nodeId);\r\n }\r\n\r\n /// @dev Remove the node for the given unique identifier from the index.\r\n /// @param index The index that should be removed\r\n /// @param id The unique identifier of the data element to remove.\r\n function remove(Index storage index, bytes32 id) public {\r\n bytes32 nodeId = computeNodeId(index.id, id);\r\n \r\n Node storage replacementNode;\r\n Node storage parent;\r\n Node storage child;\r\n bytes32 rebalanceOrigin;\r\n\r\n Node storage nodeToDelete = index.nodes[nodeId];\r\n\r\n if (nodeToDelete.id != id) {\r\n // The id does not exist in the tree.\r\n return;\r\n }\r\n\r\n if (nodeToDelete.left != 0x0 || nodeToDelete.right != 0x0) {\r\n // This node is not a leaf node and thus must replace itself in\r\n // it's tree by either the previous or next node.\r\n if (nodeToDelete.left != 0x0) {\r\n // This node is guaranteed to not have a right child.\r\n replacementNode = index.nodes[getPreviousNode(index, nodeToDelete.nodeId)];\r\n }\r\n else {\r\n // This node is guaranteed to not have a left child.\r\n replacementNode = index.nodes[getNextNode(index, nodeToDelete.nodeId)];\r\n }\r\n // The replacementNode is guaranteed to have a parent.\r\n parent = index.nodes[replacementNode.parent];\r\n\r\n // Keep note of the location that our tree rebalancing should\r\n // start at.\r\n rebalanceOrigin = replacementNode.nodeId;\r\n\r\n // Join the parent of the replacement node with any subtree of\r\n // the replacement node. We can guarantee that the replacement\r\n // node has at most one subtree because of how getNextNode and\r\n // getPreviousNode are used.\r\n if (parent.left == replacementNode.nodeId) {\r\n parent.left = replacementNode.right;\r\n if (replacementNode.right != 0x0) {\r\n child = index.nodes[replacementNode.right];\r\n child.parent = parent.nodeId;\r\n }\r\n }\r\n if (parent.right == replacementNode.nodeId) {\r\n parent.right = replacementNode.left;\r\n if (replacementNode.left != 0x0) {\r\n child = index.nodes[replacementNode.left];\r\n child.parent = parent.nodeId;\r\n }\r\n }\r\n\r\n // Now we replace the nodeToDelete with the replacementNode.\r\n // This includes parent/child relationships for all of the\r\n // parent, the left child, and the right child.\r\n replacementNode.parent = nodeToDelete.parent;\r\n if (nodeToDelete.parent != 0x0) {\r\n parent = index.nodes[nodeToDelete.parent];\r\n if (parent.left == nodeToDelete.nodeId) {\r\n parent.left = replacementNode.nodeId;\r\n }\r\n if (parent.right == nodeToDelete.nodeId) {\r\n parent.right = replacementNode.nodeId;\r\n }\r\n }\r\n else {\r\n // If the node we are deleting is the root node so update\r\n // the indexId to root node mapping.\r\n index.root = replacementNode.nodeId;\r\n }\r\n\r\n replacementNode.left = nodeToDelete.left;\r\n if (nodeToDelete.left != 0x0) {\r\n child = index.nodes[nodeToDelete.left];\r\n child.parent = replacementNode.nodeId;\r\n }\r\n\r\n replacementNode.right = nodeToDelete.right;\r\n if (nodeToDelete.right != 0x0) {\r\n child = index.nodes[nodeToDelete.right];\r\n child.parent = replacementNode.nodeId;\r\n }\r\n }\r\n else if (nodeToDelete.parent != 0x0) {\r\n // The node being deleted is a leaf node so we only erase it's\r\n // parent linkage.\r\n parent = index.nodes[nodeToDelete.parent];\r\n\r\n if (parent.left == nodeToDelete.nodeId) {\r\n parent.left = 0x0;\r\n }\r\n if (parent.right == nodeToDelete.nodeId) {\r\n parent.right = 0x0;\r\n }\r\n\r\n // keep note of where the rebalancing should begin.\r\n rebalanceOrigin = parent.nodeId;\r\n }\r\n else {\r\n // This is both a leaf node and the root node, so we need to\r\n // unset the root node pointer.\r\n index.root = 0x0;\r\n }\r\n\r\n // Now we zero out all of the fields on the nodeToDelete.\r\n nodeToDelete.id = 0x0;\r\n nodeToDelete.nodeId = 0x0;\r\n nodeToDelete.indexId = 0x0;\r\n nodeToDelete.value = 0;\r\n nodeToDelete.parent = 0x0;\r\n nodeToDelete.left = 0x0;\r\n nodeToDelete.right = 0x0;\r\n\r\n // Walk back up the tree rebalancing\r\n if (rebalanceOrigin != 0x0) {\r\n _rebalanceTree(index, rebalanceOrigin);\r\n }\r\n }\r\n\r\n bytes2 constant GT = \">\";\r\n bytes2 constant LT = \"<\";\r\n bytes2 constant GTE = \">=\";\r\n bytes2 constant LTE = \"<=\";\r\n bytes2 constant EQ = \"==\";\r\n\r\n function _compare(int left, bytes2 operator, int right) internal returns (bool) {\r\n if (operator == GT) {\r\n return (left > right);\r\n }\r\n if (operator == LT) {\r\n return (left < right);\r\n }\r\n if (operator == GTE) {\r\n return (left >= right);\r\n }\r\n if (operator == LTE) {\r\n return (left <= right);\r\n }\r\n if (operator == EQ) {\r\n return (left == right);\r\n }\r\n\r\n // Invalid operator.\r\n throw;\r\n }\r\n\r\n function _getMaximum(Index storage index, bytes32 nodeId) internal returns (int) {\r\n Node storage currentNode = index.nodes[nodeId];\r\n\r\n while (true) {\r\n if (currentNode.right == 0x0) {\r\n return currentNode.value;\r\n }\r\n currentNode = index.nodes[currentNode.right];\r\n }\r\n }\r\n\r\n function _getMinimum(Index storage index, bytes32 nodeId) internal returns (int) {\r\n Node storage currentNode = index.nodes[nodeId];\r\n\r\n while (true) {\r\n if (currentNode.left == 0x0) {\r\n return currentNode.value;\r\n }\r\n currentNode = index.nodes[currentNode.left];\r\n }\r\n }\r\n\r\n\r\n /** @dev Query the index for the edge-most node that satisfies the\r\n * given query. For >, >=, and ==, this will be the left-most node\r\n * that satisfies the comparison. For < and <= this will be the\r\n * right-most node that satisfies the comparison.\r\n */\r\n /// @param index The index that should be queried\r\n /** @param operator One of '>', '>=', '<', '<=', '==' to specify what\r\n * type of comparison operator should be used.\r\n */\r\n function query(Index storage index, bytes2 operator, int value) public returns (bytes32) {\r\n bytes32 rootNodeId = index.root;\r\n \r\n if (rootNodeId == 0x0) {\r\n // Empty tree.\r\n return 0x0;\r\n }\r\n\r\n Node storage currentNode = index.nodes[rootNodeId];\r\n\r\n while (true) {\r\n if (_compare(currentNode.value, operator, value)) {\r\n // We have found a match but it might not be the\r\n // *correct* match.\r\n if ((operator == LT) || (operator == LTE)) {\r\n // Need to keep traversing right until this is no\r\n // longer true.\r\n if (currentNode.right == 0x0) {\r\n return currentNode.nodeId;\r\n }\r\n if (_compare(_getMinimum(index, currentNode.right), operator, value)) {\r\n // There are still nodes to the right that\r\n // match.\r\n currentNode = index.nodes[currentNode.right];\r\n continue;\r\n }\r\n return currentNode.nodeId;\r\n }\r\n\r\n if ((operator == GT) || (operator == GTE) || (operator == EQ)) {\r\n // Need to keep traversing left until this is no\r\n // longer true.\r\n if (currentNode.left == 0x0) {\r\n return currentNode.nodeId;\r\n }\r\n if (_compare(_getMaximum(index, currentNode.left), operator, value)) {\r\n currentNode = index.nodes[currentNode.left];\r\n continue;\r\n }\r\n return currentNode.nodeId;\r\n }\r\n }\r\n\r\n if ((operator == LT) || (operator == LTE)) {\r\n if (currentNode.left == 0x0) {\r\n // There are no nodes that are less than the value\r\n // so return null.\r\n return 0x0;\r\n }\r\n currentNode = index.nodes[currentNode.left];\r\n continue;\r\n }\r\n\r\n if ((operator == GT) || (operator == GTE)) {\r\n if (currentNode.right == 0x0) {\r\n // There are no nodes that are greater than the value\r\n // so return null.\r\n return 0x0;\r\n }\r\n currentNode = index.nodes[currentNode.right];\r\n continue;\r\n }\r\n\r\n if (operator == EQ) {\r\n if (currentNode.value < value) {\r\n if (currentNode.right == 0x0) {\r\n return 0x0;\r\n }\r\n currentNode = index.nodes[currentNode.right];\r\n continue;\r\n }\r\n\r\n if (currentNode.value > value) {\r\n if (currentNode.left == 0x0) {\r\n return 0x0;\r\n }\r\n currentNode = index.nodes[currentNode.left];\r\n continue;\r\n }\r\n }\r\n }\r\n }\r\n\r\n function _rebalanceTree(Index storage index, bytes32 nodeId) internal {\r\n // Trace back up rebalancing the tree and updating heights as\r\n // needed..\r\n Node storage currentNode = index.nodes[nodeId];\r\n\r\n while (true) {\r\n int balanceFactor = _getBalanceFactor(index, currentNode.nodeId);\r\n\r\n if (balanceFactor == 2) {\r\n // Right rotation (tree is heavy on the left)\r\n if (_getBalanceFactor(index, currentNode.left) == -1) {\r\n // The subtree is leaning right so it need to be\r\n // rotated left before the current node is rotated\r\n // right.\r\n _rotateLeft(index, currentNode.left);\r\n }\r\n _rotateRight(index, currentNode.nodeId);\r\n }\r\n\r\n if (balanceFactor == -2) {\r\n // Left rotation (tree is heavy on the right)\r\n if (_getBalanceFactor(index, currentNode.right) == 1) {\r\n // The subtree is leaning left so it need to be\r\n // rotated right before the current node is rotated\r\n // left.\r\n _rotateRight(index, currentNode.right);\r\n }\r\n _rotateLeft(index, currentNode.nodeId);\r\n }\r\n\r\n if ((-1 <= balanceFactor) && (balanceFactor <= 1)) {\r\n _updateNodeHeight(index, currentNode.nodeId);\r\n }\r\n\r\n if (currentNode.parent == 0x0) {\r\n // Reached the root which may be new due to tree\r\n // rotation, so set it as the root and then break.\r\n break;\r\n }\r\n\r\n currentNode = index.nodes[currentNode.parent];\r\n }\r\n }\r\n\r\n function _getBalanceFactor(Index storage index, bytes32 nodeId) internal returns (int) {\r\n Node storage node = index.nodes[nodeId];\r\n\r\n return int(index.nodes[node.left].height) - int(index.nodes[node.right].height);\r\n }\r\n\r\n function _updateNodeHeight(Index storage index, bytes32 nodeId) internal {\r\n Node storage node = index.nodes[nodeId];\r\n\r\n node.height = max(index.nodes[node.left].height, index.nodes[node.right].height) + 1;\r\n }\r\n\r\n function _rotateLeft(Index storage index, bytes32 nodeId) internal {\r\n Node storage originalRoot = index.nodes[nodeId];\r\n\r\n if (originalRoot.right == 0x0) {\r\n // Cannot rotate left if there is no right originalRoot to rotate into\r\n // place.\r\n throw;\r\n }\r\n\r\n // The right child is the new root, so it gets the original\r\n // `originalRoot.parent` as it's parent.\r\n Node storage newRoot = index.nodes[originalRoot.right];\r\n newRoot.parent = originalRoot.parent;\r\n\r\n // The original root needs to have it's right child nulled out.\r\n originalRoot.right = 0x0;\r\n\r\n if (originalRoot.parent != 0x0) {\r\n // If there is a parent node, it needs to now point downward at\r\n // the newRoot which is rotating into the place where `node` was.\r\n Node storage parent = index.nodes[originalRoot.parent];\r\n\r\n // figure out if we're a left or right child and have the\r\n // parent point to the new node.\r\n if (parent.left == originalRoot.nodeId) {\r\n parent.left = newRoot.nodeId;\r\n }\r\n if (parent.right == originalRoot.nodeId) {\r\n parent.right = newRoot.nodeId;\r\n }\r\n }\r\n\r\n\r\n if (newRoot.left != 0) {\r\n // If the new root had a left child, that moves to be the\r\n // new right child of the original root node\r\n Node storage leftChild = index.nodes[newRoot.left];\r\n originalRoot.right = leftChild.nodeId;\r\n leftChild.parent = originalRoot.nodeId;\r\n }\r\n\r\n // Update the newRoot's left node to point at the original node.\r\n originalRoot.parent = newRoot.nodeId;\r\n newRoot.left = originalRoot.nodeId;\r\n\r\n if (newRoot.parent == 0x0) {\r\n index.root = newRoot.nodeId;\r\n }\r\n\r\n // TODO: are both of these updates necessary?\r\n _updateNodeHeight(index, originalRoot.nodeId);\r\n _updateNodeHeight(index, newRoot.nodeId);\r\n }\r\n\r\n function _rotateRight(Index storage index, bytes32 nodeId) internal {\r\n Node storage originalRoot = index.nodes[nodeId];\r\n\r\n if (originalRoot.left == 0x0) {\r\n // Cannot rotate right if there is no left node to rotate into\r\n // place.\r\n throw;\r\n }\r\n\r\n // The left child is taking the place of node, so we update it's\r\n // parent to be the original parent of the node.\r\n Node storage newRoot = index.nodes[originalRoot.left];\r\n newRoot.parent = originalRoot.parent;\r\n\r\n // Null out the originalRoot.left\r\n originalRoot.left = 0x0;\r\n\r\n if (originalRoot.parent != 0x0) {\r\n // If the node has a parent, update the correct child to point\r\n // at the newRoot now.\r\n Node storage parent = index.nodes[originalRoot.parent];\r\n\r\n if (parent.left == originalRoot.nodeId) {\r\n parent.left = newRoot.nodeId;\r\n }\r\n if (parent.right == originalRoot.nodeId) {\r\n parent.right = newRoot.nodeId;\r\n }\r\n }\r\n\r\n if (newRoot.right != 0x0) {\r\n Node storage rightChild = index.nodes[newRoot.right];\r\n originalRoot.left = newRoot.right;\r\n rightChild.parent = originalRoot.nodeId;\r\n }\r\n\r\n // Update the new root's right node to point to the original node.\r\n originalRoot.parent = newRoot.nodeId;\r\n newRoot.right = originalRoot.nodeId;\r\n\r\n if (newRoot.parent == 0x0) {\r\n index.root = newRoot.nodeId;\r\n }\r\n\r\n // Recompute heights.\r\n _updateNodeHeight(index, originalRoot.nodeId);\r\n _updateNodeHeight(index, newRoot.nodeId);\r\n }\r\n}\r\n\r\n\r\n/// @title Grove - queryable indexes for ordered data.\r\n/// @author Piper Merriam \r\ncontract Grove {\r\n /*\r\n * Indexes for ordered data\r\n *\r\n * Address: 0x8017f24a47c889b1ee80501ff84beb3c017edf0b\r\n */\r\n // Map index_id to index\r\n mapping (bytes32 => GroveLib.Index) index_lookup;\r\n\r\n // Map node_id to index_id.\r\n mapping (bytes32 => bytes32) node_to_index;\r\n\r\n /// @notice Computes the id for a Grove index which is sha3(owner, indexName)\r\n /// @param owner The address of the index owner.\r\n /// @param indexName The name of the index.\r\n function computeIndexId(address owner, bytes32 indexName) constant returns (bytes32) {\r\n return GroveLib.computeIndexId(owner, indexName);\r\n }\r\n\r\n /// @notice Computes the id for a node in a given Grove index which is sha3(indexId, id)\r\n /// @param indexId The id for the index the node belongs to.\r\n /// @param id The unique identifier for the data this node represents.\r\n function computeNodeId(bytes32 indexId, bytes32 id) constant returns (bytes32) {\r\n return GroveLib.computeNodeId(indexId, id);\r\n }\r\n\r\n /*\r\n * Node getters\r\n */\r\n /// @notice Retrieves the name of an index.\r\n /// @param indexId The id of the index.\r\n function getIndexName(bytes32 indexId) constant returns (bytes32) {\r\n return index_lookup[indexId].name;\r\n }\r\n\r\n /// @notice Retrieves the id of the root node for this index.\r\n /// @param indexId The id of the index.\r\n function getIndexRoot(bytes32 indexId) constant returns (bytes32) {\r\n return index_lookup[indexId].root;\r\n }\r\n\r\n\r\n /// @dev Retrieve the unique identifier this node represents.\r\n /// @param nodeId The id for the node\r\n function getNodeId(bytes32 nodeId) constant returns (bytes32) {\r\n return GroveLib.getNodeId(index_lookup[node_to_index[nodeId]], nodeId);\r\n }\r\n\r\n /// @dev Retrieve the index id for the node.\r\n /// @param nodeId The id for the node\r\n function getNodeIndexId(bytes32 nodeId) constant returns (bytes32) {\r\n return GroveLib.getNodeIndexId(index_lookup[node_to_index[nodeId]], nodeId);\r\n }\r\n\r\n /// @dev Retrieve the value of the node.\r\n /// @param nodeId The id for the node\r\n function getNodeValue(bytes32 nodeId) constant returns (int) {\r\n return GroveLib.getNodeValue(index_lookup[node_to_index[nodeId]], nodeId);\r\n }\r\n\r\n /// @dev Retrieve the height of the node.\r\n /// @param nodeId The id for the node\r\n function getNodeHeight(bytes32 nodeId) constant returns (uint) {\r\n return GroveLib.getNodeHeight(index_lookup[node_to_index[nodeId]], nodeId);\r\n }\r\n\r\n /// @dev Retrieve the parent id of the node.\r\n /// @param nodeId The id for the node\r\n function getNodeParent(bytes32 nodeId) constant returns (bytes32) {\r\n return GroveLib.getNodeParent(index_lookup[node_to_index[nodeId]], nodeId);\r\n }\r\n\r\n /// @dev Retrieve the left child id of the node.\r\n /// @param nodeId The id for the node\r\n function getNodeLeftChild(bytes32 nodeId) constant returns (bytes32) {\r\n return GroveLib.getNodeLeftChild(index_lookup[node_to_index[nodeId]], nodeId);\r\n }\r\n\r\n /// @dev Retrieve the right child id of the node.\r\n /// @param nodeId The id for the node\r\n function getNodeRightChild(bytes32 nodeId) constant returns (bytes32) {\r\n return GroveLib.getNodeRightChild(index_lookup[node_to_index[nodeId]], nodeId);\r\n }\r\n\r\n /** @dev Retrieve the id of the node that comes immediately before this\r\n * one. Returns 0x0 if there is no previous node.\r\n */\r\n /// @param nodeId The id for the node\r\n function getPreviousNode(bytes32 nodeId) constant returns (bytes32) {\r\n return GroveLib.getPreviousNode(index_lookup[node_to_index[nodeId]], nodeId);\r\n }\r\n\r\n /** @dev Retrieve the id of the node that comes immediately after this\r\n * one. Returns 0x0 if there is no previous node.\r\n */\r\n /// @param nodeId The id for the node\r\n function getNextNode(bytes32 nodeId) constant returns (bytes32) {\r\n return GroveLib.getNextNode(index_lookup[node_to_index[nodeId]], nodeId);\r\n }\r\n\r\n /** @dev Update or Insert a data element represented by the unique\r\n * identifier `id` into the index.\r\n */\r\n /// @param indexName The human readable name for the index that the node should be upserted into.\r\n /// @param id The unique identifier that the index node represents.\r\n /// @param value The number which represents this data elements total ordering.\r\n function insert(bytes32 indexName, bytes32 id, int value) public {\r\n bytes32 indexId = computeIndexId(msg.sender, indexName);\r\n var index = index_lookup[indexId];\r\n\r\n if (index.name != indexName) {\r\n // If this is a new index, store it's name and id\r\n index.name = indexName;\r\n index.id = indexId;\r\n }\r\n\r\n // Store the mapping from nodeId to the indexId\r\n node_to_index[computeNodeId(indexId, id)] = indexId;\r\n\r\n GroveLib.insert(index, id, value);\r\n }\r\n\r\n /// @dev Query whether a node exists within the specified index for the unique identifier.\r\n /// @param indexId The id for the index.\r\n /// @param id The unique identifier of the data element.\r\n function exists(bytes32 indexId, bytes32 id) constant returns (bool) {\r\n return GroveLib.exists(index_lookup[indexId], id);\r\n }\r\n\r\n /// @dev Remove the index node for the given unique identifier.\r\n /// @param indexName The name of the index.\r\n /// @param id The unique identifier of the data element.\r\n function remove(bytes32 indexName, bytes32 id) public {\r\n GroveLib.remove(index_lookup[computeIndexId(msg.sender, indexName)], id);\r\n }\r\n\r\n /** @dev Query the index for the edge-most node that satisfies the\r\n * given query. For >, >=, and ==, this will be the left-most node\r\n * that satisfies the comparison. For < and <= this will be the\r\n * right-most node that satisfies the comparison.\r\n */\r\n /// @param indexId The id of the index that should be queried\r\n /** @param operator One of '>', '>=', '<', '<=', '==' to specify what\r\n * type of comparison operator should be used.\r\n */\r\n function query(bytes32 indexId, bytes2 operator, int value) public returns (bytes32) {\r\n return GroveLib.query(index_lookup[indexId], operator, value);\r\n }\r\n}" + } + } +} diff --git a/services/ipfs/server/Dockerfile.ipfs b/services/ipfs/server/Dockerfile.ipfs index b4b9124d4..f5ac9ee9e 100644 --- a/services/ipfs/server/Dockerfile.ipfs +++ b/services/ipfs/server/Dockerfile.ipfs @@ -1,6 +1,6 @@ # Explicit setting for Mac ARM FROM --platform=linux/amd64 ubuntu:20.04 -RUN apt-get update && apt-get install -y cron curl +RUN apt-get update && apt-get install -y cron curl jq RUN mkdir /sourcify WORKDIR /sourcify diff --git a/services/ipfs/server/delete-old-pins.sh b/services/ipfs/server/delete-old-pins.sh new file mode 100644 index 000000000..7b1323a05 --- /dev/null +++ b/services/ipfs/server/delete-old-pins.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +BASE_URL="https://api.estuary.tech" # Replace with your API base URL +DAYS_BACK=2 +LIMIT=1000 +# ESTUARY_PINNING_SECRET should be already set in your environment + +# Header for authorization +AUTH_HEADER="Authorization: Bearer $ESTUARY_PINNING_SECRET" + +# Calculate the date in the required format +calculate_date() { + # date -u -d "${DAYS_BACK} days ago" +"%Y-%m-%dT00:00:%SZ" + # MacOS has a different date command + date -v-${DAYS_BACK}d +"%Y-%m-%dT00:00:%SZ" +} + +# Fetch pins before a certain date +fetch_pins_before_date() { + local before_date="$1" + curl -s -H "$AUTH_HEADER" "${BASE_URL}/pinning/pins?before=${before_date}&limit=${LIMIT}" +} + +# Delete a pin by pinid +delete_pin() { + local pinid="$1" + curl -s -H "$AUTH_HEADER" -X DELETE "${BASE_URL}/pinning/pins/${pinid}" + echo "Deleted pin with ID: ${pinid}" +} + +# Main function to cleanup old pins +cleanup_old_pins() { + local before_date + before_date=$(calculate_date) + + local pins + pins=$(fetch_pins_before_date "$before_date") + + # Assuming the response is a JSON array and you only need pinid to delete them. + local pinids + pinids=$(echo "$pins" | jq -r '.results[] | .requestid') + + # Loop through the pins and delete them + while IFS= read -r pinid; do + if [ -n "$pinid" ]; then + delete_pin $pinid + fi + done <<< "$pinids" +} + +# Execute the main function +cleanup_old_pins \ No newline at end of file diff --git a/src/Dockerfile.server.debug b/src/Dockerfile.server.debug new file mode 100644 index 000000000..897aa594c --- /dev/null +++ b/src/Dockerfile.server.debug @@ -0,0 +1,9 @@ +# Runs the server in a linux-amd64 container with debug port exposed +# Used in debugging solidity linux-amd64 binary executables +FROM node:16 +WORKDIR /home/app + +# Install puppeteer dependencies. +RUN apt-get update && apt-get -y install xvfb gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget + +CMD ["node", "--inspect=0.0.0.0:9229", "./dist/server/server.js"] \ No newline at end of file diff --git a/src/chains.json b/src/chains.json index ec1994edf..620969572 100644 --- a/src/chains.json +++ b/src/chains.json @@ -24,6 +24,12 @@ "name": "etherscan", "url": "https://etherscan.io", "standard": "EIP3091" + }, + { + "name": "blockscout", + "url": "https://eth.blockscout.com", + "icon": "blockscout", + "standard": "EIP3091" } ] }, @@ -133,6 +139,12 @@ "name": "etherscan-goerli", "url": "https://goerli.etherscan.io", "standard": "EIP3091" + }, + { + "name": "blockscout-goerli", + "url": "https://eth-goerli.blockscout.com", + "icon": "blockscout", + "standard": "EIP3091" } ] }, @@ -211,7 +223,7 @@ { "name": "OP Mainnet", "chain": "ETH", - "rpc": ["https://mainnet.optimism.io/"], + "rpc": ["https://mainnet.optimism.io", "https://optimism.publicnode.com"], "faucets": [], "nativeCurrency": { "name": "Ether", "symbol": "ETH", "decimals": 18 }, "infoURL": "https://optimism.io", @@ -223,6 +235,12 @@ "name": "etherscan", "url": "https://optimistic.etherscan.io", "standard": "EIP3091" + }, + { + "name": "blockscout", + "url": "https://optimism.blockscout.com", + "icon": "blockscout", + "standard": "EIP3091" } ] }, @@ -465,7 +483,7 @@ "redFlags": ["reusedChainId"] }, { - "name": "Cronos Mainnet Beta", + "name": "Cronos Mainnet", "chain": "CRO", "rpc": ["https://evm.cronos.org", "https://cronos-evm.publicnode.com"], "features": [{ "name": "EIP1559" }], @@ -1122,7 +1140,8 @@ "https://rpc.syscoin.org", "https://rpc.ankr.com/syscoin/${ANKR_API_KEY}", "https://syscoin.public-rpc.com", - "wss://rpc.syscoin.org/wss" + "wss://rpc.syscoin.org/wss", + "https://syscoin-evm.publicnode.com" ], "faucets": ["https://faucet.syscoin.org"], "nativeCurrency": { "name": "Syscoin", "symbol": "SYS", "decimals": 18 }, @@ -1475,7 +1494,7 @@ { "name": "Decimal Smart Chain Mainnet", "chain": "DSC", - "rpc": ["https://node.decimalchain.com/web3"], + "rpc": ["https://node.decimalchain.com/web3/"], "faucets": [], "nativeCurrency": { "name": "Decimal", "symbol": "DEL", "decimals": 18 }, "features": [{ "name": "EIP155" }, { "name": "EIP1559" }], @@ -1527,7 +1546,8 @@ { "name": "blockscout", "url": "https://blockscout.com/poa/sokol", - "standard": "none" + "icon": "blockscout", + "standard": "EIP3091" } ] }, @@ -1591,22 +1611,32 @@ ] }, { - "name": "Zenith Testnet (Vilnius)", - "chain": "Zenith", - "rpc": ["https://vilnius.zenithchain.co/http"], - "faucets": ["https://faucet.zenithchain.co/"], - "nativeCurrency": { "name": "Vilnius", "symbol": "VIL", "decimals": 18 }, - "infoURL": "https://www.zenithchain.co/", + "name": "Japan Open Chain Mainnet", + "chain": "JOC", + "rpc": [ + "https://rpc-1.japanopenchain.org:8545", + "https://rpc-2.japanopenchain.org:8545" + ], + "faucets": [], + "nativeCurrency": { + "name": "Japan Open Chain Token", + "symbol": "JOC", + "decimals": 18 + }, + "infoURL": "https://www.japanopenchain.org/", + "shortName": "joc", "chainId": 81, "networkId": 81, - "shortName": "VIL", + "icon": "joc", "explorers": [ { - "name": "vilnius scan", - "url": "https://vilnius.scan.zenithchain.co", - "standard": "EIP3091" + "name": "Block Explorer", + "url": "https://explorer.japanopenchain.org", + "standard": "EIP3091", + "icon": "joc" } - ] + ], + "redFlags": ["reusedChainId"] }, { "name": "Meter Mainnet", @@ -1969,7 +1999,8 @@ { "name": "blockscout", "url": "https://blockscout.com/poa/core", - "standard": "none" + "icon": "blockscout", + "standard": "EIP3091" } ] }, @@ -2007,7 +2038,7 @@ }, { "name": "blockscout", - "url": "https://blockscout.com/xdai/mainnet", + "url": "https://gnosis.blockscout.com", "icon": "blockscout", "standard": "EIP3091" } @@ -3135,6 +3166,25 @@ "chainId": 208, "networkId": 208 }, + { + "name": "Bitnet", + "chain": "BTN", + "icon": "bitnet", + "rpc": ["https://rpc.bitnet.money", "https://rpc.btnscan.com"], + "faucets": [], + "nativeCurrency": { "name": "Bitnet", "symbol": "BTN", "decimals": 18 }, + "infoURL": "https://bitnet.money", + "shortName": "BTN", + "chainId": 210, + "networkId": 210, + "explorers": [ + { + "name": "Bitnet Explorer", + "url": "https://btnscan.com", + "standard": "EIP3091" + } + ] + }, { "name": "Freight Trust Network", "chain": "EDI", @@ -3978,6 +4028,12 @@ "url": "https://shiden.subscan.io", "standard": "none", "icon": "subscan" + }, + { + "name": "blockscout", + "url": "https://blockscout.com/shiden", + "icon": "blockscout", + "standard": "EIP3091" } ] }, @@ -4098,7 +4154,11 @@ "chainId": 369, "networkId": 369, "infoURL": "https://pulsechain.com/", - "rpc": ["https://rpc.pulsechain.com/", "wss://rpc.pulsechain.com/"], + "rpc": [ + "https://rpc.pulsechain.com", + "wss://rpc.pulsechain.com", + "https://pulsechain.publicnode.com" + ], "features": [{ "name": "EIP155" }, { "name": "EIP1559" }], "faucets": [], "ens": { "registry": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e" }, @@ -4251,7 +4311,10 @@ { "name": "Optimism Goerli Testnet", "chain": "ETH", - "rpc": ["https://goerli.optimism.io/"], + "rpc": [ + "https://goerli.optimism.io", + "https://optimism-goerli.publicnode.com" + ], "faucets": [], "nativeCurrency": { "name": "Goerli Ether", @@ -4261,7 +4324,15 @@ "infoURL": "https://optimism.io", "shortName": "ogor", "chainId": 420, - "networkId": 420 + "networkId": 420, + "explorers": [ + { + "name": "blockscout", + "url": "https://optimism-goerli.blockscout.com", + "icon": "blockscout", + "standard": "EIP3091" + } + ] }, { "name": "PGN (Public Goods Network)", @@ -4311,6 +4382,34 @@ } ] }, + { + "name": "Obscuro Testnet", + "title": "Obscuro Sepolia Rollup Testnet", + "chainId": 443, + "shortName": "obs-testnet", + "chain": "ETH", + "networkId": 443, + "nativeCurrency": { + "name": "Sepolia Ether", + "symbol": "ETH", + "decimals": 18 + }, + "rpc": ["https://testnet.obscu.ro"], + "faucets": [], + "infoURL": "https://obscu.ro", + "explorers": [ + { + "name": "Obscuro Sepolia Rollup Explorer", + "url": "https://testnet.obscuroscan.io", + "standard": "none" + } + ], + "parent": { + "type": "L2", + "chain": "eip155-5", + "bridges": [{ "url": "https://bridge.obscu.ro" }] + } + }, { "name": "Frenchain Testnet", "chain": "tfren", @@ -4660,13 +4759,23 @@ "url": "https://astar.subscan.io", "standard": "none", "icon": "subscan" + }, + { + "name": "blockscout", + "url": "https://blockscout.com/astar", + "icon": "blockscout", + "standard": "EIP3091" } ] }, { - "name": "Acala Mandala Testnet", + "name": "Acala Mandala Testnet TC9", "chain": "mACA", - "rpc": [], + "rpc": [ + "https://eth-rpc-tc9.aca-staging.network", + "wss://eth-rpc-tc9.aca-staging.network" + ], + "features": [{ "name": "EIP155" }, { "name": "EIP1559" }], "faucets": [], "nativeCurrency": { "name": "Acala Mandala Token", @@ -4676,12 +4785,23 @@ "infoURL": "https://acala.network", "shortName": "maca", "chainId": 595, - "networkId": 595 + "networkId": 595, + "slip44": 595, + "explorers": [ + { + "name": "blockscout", + "url": "https://blockscout.mandala.aca-staging.network", + "standard": "EIP3091" + } + ] }, { "name": "Karura Network Testnet", "chain": "KAR", - "rpc": [], + "rpc": [ + "https://eth-rpc-karura-testnet.aca-staging.network", + "wss://eth-rpc-karura-testnet.aca-staging.network" + ], "faucets": [], "nativeCurrency": { "name": "Karura Token", @@ -4692,12 +4812,22 @@ "shortName": "tkar", "chainId": 596, "networkId": 596, - "slip44": 596 + "slip44": 596, + "explorers": [ + { + "name": "blockscout", + "url": "https://blockscout.karura-testnet.aca-staging.network", + "standard": "EIP3091" + } + ] }, { "name": "Acala Network Testnet", "chain": "ACA", - "rpc": [], + "rpc": [ + "https://eth-rpc-acala-testnet.aca-staging.network", + "wss://eth-rpc-acala-testnet.aca-staging.network" + ], "faucets": [], "nativeCurrency": { "name": "Acala Token", @@ -4708,7 +4838,14 @@ "shortName": "taca", "chainId": 597, "networkId": 597, - "slip44": 597 + "slip44": 597, + "explorers": [ + { + "name": "blockscout", + "url": "https://blockscout.acala-dev.aca-dev.network", + "standard": "EIP3091" + } + ] }, { "name": "Metis Goerli Testnet", @@ -4752,6 +4889,30 @@ "chainId": 600, "networkId": 600 }, + { + "name": "PEER Testnet", + "chain": "PEER", + "rpc": ["http://testnet-polka-host-232813573.us-west-1.elb.amazonaws.com"], + "faucets": ["https://testnet.peer.inc"], + "nativeCurrency": { + "name": "PEER Token", + "symbol": "PEER", + "decimals": 18 + }, + "infoURL": "https://peer.inc", + "shortName": "PEER", + "chainId": 601, + "networkId": 601, + "icon": "peer", + "explorers": [ + { + "name": "PEER Explorer", + "url": "https://testnet.peer.inc", + "standard": "none", + "icon": "peer" + } + ] + }, { "name": "Graphlinq Blockchain Mainnet", "chain": "GLQ Blockchain", @@ -4852,18 +5013,29 @@ { "name": "Karura Network", "chain": "KAR", - "rpc": [], + "rpc": [ + "https://eth-rpc-karura.aca-api.network", + "wss://eth-rpc-karura.aca-api.network" + ], + "features": [{ "name": "EIP155" }, { "name": "EIP1559" }], "faucets": [], "nativeCurrency": { "name": "Karura Token", "symbol": "KAR", "decimals": 18 }, - "infoURL": "https://karura.network", + "infoURL": "https://acala.network/karura", "shortName": "kar", "chainId": 686, "networkId": 686, - "slip44": 686 + "slip44": 686, + "explorers": [ + { + "name": "blockscout", + "url": "https://blockscout.karura.network", + "standard": "EIP3091" + } + ] }, { "name": "Star Social Testnet", @@ -5101,7 +5273,11 @@ { "name": "Acala Network", "chain": "ACA", - "rpc": [], + "rpc": [ + "https://eth-rpc-acala.aca-api.network", + "wss://eth-rpc-acala.aca-api.network" + ], + "features": [{ "name": "EIP155" }, { "name": "EIP1559" }], "faucets": [], "nativeCurrency": { "name": "Acala Token", @@ -5112,7 +5288,14 @@ "shortName": "aca", "chainId": 787, "networkId": 787, - "slip44": 787 + "slip44": 787, + "explorers": [ + { + "name": "blockscout", + "url": "https://blockscout.acala.network", + "standard": "EIP3091" + } + ] }, { "name": "Aerochain Testnet", @@ -5213,7 +5396,11 @@ "https://evm-dataseed3.meerscan.io", "https://evm-dataseed.meerscan.com", "https://evm-dataseed1.meerscan.com", - "https://evm-dataseed2.meerscan.com" + "https://evm-dataseed2.meerscan.com", + "https://qng.rpc.qitmeer.io", + "https://mainnet.meerlabs.com", + "https://rpc.dimai.ai", + "https://rpc.woowow.io" ], "faucets": [], "nativeCurrency": { "name": "Qitmeer", "symbol": "MEER", "decimals": 18 }, @@ -5227,7 +5414,7 @@ { "name": "meerscan", "icon": "meer", - "url": "https://evm.meerscan.com", + "url": "https://qng.meerscan.io", "standard": "none" } ] @@ -5653,7 +5840,8 @@ "infoURL": "https://pulsechain.com", "rpc": [ "https://rpc.v4.testnet.pulsechain.com/", - "wss://rpc.v4.testnet.pulsechain.com/" + "wss://rpc.v4.testnet.pulsechain.com/", + "https://pulsechain-testnet.publicnode.com" ], "features": [{ "name": "EIP155" }, { "name": "EIP1559" }], "faucets": ["https://faucet.v4.testnet.pulsechain.com/"], @@ -6167,6 +6355,26 @@ } ] }, + { + "name": "Mintara Mainnet", + "title": "Mintara Mainnet", + "chain": "Mintara", + "icon": "mintara", + "rpc": ["https://subnets.avax.network/mintara/mainnet/rpc"], + "faucets": [], + "nativeCurrency": { "name": "MINTARA", "symbol": "MNTR", "decimals": 18 }, + "infoURL": "https://playthink.co.jp", + "shortName": "mintara", + "chainId": 1080, + "networkId": 1080, + "explorers": [ + { + "name": "explorer", + "url": "https://subnets.avax.network/mintara", + "standard": "EIP3091" + } + ] + }, { "name": "Metis Andromeda Mainnet", "chain": "ETH", @@ -7096,6 +7304,26 @@ } ] }, + { + "name": "Kalar Chain", + "chain": "KLC", + "icon": "kalarchain", + "rpc": ["https://rpc-api.kalarchain.tech"], + "faucets": [], + "nativeCurrency": { "name": "Kalar", "symbol": "KLC", "decimals": 18 }, + "infoURL": "https://kalarchain.tech", + "shortName": "KLC", + "chainId": 1379, + "networkId": 1379, + "explorers": [ + { + "name": "kalarscan", + "url": "https://explorer.kalarchain.tech", + "icon": "kalarscan", + "standard": "EIP3091" + } + ] + }, { "name": "AmStar Mainnet", "chain": "AmStar", @@ -7339,7 +7567,7 @@ "title": "Tenet Mainnet", "chain": "TENET", "icon": "tenet", - "rpc": ["https://rpc.tenet.org"], + "rpc": ["https://rpc.tenet.org", "https://tenet-evm.publicnode.com"], "faucets": [], "nativeCurrency": { "name": "TENET", "symbol": "TENET", "decimals": 18 }, "infoURL": "https://tenet.org/", @@ -7714,6 +7942,29 @@ "chainId": 1856, "networkId": 1 }, + { + "name": "WhiteBIT Network", + "chain": "WBT", + "rpc": ["https://rpc.whitebit.network"], + "faucets": [], + "nativeCurrency": { + "name": "WhiteBIT Coin", + "symbol": "WBT", + "decimals": 18 + }, + "infoURL": "https://whitebit.network", + "shortName": "wbt", + "chainId": 1875, + "networkId": 1875, + "icon": "whitebit", + "explorers": [ + { + "name": "wb-explorer", + "url": "https://explorer.whitebit.network", + "standard": "EIP3091" + } + ] + }, { "name": "Gitshock Cartenz Testnet", "chain": "Gitshock Cartenz", @@ -8800,7 +9051,8 @@ "https://evm.kava.io", "https://evm2.kava.io", "wss://wevm.kava.io", - "wss://wevm2.kava.io" + "wss://wevm2.kava.io", + "https://kava-evm.publicnode.com" ], "faucets": [], "nativeCurrency": { "name": "Kava", "symbol": "KAVA", "decimals": 18 }, @@ -10381,7 +10633,7 @@ "name": "Mantle", "chain": "ETH", "icon": "mantle", - "rpc": ["https://rpc.mantle.xyz"], + "rpc": ["https://rpc.mantle.xyz", "https://mantle.publicnode.com"], "faucets": [], "nativeCurrency": { "name": "Mantle", "symbol": "MNT", "decimals": 18 }, "infoURL": "https://mantle.xyz", @@ -10639,10 +10891,29 @@ } ] }, + { + "name": "Arcturus Chain Testnet", + "chain": "ARCTURUS", + "rpc": ["http://185.99.196.3:8545"], + "faucets": [], + "nativeCurrency": { + "name": "Test Arct", + "symbol": "tARCT", + "decimals": 18 + }, + "infoURL": "https://arcturuschain.io", + "shortName": "ARCT", + "chainId": 5616, + "networkId": 5616 + }, { "name": "Syscoin Tanenbaum Testnet", "chain": "SYS", - "rpc": ["https://rpc.tanenbaum.io", "wss://rpc.tanenbaum.io/wss"], + "rpc": [ + "https://rpc.tanenbaum.io", + "wss://rpc.tanenbaum.io/wss", + "https://syscoin-tanenbaum-evm.publicnode.com" + ], "faucets": ["https://faucet.tanenbaum.io"], "nativeCurrency": { "name": "Testnet Syscoin", @@ -10961,7 +11232,10 @@ { "name": "IRIShub", "chain": "IRIShub", - "rpc": ["https://evmrpc.irishub-1.irisnet.org"], + "rpc": [ + "https://evmrpc.irishub-1.irisnet.org", + "https://iris-evm.publicnode.com" + ], "features": [{ "name": "EIP155" }, { "name": "EIP1559" }], "faucets": [], "nativeCurrency": { "name": "Eris", "symbol": "ERIS", "decimals": 18 }, @@ -11061,10 +11335,10 @@ ] }, { - "name": "ZetaChain Athens Testnet", + "name": "ZetaChain Athens 3 Testnet", "chain": "ZetaChain", "icon": "zetachain", - "rpc": ["https://api.athens2.zetachain.com/evm"], + "rpc": ["https://rpc.ankr.com/zetachain_evm_athens_testnet"], "faucets": ["https://labs.zetachain.com/get-zeta"], "nativeCurrency": { "name": "Zeta", "symbol": "aZETA", "decimals": 18 }, "infoURL": "https://zetachain.com/docs", @@ -11075,8 +11349,14 @@ "explorers": [ { "name": "ZetaChain Athens Testnet Explorer", - "url": "https://explorer.athens.zetachain.com", + "url": "https://athens3.explorer.zetachain.com", "standard": "none" + }, + { + "name": "blockscout", + "url": "https://zetachain-athens-3.blockscout.com", + "icon": "blockscout", + "standard": "EIP3091" } ] }, @@ -11654,8 +11934,12 @@ { "name": "Qitmeer Network Testnet", "chain": "MEER", - "rpc": [], - "faucets": [], + "rpc": [ + "https://testnet-qng.rpc.qitmeer.io", + "https://testnet.meerlabs.com", + "https://meer.testnet.meerfans.club" + ], + "faucets": ["https://faucet.qitmeer.io"], "nativeCurrency": { "name": "Qitmeer Testnet", "symbol": "MEER-T", @@ -11670,7 +11954,7 @@ { "name": "meerscan testnet", "icon": "meer", - "url": "https://testnet.qng.meerscan.io", + "url": "https://qng-testnet.meerscan.io", "standard": "none" } ] @@ -11862,7 +12146,7 @@ { "name": "Base", "chain": "ETH", - "rpc": ["https://developer-access-mainnet.base.org/"], + "rpc": ["https://mainnet.base.org/"], "faucets": [], "nativeCurrency": { "name": "Ether", "symbol": "ETH", "decimals": 18 }, "infoURL": "https://base.org", @@ -11875,7 +12159,8 @@ { "name": "basescout", "url": "https://base.blockscout.com", - "standard": "none" + "icon": "blockscout", + "standard": "EIP3091" } ], "status": "active" @@ -12511,6 +12796,98 @@ "networkId": 9792, "explorers": [] }, + { + "name": "IMPERIUM TESTNET", + "chain": "tIMP", + "rpc": [ + "https://data-aws-testnet.imperiumchain.com", + "https://data-aws2-testnet.imperiumchain.com" + ], + "faucets": ["https://faucet.imperiumchain.com/"], + "nativeCurrency": { "name": "tIMP", "symbol": "tIMP", "decimals": 18 }, + "infoURL": "https://imperiumchain.com", + "shortName": "tIMP", + "chainId": 9818, + "networkId": 9818, + "icon": "timp", + "explorers": [ + { + "name": "IMPERIUM TESTNET Explorer", + "icon": "timp", + "url": "https://network.impscan.com", + "standard": "none" + } + ] + }, + { + "name": "IMPERIUM MAINNET", + "chain": "IMP", + "rpc": [ + "https://data-aws-mainnet.imperiumchain.com", + "https://data-aws2-mainnet.imperiumchain.com" + ], + "faucets": ["https://faucet.imperiumchain.com/"], + "nativeCurrency": { "name": "IMP", "symbol": "IMP", "decimals": 18 }, + "infoURL": "https://imperiumchain.com", + "shortName": "IMP", + "chainId": 9819, + "networkId": 9819, + "icon": "imp", + "explorers": [ + { + "name": "IMPERIUM Explorer", + "icon": "imp", + "url": "https://impscan.com", + "standard": "none" + } + ] + }, + { + "name": "Mind Smart Chain Testnet", + "chain": "tMIND", + "icon": "mindchain", + "rpc": ["https://testnet-msc.mindchain.info/"], + "faucets": ["https://faucet.mindchain.info/"], + "nativeCurrency": { + "name": "MIND Coin", + "symbol": "tMIND", + "decimals": 18 + }, + "infoURL": "https://mindscan.info", + "shortName": "tMIND", + "chainId": 9977, + "networkId": 9977, + "explorers": [ + { + "name": "Mind Chain explorer", + "url": "https://testnet.mindscan.info", + "standard": "EIP3091" + } + ] + }, + { + "name": "Mind Smart Chain Mainnet", + "chain": "MIND", + "icon": "mindchain", + "rpc": [ + "https://rpc-msc.mindchain.info/", + "https://seednode.mindchain.info", + "wss://seednode.mindchain.info/ws" + ], + "faucets": [], + "nativeCurrency": { "name": "MIND Coin", "symbol": "MIND", "decimals": 18 }, + "infoURL": "https://mindscan.info", + "shortName": "MIND", + "chainId": 9996, + "networkId": 9996, + "explorers": [ + { + "name": "Mind Chain explorer", + "url": "https://mainnet.mindscan.info", + "standard": "EIP3091" + } + ] + }, { "name": "AltLayer Testnet", "chain": "ETH", @@ -12653,10 +13030,16 @@ "networkId": 10200, "explorers": [ { - "name": "blockscout", + "name": "blockscout-chiadochain", "url": "https://blockscout.chiadochain.net", "icon": "blockscout", "standard": "EIP3091" + }, + { + "name": "blockscout", + "url": "https://gnosis-chiado.blockscout.com", + "icon": "blockscout", + "standard": "EIP3091" } ] }, @@ -12897,7 +13280,7 @@ { "name": "Haqq Network", "chain": "Haqq", - "rpc": ["https://rpc.eth.haqq.network"], + "rpc": ["https://rpc.eth.haqq.network", "https://haqq-evm.publicnode.com"], "faucets": [], "nativeCurrency": { "name": "Islamic Coin", @@ -13176,26 +13559,49 @@ ] }, { - "name": "Credit Smartchain Mainnet", + "name": "Credit Smart Chain", "chain": "CREDIT", - "rpc": ["https://mainnet-rpc.cscscan.io"], + "rpc": ["https://rpc.creditsmartchain.com"], "faucets": [], "nativeCurrency": { "name": "Credit", "symbol": "CREDIT", "decimals": 18 }, "features": [{ "name": "EIP155" }, { "name": "EIP1559" }], "infoURL": "https://creditsmartchain.com", "shortName": "Credit", "chainId": 13308, - "networkId": 1, + "networkId": 13308, "icon": "credit", "explorers": [ { - "name": "CSC Scan", - "url": "https://explorer.cscscan.io", + "name": "Creditscan", + "url": "https://scan.creditsmartchain.com", "icon": "credit", "standard": "EIP3091" } ] }, + { + "name": "Beam Testnet", + "chain": "BEAM", + "rpc": ["https://subnets.avax.network/beam/testnet/rpc"], + "features": [{ "name": "EIP1559" }], + "faucets": [], + "nativeCurrency": { + "name": "Merit Circle", + "symbol": "MC", + "decimals": 18 + }, + "infoURL": "https://gaming.meritcircle.io", + "shortName": "BEAM", + "chainId": 13337, + "networkId": 13337, + "explorers": [ + { + "name": "Beam Explorer", + "url": "https://subnets-test.avax.network/beam", + "standard": "EIP3091" + } + ] + }, { "name": "Phoenix Mainnet", "chain": "Phoenix", @@ -13491,6 +13897,26 @@ } ] }, + { + "name": "Smart Trade Networks", + "chain": "Smart Trade Networks", + "rpc": ["https://beefledgerwallet.com:8544"], + "faucets": [], + "nativeCurrency": { "name": "STN", "symbol": "STN", "decimals": 18 }, + "infoURL": "https://www.smarttradenetworks.com", + "shortName": "STN", + "chainId": 18122, + "networkId": 18122, + "icon": "stn", + "explorers": [ + { + "name": "stnscan", + "url": "https://stnscan.com", + "icon": "stn", + "standard": "none" + } + ] + }, { "name": "Proof Of Memes", "title": "Proof Of Memes Mainnet", @@ -14352,7 +14778,8 @@ "rpc": [ "https://arbitrum-mainnet.infura.io/v3/${INFURA_API_KEY}", "https://arb-mainnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}", - "https://arb1.arbitrum.io/rpc" + "https://arb1.arbitrum.io/rpc", + "https://arbitrum-one.publicnode.com" ], "faucets": [], "explorers": [ @@ -14381,7 +14808,10 @@ "chain": "ETH", "networkId": 42170, "nativeCurrency": { "name": "Ether", "symbol": "ETH", "decimals": 18 }, - "rpc": ["https://nova.arbitrum.io/rpc"], + "rpc": [ + "https://nova.arbitrum.io/rpc", + "https://arbitrum-nova.publicnode.com" + ], "faucets": [], "explorers": [ { @@ -14470,6 +14900,26 @@ } ] }, + { + "name": "Kinto Testnet", + "title": "Kinto Testnet", + "chain": "ETH", + "rpc": ["http://35.215.120.180:8545"], + "features": [{ "name": "EIP155" }, { "name": "EIP1559" }], + "faucets": [], + "nativeCurrency": { "name": "Ether", "symbol": "ETH", "decimals": 18 }, + "infoURL": "https://ethereum.org", + "shortName": "keth", + "chainId": 42888, + "networkId": 42888, + "explorers": [ + { + "name": "kintoscan", + "url": "http://35.215.120.180:4000", + "standard": "EIP3091" + } + ] + }, { "name": "Athereum", "chain": "ATH", @@ -15904,7 +16354,8 @@ { "name": "basescout", "url": "https://base-goerli.blockscout.com", - "standard": "none" + "icon": "blockscout", + "standard": "EIP3091" } ] }, @@ -16962,7 +17413,7 @@ { "name": "Decimal Smart Chain Testnet", "chain": "tDSC", - "rpc": ["https://testnet-val.decimalchain.com/web3"], + "rpc": ["https://testnet-val.decimalchain.com/web3/"], "faucets": [], "nativeCurrency": { "name": "Decimal", "symbol": "tDEL", "decimals": 18 }, "features": [{ "name": "EIP155" }, { "name": "EIP1559" }], @@ -17047,6 +17498,52 @@ } ] }, + { + "name": "Reapchain Mainnet", + "chain": "REAP", + "rpc": ["https://rpc.reapchain.org"], + "faucets": [], + "nativeCurrency": { "name": "Reap", "symbol": "REAP", "decimals": 18 }, + "features": [], + "infoURL": "https://reapchain.com", + "shortName": "reap", + "chainId": 221230, + "networkId": 221230, + "icon": "reapchain", + "explorers": [ + { + "name": "Reapchain Dashboard", + "url": "https://dashboard.reapchain.org", + "icon": "reapchain", + "standard": "none" + } + ] + }, + { + "name": "Reapchain Testnet", + "chain": "REAP", + "rpc": ["https://test-rpc.reapchain.org"], + "faucets": ["http://faucet.reapchain.com"], + "nativeCurrency": { + "name": "test-Reap", + "symbol": "tREAP", + "decimals": 18 + }, + "features": [], + "infoURL": "https://reapchain.com", + "shortName": "reap-testnet", + "chainId": 221231, + "networkId": 221231, + "icon": "reapchain", + "explorers": [ + { + "name": "Reapchain Testnet Dashboard", + "url": "https://test-dashboard.reapchain.org", + "icon": "reapchain", + "standard": "none" + } + ] + }, { "name": "Taf ECO Chain Mainnet", "chain": "Taf ECO Chain", @@ -17540,7 +18037,10 @@ "symbol": "AGOR", "decimals": 18 }, - "rpc": ["https://goerli-rollup.arbitrum.io/rpc/"], + "rpc": [ + "https://goerli-rollup.arbitrum.io/rpc", + "https://arbitrum-goerli.publicnode.com" + ], "faucets": [], "infoURL": "https://arbitrum.io/", "explorers": [ @@ -17733,35 +18233,55 @@ { "name": "Scroll Sepolia Testnet", "chain": "ETH", - "status": "incubating", - "rpc": [], + "status": "active", + "rpc": ["https://sepolia-rpc.scroll.io/"], "faucets": [], "nativeCurrency": { "name": "Ether", "symbol": "ETH", "decimals": 18 }, "infoURL": "https://scroll.io", "shortName": "scr-sepolia", "chainId": 534351, "networkId": 534351, - "explorers": [], - "parent": { "type": "L2", "chain": "eip155-11155111", "bridges": [] } + "explorers": [ + { + "name": "Scroll Sepolia Testnet Block Explorer", + "url": "https://sepolia-blockscout.scroll.io", + "standard": "EIP3091" + } + ], + "parent": { + "type": "L2", + "chain": "eip155-11155111", + "bridges": [{ "url": "https://scroll.io/bridge" }] + } }, { "name": "Scroll", "chain": "ETH", "status": "incubating", - "rpc": [], + "rpc": ["https://rpc.scroll.io"], "faucets": [], "nativeCurrency": { "name": "Ether", "symbol": "ETH", "decimals": 18 }, "infoURL": "https://scroll.io", "shortName": "scr", "chainId": 534352, "networkId": 534352, - "explorers": [], - "parent": { "type": "L2", "chain": "eip155-1", "bridges": [] } + "explorers": [ + { + "name": "Scroll Mainnet Block Explorer", + "url": "https://blockscout.scroll.io", + "standard": "EIP3091" + } + ], + "parent": { + "type": "L2", + "chain": "eip155-1", + "bridges": [{ "url": "https://scroll.io/bridge" }] + } }, { "name": "Scroll Alpha Testnet", "chain": "ETH", - "status": "active", + "status": "deprecated", "rpc": ["https://alpha-rpc.scroll.io/l2"], "faucets": [], "nativeCurrency": { "name": "Ether", "symbol": "ETH", "decimals": 18 }, @@ -17772,12 +18292,7 @@ "explorers": [ { "name": "Scroll Alpha Testnet Block Explorer", - "url": "https://blockscout.scroll.io", - "standard": "EIP3091" - }, - { - "name": "Scroll Alpha Testnet Block Explorer", - "url": "https://scrollscan.co", + "url": "https://alpha-blockscout.scroll.io", "standard": "EIP3091" } ], @@ -17787,20 +18302,14 @@ "name": "Scroll Pre-Alpha Testnet", "chain": "ETH", "status": "deprecated", - "rpc": ["https://prealpha-rpc.scroll.io/l2"], - "faucets": ["https://prealpha.scroll.io/faucet"], + "rpc": [], + "faucets": [], "nativeCurrency": { "name": "Ether", "symbol": "TSETH", "decimals": 18 }, "infoURL": "https://scroll.io", "shortName": "scr-prealpha", "chainId": 534354, "networkId": 534354, - "explorers": [ - { - "name": "Scroll L2 Block Explorer", - "url": "https://l2scan.scroll.io", - "standard": "EIP3091" - } - ] + "explorers": [] }, { "name": "Shinarium Beta", @@ -18332,6 +18841,26 @@ "slip44": 1, "explorers": [] }, + { + "name": "Manta Pacific Testnet", + "chain": "Manta Pacific", + "rpc": ["https://manta-testnet.calderachain.xyz/http"], + "faucets": [], + "nativeCurrency": { "name": "Manta", "symbol": "MANTA", "decimals": 18 }, + "features": [{ "name": "EIP155" }, { "name": "EIP1559" }], + "infoURL": "https://manta-testnet.caldera.dev/", + "shortName": "manta", + "chainId": 3441005, + "networkId": 3441005, + "icon": "manta", + "explorers": [ + { + "name": "manta-testnet Explorer", + "url": "https://manta-testnet.calderaexplorer.xyz", + "standard": "EIP3091" + } + ] + }, { "name": "AltLayer Zero Gas Network", "chain": "ETH", @@ -18670,7 +19199,10 @@ "rpc": [ "https://rpc.sepolia.org", "https://rpc2.sepolia.org", - "https://rpc-sepolia.rockx.com" + "https://rpc-sepolia.rockx.com", + "https://rpc.sepolia.ethpandaops.io", + "https://sepolia.infura.io/v3/${INFURA_API_KEY}", + "wss://sepolia.infura.io/v3/${INFURA_API_KEY}" ], "faucets": [ "http://fauceth.komputing.org?chain=11155111&address=${ADDRESS}" @@ -19140,6 +19672,12 @@ "name": "neonscan", "url": "https://devnet.neonscan.org", "standard": "EIP3091" + }, + { + "name": "blockscout", + "url": "https://neon-devnet.blockscout.com", + "icon": "blockscout", + "standard": "EIP3091" } ] }, @@ -19522,10 +20060,19 @@ { "name": "Harmony Mainnet Shard 0", "chain": "Harmony", - "rpc": ["https://api.harmony.one", "https://api.s0.t.hmny.io"], - "faucets": ["https://free-online-app.com/faucet-for-eth-evm-chains/"], + "rpc": [ + "https://api.harmony.one", + "https://a.api.s0.t.hmny.io", + "https://api.s0.t.hmny.io", + "https://rpc.ankr.com/harmony", + "https://harmony.api.onfinality.io/public", + "https://1rpc.io/one" + ], + "faucets": [], "nativeCurrency": { "name": "ONE", "symbol": "ONE", "decimals": 18 }, "infoURL": "https://www.harmony.one/", + "slip44": 1023, + "ens": { "registry": "0x4cd2563118e57b19179d8dc033f2b0c5b5d69ff5" }, "shortName": "hmy-s0", "chainId": 1666600000, "networkId": 1666600000, @@ -19544,9 +20091,17 @@ "faucets": [], "nativeCurrency": { "name": "ONE", "symbol": "ONE", "decimals": 18 }, "infoURL": "https://www.harmony.one/", + "slip44": 1023, "shortName": "hmy-s1", "chainId": 1666600001, - "networkId": 1666600001 + "networkId": 1666600001, + "explorers": [ + { + "name": "Harmony Block Explorer", + "url": "https://explorer.harmony.one/blocks/shard/1", + "standard": "none" + } + ] }, { "name": "Harmony Mainnet Shard 2", @@ -19555,9 +20110,17 @@ "faucets": [], "nativeCurrency": { "name": "ONE", "symbol": "ONE", "decimals": 18 }, "infoURL": "https://www.harmony.one/", + "slip44": 1023, "shortName": "hmy-s2", "chainId": 1666600002, - "networkId": 1666600002 + "networkId": 1666600002, + "explorers": [ + { + "name": "Harmony Block Explorer", + "url": "https://explorer.harmony.one/blocks/shard/2", + "standard": "none" + } + ] }, { "name": "Harmony Mainnet Shard 3", @@ -19566,9 +20129,17 @@ "faucets": [], "nativeCurrency": { "name": "ONE", "symbol": "ONE", "decimals": 18 }, "infoURL": "https://www.harmony.one/", + "slip44": 1023, "shortName": "hmy-s3", "chainId": 1666600003, - "networkId": 1666600003 + "networkId": 1666600003, + "explorers": [ + { + "name": "Harmony Block Explorer", + "url": "https://explorer.harmony.one/blocks/shard/3", + "standard": "none" + } + ] }, { "name": "Harmony Testnet Shard 0", @@ -19583,7 +20154,7 @@ "explorers": [ { "name": "Harmony Testnet Block Explorer", - "url": "https://explorer.pops.one", + "url": "https://explorer.testnet.harmony.one", "standard": "EIP3091" } ] @@ -19592,52 +20163,43 @@ "name": "Harmony Testnet Shard 1", "chain": "Harmony", "rpc": ["https://api.s1.b.hmny.io"], - "faucets": [], + "faucets": ["https://faucet.pops.one"], "nativeCurrency": { "name": "ONE", "symbol": "ONE", "decimals": 18 }, "infoURL": "https://www.harmony.one/", "shortName": "hmy-b-s1", "chainId": 1666700001, - "networkId": 1666700001 + "networkId": 1666700001, + "explorers": [ + { + "name": "Harmony Block Explorer", + "url": "https://explorer.testnet.harmony.one", + "standard": "none" + } + ] }, { - "name": "Harmony Testnet Shard 2", + "name": "Harmony Devnet Shard 0", "chain": "Harmony", - "rpc": ["https://api.s2.b.hmny.io"], + "rpc": ["https://api.s0.ps.hmny.io"], "faucets": [], "nativeCurrency": { "name": "ONE", "symbol": "ONE", "decimals": 18 }, "infoURL": "https://www.harmony.one/", - "shortName": "hmy-b-s2", - "chainId": 1666700002, - "networkId": 1666700002 + "shortName": "hmy-ps-s0", + "chainId": 1666900000, + "networkId": 1666900000, + "explorers": [] }, { - "name": "Harmony Testnet Shard 3", + "name": "Harmony Devnet Shard 1", "chain": "Harmony", - "rpc": ["https://api.s3.b.hmny.io"], + "rpc": ["https://api.s1.ps.hmny.io"], "faucets": [], "nativeCurrency": { "name": "ONE", "symbol": "ONE", "decimals": 18 }, "infoURL": "https://www.harmony.one/", - "shortName": "hmy-b-s3", - "chainId": 1666700003, - "networkId": 1666700003 - }, - { - "name": "Harmony Devnet Shard 0", - "chain": "Harmony", - "rpc": ["https://api.s1.ps.hmny.io", "https://api.s1.ps.hmny.io"], - "faucets": ["http://dev.faucet.easynode.one/"], - "nativeCurrency": { "name": "ONE", "symbol": "ONE", "decimals": 18 }, - "infoURL": "https://www.harmony.one/", - "shortName": "hmy-ps-s0", - "chainId": 1666900000, - "networkId": 1666900000, - "explorers": [ - { - "name": "Harmony Block Explorer", - "url": "https://explorer.ps.hmny.io", - "standard": "EIP3091" - } - ] + "shortName": "hmy-ps-s1", + "chainId": 1666900001, + "networkId": 1666900001, + "explorers": [] }, { "name": "DataHopper", diff --git a/src/server/controllers/repository/get-file-static.stateless.paths.yaml b/src/server/controllers/repository/get-file-static.stateless.paths.yaml index 6a75994df..0653f54a0 100644 --- a/src/server/controllers/repository/get-file-static.stateless.paths.yaml +++ b/src/server/controllers/repository/get-file-static.stateless.paths.yaml @@ -1,7 +1,7 @@ openapi: "3.0.0" paths: - /contracts/{full_match | partial_match}/{chain}/{address}/{filePath}: + /repository/contracts/{full_match | partial_match}/{chain}/{address}/{filePath}: get: summary: Get file from /repository description: Retrieve statically served files over the server. diff --git a/src/server/services/RepositoryService.ts b/src/server/services/RepositoryService.ts index 1b44a187d..0398e5716 100644 --- a/src/server/services/RepositoryService.ts +++ b/src/server/services/RepositoryService.ts @@ -521,18 +521,35 @@ export class RepositoryService implements IRepositoryService { address: string, sources: StringMap ) { + const pathTranslation: StringMap = {}; for (const sourcePath in sources) { + const { sanitizedPath, originalPath } = this.sanitizePath(sourcePath); + if (sanitizedPath !== originalPath) { + pathTranslation[originalPath] = sanitizedPath; + } this.save( { matchQuality, chainId, address, source: true, - fileName: this.sanitizePath(sourcePath), + fileName: sanitizedPath, }, sources[sourcePath] ); } + // Finally save the path translation + if (Object.keys(pathTranslation).length === 0) return; + this.save( + { + matchQuality, + chainId, + address, + source: false, + fileName: "path-translation.json", + }, + JSON.stringify(pathTranslation) + ); } private storeJSON( @@ -609,24 +626,26 @@ export class RepositoryService implements IRepositoryService { await this.ipfsClient.files.cp(addResult.cid, mfsPath, { parents: true }); } } - // This needs to be removed at some point https://github.com/ethereum/sourcify/issues/515 - private sanitizePath(originalPath: string): string { - const parsedPath = path.parse(originalPath); + private sanitizePath(originalPath: string) { + // Clean ../ and ./ from the path. Also collapse multiple slashes into one. + let sanitizedPath = path.normalize(originalPath); + + // If there are no upper folders to traverse, path.normalize will keep ../ parts. Need to remove any of those. + const parsedPath = path.parse(sanitizedPath); const sanitizedDir = parsedPath.dir .split(path.sep) .filter((segment) => segment !== "..") - .join(path.sep) - .replace(/[^a-z0-9_./-]/gim, "_") - .replace(/(^|\/)[.]+($|\/)/, "_"); + .join(path.sep); // Force absolute paths to be relative if (parsedPath.root) { - parsedPath.root = ""; parsedPath.dir = sanitizedDir.slice(parsedPath.root.length); + parsedPath.root = ""; } else { parsedPath.dir = sanitizedDir; } - return path.format(parsedPath); + sanitizedPath = path.format(parsedPath); + return { sanitizedPath, originalPath }; } } diff --git a/src/sourcify-chains.ts b/src/sourcify-chains.ts index 4644a2469..e74953a28 100644 --- a/src/sourcify-chains.ts +++ b/src/sourcify-chains.ts @@ -269,6 +269,10 @@ const sourcifyChainsExtensions: SourcifyChainsExtensionsObject = { supported: true, monitored: false, }, + "314159": { + supported: true, + monitored: false, + }, "137": { supported: true, monitored: true, @@ -768,13 +772,11 @@ const sourcifyChainsExtensions: SourcifyChainsExtensionsObject = { txRegex: getBlockscoutRegex(), }, "2047": { - // Turned off support as RPCs are failing - // Stratos Testnet - supported: false, + // Stratos Testnet (Mesos) + supported: true, monitored: false, contractFetchAddress: - "https://web3-testnet-explorer.thestratos.org/" + BLOCKSCOUT_SUFFIX, - rpc: ["https://web3-testnet-rpc.thestratos.org"], + "https://web3-explorer-mesos.thestratos.org/" + BLOCKSCOUT_SUFFIX, txRegex: getBlockscoutRegex(), }, "641230": { @@ -884,7 +886,12 @@ const sourcifyChainsExtensions: SourcifyChainsExtensionsObject = { txRegex: getBlockscoutRegex(), }, "167005": { - // Taiko Alpha-3 + // Taiko Grimsvotn L2 + supported: true, + monitored: false, + }, + "167006": { + // Taiko Eldfell L3 supported: true, monitored: false, }, @@ -903,6 +910,14 @@ const sourcifyChainsExtensions: SourcifyChainsExtensionsObject = { `https://glacier-api.avax.network/v1/chains/6119/` + AVALANCHE_SUBNET_SUFFIX, }, + "13337": { + // BEAM Testnet + supported: true, + monitored: false, + contractFetchAddress: + `https://glacier-api.avax.network/v1/chains/13337/` + + AVALANCHE_SUBNET_SUFFIX, + }, "2222": { // Kava EVM supported: true, @@ -939,18 +954,37 @@ const sourcifyChainsExtensions: SourcifyChainsExtensionsObject = { // MAP Testnet Makalu supported: true, monitored: false, - contractFetchAddress: - "https://testnet.maposcan.io/" + BLOCKSCOUT_SUFFIX, + contractFetchAddress: "https://testnet.maposcan.io/" + BLOCKSCOUT_SUFFIX, txRegex: getBlockscoutRegex(), }, "22776": { // map-relay-chain Mainnet supported: true, monitored: false, - contractFetchAddress: - "https://maposcan.io/" + BLOCKSCOUT_SUFFIX, + contractFetchAddress: "https://maposcan.io/" + BLOCKSCOUT_SUFFIX, txRegex: getBlockscoutRegex(), }, + "2021": { + // Edgeware EdgeEVM Mainnet + supported: true, + monitored: false, + contractFetchAddress: "https://edgscan.live/" + BLOCKSCOUT_SUFFIX, + txRegex: getBlockscoutRegex(), + }, + "250": { + // FTM Fantom Opera Mainnet + supported: true, + monitored: false, + contractFetchAddress: "https://fantom.dex.guru/" + ETHERSCAN_SUFFIX, + txRegex: ETHERSCAN_REGEX, + }, + "42170": { + // Arbitrum Nova + supported: true, + monitored: false, + contractFetchAddress: "https://nova.dex.guru/" + ETHERSCAN_SUFFIX, + txRegex: ETHERSCAN_REGEX, + }, }; const sourcifyChainsMap: SourcifyChainMap = {}; @@ -992,6 +1026,21 @@ for (const i in allChains) { } } +// Check if all chains in sourcify-chains.ts are in chains.json +const missingChains = []; +for (const chainId in sourcifyChainsExtensions) { + if (!sourcifyChainsMap[chainId]) { + missingChains.push(chainId); + } +} +if (missingChains.length > 0) { + throw new Error( + `Some of the chains in sourcify-chains.ts are not in chains.json: ${missingChains.join( + "," + )}` + ); +} + const sourcifyChainsArray = getSortedChainsArray(sourcifyChainsMap); const supportedChainsArray = sourcifyChainsArray.filter( (chain) => chain.supported @@ -1033,8 +1082,8 @@ export function getSortedChainsArray( getPrimarySortKey(a) > getPrimarySortKey(b) ? 1 : getPrimarySortKey(b) > getPrimarySortKey(a) - ? -1 - : 0 + ? -1 + : 0 ); const sortedChains = ethereumChains.concat(otherChains); diff --git a/test/RepositoryService.test.js b/test/RepositoryService.test.js new file mode 100644 index 000000000..e8ce3f8d3 --- /dev/null +++ b/test/RepositoryService.test.js @@ -0,0 +1,77 @@ +const { expect } = require("chai"); +const { + RepositoryService, +} = require("../dist/server/services/RepositoryService"); + +describe("RepositoryService", () => { + const instance = new RepositoryService("./dist/data/mock-repository"); + + describe("sanitizePath function", () => { + it("should remove directory traversal sequences", () => { + const result = instance.sanitizePath("some/path/../to/file.txt"); + expect(result.sanitizedPath).to.equal("some/to/file.txt"); + expect(result.originalPath).to.equal("some/path/../to/file.txt"); + }); + + it("should return the original path unchanged", () => { + const result = instance.sanitizePath("some/path/to/file.txt"); + expect(result.sanitizedPath).to.equal("some/path/to/file.txt"); + expect(result.originalPath).to.equal("some/path/to/file.txt"); + }); + + it("should convert absolute paths to relative", () => { + const result = instance.sanitizePath("/absolute/path/to/file.txt"); + expect(result.sanitizedPath).to.equal("absolute/path/to/file.txt"); + expect(result.originalPath).to.equal("/absolute/path/to/file.txt"); + }); + + it("should not keep any .. even if there are no upper directories left", () => { + const result = instance.sanitizePath("path/../../../../to/file.txt"); + expect(result.sanitizedPath).to.equal("to/file.txt"); + expect(result.originalPath).to.equal("path/../../../../to/file.txt"); + }); + + it("should sanitize a path containing localhost and directory traversal sequences", () => { + const result = instance.sanitizePath( + "localhost/../Users/pc/workspace/remixsrc/openzeppelin-contracts/IOSB/token/ERC20/ERC20Pausable.sol" + ); + expect(result.sanitizedPath).to.equal( + "Users/pc/workspace/remixsrc/openzeppelin-contracts/IOSB/token/ERC20/ERC20Pausable.sol" + ); + expect(result.originalPath).to.equal( + "localhost/../Users/pc/workspace/remixsrc/openzeppelin-contracts/IOSB/token/ERC20/ERC20Pausable.sol" + ); + }); + + it("should not modify a file name containing '..'", () => { + const result = instance.sanitizePath("myToken..sol"); + expect(result.sanitizedPath).to.equal("myToken..sol"); + expect(result.originalPath).to.equal("myToken..sol"); + }); + + it("should sanitize a URL path containing directory traversal sequences", () => { + const result = instance.sanitizePath( + "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/../../utils/Context.sol" + ); + expect(result.sanitizedPath).to.equal( + "https:/github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Context.sol" + ); + expect(result.originalPath).to.equal( + "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/../../utils/Context.sol" + ); + }); + it("should not change special characters in the path", () => { + const result = instance.sanitizePath( + "project:/contracts/a`~!@#$%^&*()-=_+[]{}|\\;:'\",<>?ÿø±ö«»¿ð�~K�~X��~_~X~@.sol" + ); + expect(result.sanitizedPath).to.equal( + "project:/contracts/a`~!@#$%^&*()-=_+[]{}|\\;:'\",<>?ÿø±ö«»¿ð�~K�~X��~_~X~@.sol" + ); + expect(result.originalPath).to.equal( + "project:/contracts/a`~!@#$%^&*()-=_+[]{}|\\;:'\",<>?ÿø±ö«»¿ð�~K�~X��~_~X~@.sol" + ); + }); + }); + + // ... more tests for other methods of RepositoryService +}); diff --git a/test/chains/chain-tests.js b/test/chains/chain-tests.js index c78f8917f..a141d0023 100644 --- a/test/chains/chain-tests.js +++ b/test/chains/chain-tests.js @@ -18,7 +18,7 @@ const rimraf = require("rimraf"); const addContext = require("mochawesome/addContext"); const { assertVerification } = require("../helpers/assertions"); -const TEST_TIME = 30000; // 30 seconds +const TEST_TIME = process.env.TEST_TIME || 30000; // 30 seconds // Extract the chainId from new chain support pull request, if exists const newAddedChainId = process.env.NEW_CHAIN_ID; @@ -30,6 +30,7 @@ let anyTestsPass = false; // Fail when zero tests passing chai.use(chaiHttp); describe("Test Supported Chains", function () { + console.log(`Set up tests timeout with ${Math.floor(TEST_TIME / 1000)} secs`); this.timeout(TEST_TIME); const server = new Server(); let currentResponse = null; // to log server response when test fails @@ -764,7 +765,7 @@ describe("Test Supported Chains", function () { // Stratos Testnet verifyContract( - "0x9082db5F71534984DEAC8E4ed66cFe364d77dd36", + "0x999986dE5D86Ae4bbd4b9AbFBD65352622D11326", "2047", "Stratos Testnet", ["shared/1_Storage.sol"], @@ -888,15 +889,25 @@ describe("Test Supported Chains", function () { "shared/1_Storage.metadata.json" ); - // Taiko Alpha-3 Testnet + // Taiko Grimsvotn L2 verifyContract( "0x68107Fb54f5f29D8e0B3Ac44a99f4444D1F22a68", "167005", - "Taiko Alpha-3 Testnet", + "Taiko Grimsvotn L2", ["shared/1_Storage.sol"], "shared/1_Storage.metadata.json" ); + // Taiko Eldfell L3 + verifyContract( + "0x270a7521B3678784f96848D441fE1B2dc2f040D8", + "167006", + "Taiko Eldfell L3", + ["shared/1_Storage.sol"], + "shared/1_Storage.metadata.json", + "partial" + ); + // ZORA Mainnet verifyContract( "0x090734f94FA67590702421A9B61892509b7CE80A", @@ -919,6 +930,16 @@ describe("Test Supported Chains", function () { "6119/UptnNFTsV1.metadata.json" ); + // BEAM Chain Testnet + verifyContract( + "0x9BF49b704EE2A095b95c1f2D4EB9010510c41C9E", + "13337", + "BEAM Chain", + ["multicall3/Multicall3.sol"], + "multicall3/multicall3.metadata.json", + "partial" + ); + // KAVA EVM verifyContract( "0xAdFa11e737ec8fA6e91091468aEF33a66Ae0044c", @@ -955,6 +976,15 @@ describe("Test Supported Chains", function () { "shared/1_Storage.metadata.json" ); + // Filecoin Calibration Testnet + verifyContract( + "0xB34d5e2Eb6eCFDe11cC63955b43335A2407A4683", + "314159", + "Filecoin Calibration Testnet", + ["shared/1_Storage.sol"], + "shared/1_Storage.metadata.json" + ); + // Zilliqa EVM verifyContract( "0x6F85669808e20b121980DE8E7a794a0cc90fDc77", @@ -997,6 +1027,33 @@ describe("Test Supported Chains", function () { "shared/1_Storage.metadata.json" ); + // Edgeware EdgeEVM Mainnet + verifyContract( + "0xCc21c38A22918a86d350dF9aB9c5A60314A01e06", + "2021", + "Edgeware EdgeEVM Mainnet", + ["shared/1_Storage.sol"], + "shared/1_Storage.metadata.json" + ); + + // Arbitrum Nova + verifyContract( + "0xC2141cb30Ef8cE403569D59964eaF3D66848822F", + "42170", + "Arbitrum Nova", + ["shared/1_Storage.sol"], + "shared/1_Storage.metadata.json" + ); + + // FTM Fantom Opera Mainnet + verifyContract( + "0xc47856bEBCcc2BBB23E7a5E1Ba8bB4Fffa5C5476", + "250", + "Fantom Opera", + ["shared/1_Storage.sol"], + "shared/1_Storage.metadata.json" + ); + // Finally check if all the "supported: true" chains have been tested it("should have tested all supported chains", function (done) { if (newAddedChainId) { @@ -1037,7 +1094,8 @@ describe("Test Supported Chains", function () { chainId, chainName, relativeSourcePathsArray, // Allow multiple source files - relativeMetadataPath + relativeMetadataPath, + expectedStatus = "perfect" ) { // If it is a pull request for adding new chain support, only test the new chain if (newAddedChainId && newAddedChainId != chainId) return; @@ -1076,7 +1134,7 @@ describe("Test Supported Chains", function () { files: files, }) .end((err, res) => { - assertVerification(err, res, done, address, chainId); + assertVerification(err, res, done, address, chainId, expectedStatus); anyTestsPass = true; }); }); diff --git a/test/chains/deployContracts.js b/test/chains/deployContracts.js index 0bcc0bdc1..497b9c3d4 100644 --- a/test/chains/deployContracts.js +++ b/test/chains/deployContracts.js @@ -2,7 +2,7 @@ const { deployFromPrivateKey } = require("../helpers/helpers"); const StorageArtifact = require("./sources/shared/1_Storage.json"); const { supportedChainsArray } = require("../../dist/sourcify-chains"); const { program } = require("commander"); -const { JsonRpcApiProvider } = require("ethers"); +const { JsonRpcProvider } = require("ethers"); program .description( @@ -18,9 +18,10 @@ program "--privateKey ", "Private key of the account that will deploy the contract" ) + // DEPRECATED .option( "--immutableValue ", - "Value to be stored as the immutable value. " + "Value to be stored as the immutable value. (DEPRECATED)" ) .showSuggestionAfterError() .showHelpAfterError("(add --help for additional information)"); @@ -44,10 +45,10 @@ async function main(chainId, privateKey) { let provider; console.log("Using rpc: " + chain.rpc[0]); try { - provider = new JsonRpcApiProvider(chain.rpc[0]); + provider = new JsonRpcProvider(chain.rpc[0]); } catch (err) { console.log( - `Can't initiate a Provider instance with the chain: ${chain}. \n\nMake sure the chainId is added to src/sourcify-chains.ts and built with npx lerna run build` + `Can't initiate a Provider instance with the chain: ${JSON.stringify(chain)}. \n\nMake sure the chainId is added to src/sourcify-chains.ts and built with npx lerna run build` ); throw new Error(err); } diff --git a/test/chains/sources/multicall3/Multicall3.sol b/test/chains/sources/multicall3/Multicall3.sol new file mode 100644 index 000000000..7582ea960 --- /dev/null +++ b/test/chains/sources/multicall3/Multicall3.sol @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.12; + +/// @title Multicall3 +/// @notice Aggregate results from multiple function calls +/// @dev Multicall & Multicall2 backwards-compatible +/// @dev Aggregate methods are marked `payable` to save 24 gas per call +/// @author Michael Elliot +/// @author Joshua Levine +/// @author Nick Johnson +/// @author Andreas Bigger +/// @author Matt Solomon +contract Multicall3 { + struct Call { + address target; + bytes callData; + } + + struct Call3 { + address target; + bool allowFailure; + bytes callData; + } + + struct Call3Value { + address target; + bool allowFailure; + uint256 value; + bytes callData; + } + + struct Result { + bool success; + bytes returnData; + } + + /// @notice Backwards-compatible call aggregation with Multicall + /// @param calls An array of Call structs + /// @return blockNumber The block number where the calls were executed + /// @return returnData An array of bytes containing the responses + function aggregate(Call[] calldata calls) public payable returns (uint256 blockNumber, bytes[] memory returnData) { + blockNumber = block.number; + uint256 length = calls.length; + returnData = new bytes[](length); + Call calldata call; + for (uint256 i = 0; i < length;) { + bool success; + call = calls[i]; + (success, returnData[i]) = call.target.call(call.callData); + require(success, "Multicall3: call failed"); + unchecked { ++i; } + } + } + + /// @notice Backwards-compatible with Multicall2 + /// @notice Aggregate calls without requiring success + /// @param requireSuccess If true, require all calls to succeed + /// @param calls An array of Call structs + /// @return returnData An array of Result structs + function tryAggregate(bool requireSuccess, Call[] calldata calls) public payable returns (Result[] memory returnData) { + uint256 length = calls.length; + returnData = new Result[](length); + Call calldata call; + for (uint256 i = 0; i < length;) { + Result memory result = returnData[i]; + call = calls[i]; + (result.success, result.returnData) = call.target.call(call.callData); + if (requireSuccess) require(result.success, "Multicall3: call failed"); + unchecked { ++i; } + } + } + + /// @notice Backwards-compatible with Multicall2 + /// @notice Aggregate calls and allow failures using tryAggregate + /// @param calls An array of Call structs + /// @return blockNumber The block number where the calls were executed + /// @return blockHash The hash of the block where the calls were executed + /// @return returnData An array of Result structs + function tryBlockAndAggregate(bool requireSuccess, Call[] calldata calls) public payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) { + blockNumber = block.number; + blockHash = blockhash(block.number); + returnData = tryAggregate(requireSuccess, calls); + } + + /// @notice Backwards-compatible with Multicall2 + /// @notice Aggregate calls and allow failures using tryAggregate + /// @param calls An array of Call structs + /// @return blockNumber The block number where the calls were executed + /// @return blockHash The hash of the block where the calls were executed + /// @return returnData An array of Result structs + function blockAndAggregate(Call[] calldata calls) public payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) { + (blockNumber, blockHash, returnData) = tryBlockAndAggregate(true, calls); + } + + /// @notice Aggregate calls, ensuring each returns success if required + /// @param calls An array of Call3 structs + /// @return returnData An array of Result structs + function aggregate3(Call3[] calldata calls) public payable returns (Result[] memory returnData) { + uint256 length = calls.length; + returnData = new Result[](length); + Call3 calldata calli; + for (uint256 i = 0; i < length;) { + Result memory result = returnData[i]; + calli = calls[i]; + (result.success, result.returnData) = calli.target.call(calli.callData); + assembly { + // Revert if the call fails and failure is not allowed + // `allowFailure := calldataload(add(calli, 0x20))` and `success := mload(result)` + if iszero(or(calldataload(add(calli, 0x20)), mload(result))) { + // set "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)"))) + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + // set data offset + mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020) + // set length of revert string + mstore(0x24, 0x0000000000000000000000000000000000000000000000000000000000000017) + // set revert string: bytes32(abi.encodePacked("Multicall3: call failed")) + mstore(0x44, 0x4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000) + revert(0x00, 0x64) + } + } + unchecked { ++i; } + } + } + + /// @notice Aggregate calls with a msg value + /// @notice Reverts if msg.value is less than the sum of the call values + /// @param calls An array of Call3Value structs + /// @return returnData An array of Result structs + function aggregate3Value(Call3Value[] calldata calls) public payable returns (Result[] memory returnData) { + uint256 valAccumulator; + uint256 length = calls.length; + returnData = new Result[](length); + Call3Value calldata calli; + for (uint256 i = 0; i < length;) { + Result memory result = returnData[i]; + calli = calls[i]; + uint256 val = calli.value; + // Humanity will be a Type V Kardashev Civilization before this overflows - andreas + // ~ 10^25 Wei in existence << ~ 10^76 size uint fits in a uint256 + unchecked { valAccumulator += val; } + (result.success, result.returnData) = calli.target.call{value: val}(calli.callData); + assembly { + // Revert if the call fails and failure is not allowed + // `allowFailure := calldataload(add(calli, 0x20))` and `success := mload(result)` + if iszero(or(calldataload(add(calli, 0x20)), mload(result))) { + // set "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)"))) + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + // set data offset + mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020) + // set length of revert string + mstore(0x24, 0x0000000000000000000000000000000000000000000000000000000000000017) + // set revert string: bytes32(abi.encodePacked("Multicall3: call failed")) + mstore(0x44, 0x4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000) + revert(0x00, 0x84) + } + } + unchecked { ++i; } + } + // Finally, make sure the msg.value = SUM(call[0...i].value) + require(msg.value == valAccumulator, "Multicall3: value mismatch"); + } + + /// @notice Returns the block hash for the given block number + /// @param blockNumber The block number + function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash) { + blockHash = blockhash(blockNumber); + } + + /// @notice Returns the block number + function getBlockNumber() public view returns (uint256 blockNumber) { + blockNumber = block.number; + } + + /// @notice Returns the block coinbase + function getCurrentBlockCoinbase() public view returns (address coinbase) { + coinbase = block.coinbase; + } + + /// @notice Returns the block difficulty + function getCurrentBlockDifficulty() public view returns (uint256 difficulty) { + difficulty = block.difficulty; + } + + /// @notice Returns the block gas limit + function getCurrentBlockGasLimit() public view returns (uint256 gaslimit) { + gaslimit = block.gaslimit; + } + + /// @notice Returns the block timestamp + function getCurrentBlockTimestamp() public view returns (uint256 timestamp) { + timestamp = block.timestamp; + } + + /// @notice Returns the (ETH) balance of a given address + function getEthBalance(address addr) public view returns (uint256 balance) { + balance = addr.balance; + } + + /// @notice Returns the block hash of the last block + function getLastBlockHash() public view returns (bytes32 blockHash) { + unchecked { + blockHash = blockhash(block.number - 1); + } + } + + /// @notice Gets the base fee of the given block + /// @notice Can revert if the BASEFEE opcode is not implemented by the given chain + function getBasefee() public view returns (uint256 basefee) { + basefee = block.basefee; + } + + /// @notice Returns the chain id + function getChainId() public view returns (uint256 chainid) { + chainid = block.chainid; + } +} diff --git a/test/chains/sources/multicall3/multicall3.metadata.json b/test/chains/sources/multicall3/multicall3.metadata.json new file mode 100644 index 000000000..41f5f0bac --- /dev/null +++ b/test/chains/sources/multicall3/multicall3.metadata.json @@ -0,0 +1,436 @@ +{ + "compiler": { "version": "0.8.12+commit.f00d7308" }, + "language": "Solidity", + "output": { + "abi": [ + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { "internalType": "bytes", "name": "callData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "aggregate", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + }, + { "internalType": "bytes[]", "name": "returnData", "type": "bytes[]" } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowFailure", + "type": "bool" + }, + { "internalType": "bytes", "name": "callData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Call3[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "aggregate3", + "outputs": [ + { + "components": [ + { "internalType": "bool", "name": "success", "type": "bool" }, + { "internalType": "bytes", "name": "returnData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowFailure", + "type": "bool" + }, + { "internalType": "uint256", "name": "value", "type": "uint256" }, + { "internalType": "bytes", "name": "callData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Call3Value[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "aggregate3Value", + "outputs": [ + { + "components": [ + { "internalType": "bool", "name": "success", "type": "bool" }, + { "internalType": "bytes", "name": "returnData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { "internalType": "bytes", "name": "callData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "blockAndAggregate", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + }, + { "internalType": "bytes32", "name": "blockHash", "type": "bytes32" }, + { + "components": [ + { "internalType": "bool", "name": "success", "type": "bool" }, + { "internalType": "bytes", "name": "returnData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "getBasefee", + "outputs": [ + { "internalType": "uint256", "name": "basefee", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "name": "getBlockHash", + "outputs": [ + { "internalType": "bytes32", "name": "blockHash", "type": "bytes32" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getBlockNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getChainId", + "outputs": [ + { "internalType": "uint256", "name": "chainid", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockCoinbase", + "outputs": [ + { "internalType": "address", "name": "coinbase", "type": "address" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockDifficulty", + "outputs": [ + { "internalType": "uint256", "name": "difficulty", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockGasLimit", + "outputs": [ + { "internalType": "uint256", "name": "gaslimit", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockTimestamp", + "outputs": [ + { "internalType": "uint256", "name": "timestamp", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "addr", "type": "address" } + ], + "name": "getEthBalance", + "outputs": [ + { "internalType": "uint256", "name": "balance", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLastBlockHash", + "outputs": [ + { "internalType": "bytes32", "name": "blockHash", "type": "bytes32" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bool", "name": "requireSuccess", "type": "bool" }, + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { "internalType": "bytes", "name": "callData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "tryAggregate", + "outputs": [ + { + "components": [ + { "internalType": "bool", "name": "success", "type": "bool" }, + { "internalType": "bytes", "name": "returnData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bool", "name": "requireSuccess", "type": "bool" }, + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { "internalType": "bytes", "name": "callData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "tryBlockAndAggregate", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + }, + { "internalType": "bytes32", "name": "blockHash", "type": "bytes32" }, + { + "components": [ + { "internalType": "bool", "name": "success", "type": "bool" }, + { "internalType": "bytes", "name": "returnData", "type": "bytes" } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + } + ], + "devdoc": { + "author": "Michael Elliot Joshua Levine Nick Johnson Andreas Bigger Matt Solomon ", + "details": "Multicall & Multicall2 backwards-compatibleAggregate methods are marked `payable` to save 24 gas per call", + "kind": "dev", + "methods": { + "aggregate((address,bytes)[])": { + "params": { "calls": "An array of Call structs" }, + "returns": { + "blockNumber": "The block number where the calls were executed", + "returnData": "An array of bytes containing the responses" + } + }, + "aggregate3((address,bool,bytes)[])": { + "params": { "calls": "An array of Call3 structs" }, + "returns": { "returnData": "An array of Result structs" } + }, + "aggregate3Value((address,bool,uint256,bytes)[])": { + "params": { "calls": "An array of Call3Value structs" }, + "returns": { "returnData": "An array of Result structs" } + }, + "blockAndAggregate((address,bytes)[])": { + "params": { "calls": "An array of Call structs" }, + "returns": { + "blockHash": "The hash of the block where the calls were executed", + "blockNumber": "The block number where the calls were executed", + "returnData": "An array of Result structs" + } + }, + "getBlockHash(uint256)": { + "params": { "blockNumber": "The block number" } + }, + "tryAggregate(bool,(address,bytes)[])": { + "params": { + "calls": "An array of Call structs", + "requireSuccess": "If true, require all calls to succeed" + }, + "returns": { "returnData": "An array of Result structs" } + }, + "tryBlockAndAggregate(bool,(address,bytes)[])": { + "params": { "calls": "An array of Call structs" }, + "returns": { + "blockHash": "The hash of the block where the calls were executed", + "blockNumber": "The block number where the calls were executed", + "returnData": "An array of Result structs" + } + } + }, + "title": "Multicall3", + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": { + "aggregate((address,bytes)[])": { + "notice": "Backwards-compatible call aggregation with Multicall" + }, + "aggregate3((address,bool,bytes)[])": { + "notice": "Aggregate calls, ensuring each returns success if required" + }, + "aggregate3Value((address,bool,uint256,bytes)[])": { + "notice": "Aggregate calls with a msg valueReverts if msg.value is less than the sum of the call values" + }, + "blockAndAggregate((address,bytes)[])": { + "notice": "Backwards-compatible with Multicall2Aggregate calls and allow failures using tryAggregate" + }, + "getBasefee()": { + "notice": "Gets the base fee of the given blockCan revert if the BASEFEE opcode is not implemented by the given chain" + }, + "getBlockHash(uint256)": { + "notice": "Returns the block hash for the given block number" + }, + "getBlockNumber()": { "notice": "Returns the block number" }, + "getChainId()": { "notice": "Returns the chain id" }, + "getCurrentBlockCoinbase()": { "notice": "Returns the block coinbase" }, + "getCurrentBlockDifficulty()": { + "notice": "Returns the block difficulty" + }, + "getCurrentBlockGasLimit()": { + "notice": "Returns the block gas limit" + }, + "getCurrentBlockTimestamp()": { + "notice": "Returns the block timestamp" + }, + "getEthBalance(address)": { + "notice": "Returns the (ETH) balance of a given address" + }, + "getLastBlockHash()": { + "notice": "Returns the block hash of the last block" + }, + "tryAggregate(bool,(address,bytes)[])": { + "notice": "Backwards-compatible with Multicall2Aggregate calls without requiring success" + }, + "tryBlockAndAggregate(bool,(address,bytes)[])": { + "notice": "Backwards-compatible with Multicall2Aggregate calls and allow failures using tryAggregate" + } + }, + "notice": "Aggregate results from multiple function calls", + "version": 1 + } + }, + "settings": { + "compilationTarget": { "Multicall3.sol": "Multicall3" }, + "evmVersion": "london", + "libraries": {}, + "metadata": { "bytecodeHash": "ipfs" }, + "optimizer": { "enabled": false, "runs": 200 }, + "remappings": [] + }, + "sources": { + "Multicall3.sol": { + "keccak256": "0x95dfd0a2dd6626c7119ff7c3f214d56b289145f81a0521cd93a6252a326966f6", + "license": "MIT", + "urls": [ + "bzz-raw://ead513cb13fe5373523cb3e1f3f1a1052791503269f4cc22e5e6177ed57d03ae", + "dweb:/ipfs/Qmd5sRoq3rxHpBkcHr4baM2zP6Sud6eAL7AFoBbizFnWji" + ] + } + }, + "version": 1 +} diff --git a/test/server.js b/test/server.js index 4136654c5..dc8dfe7e9 100644 --- a/test/server.js +++ b/test/server.js @@ -45,7 +45,7 @@ const { callWithAccessToken, } = require("./helpers/helpers"); const { deployFromAbiAndBytecode } = require("./helpers/helpers"); -const { JsonRpcProvider, Network } = require("ethers"); +const { JsonRpcProvider, Network, id: keccak256str } = require("ethers"); const { LOCAL_CHAINS } = require("../dist/sourcify-chains"); chai.use(chaiHttp); @@ -161,99 +161,102 @@ describe("Server", function () { chai.expect(obj1, `assertFromPath: ${obj2path}`).to.deep.equal(obj2); } - describe("Verify create2", function () { - this.timeout(EXTENDED_TIME_60); + // Don't run if it's an external PR. + if (process.env.CIRCLE_PR_REPONAME == undefined) { + describe("Verify create2", function () { + this.timeout(EXTENDED_TIME_60); - const agent = chai.request.agent(server.app); - let verificationId; + const agent = chai.request.agent(server.app); + let verificationId; - it("should input files from existing contract via auxdata ipfs", async () => { - const artifacts = require("./testcontracts/Create2/Wallet.json"); + it("should input files from existing contract via auxdata ipfs", async () => { + const artifacts = require("./testcontracts/Create2/Wallet.json"); - const account = await localSigner.getAddress(); - const addressDeployed = await deployFromAbiAndBytecode( - localSigner, - artifacts.abi, - artifacts.bytecode, - [account, account] - ); + const account = await localSigner.getAddress(); + const addressDeployed = await deployFromAbiAndBytecode( + localSigner, + artifacts.abi, + artifacts.bytecode, + [account, account] + ); - const res = await agent - .post("/session/input-contract") - .field("address", addressDeployed) - .field("chainId", defaultContractChain); - - verificationId = res.body.contracts[0].verificationId; - chai.expect(res.body.contracts).to.have.a.lengthOf(1); - const contract = res.body.contracts[0]; - chai.expect(contract.files.found).to.have.a.lengthOf(1); - const retrivedFile = contract.files.found[0]; - chai.expect(retrivedFile).to.equal("contracts/create2/Wallet.sol"); - }); + const res = await agent + .post("/session/input-contract") + .field("address", addressDeployed) + .field("chainId", defaultContractChain); + + verificationId = res.body.contracts[0].verificationId; + chai.expect(res.body.contracts).to.have.a.lengthOf(1); + const contract = res.body.contracts[0]; + chai.expect(contract.files.found).to.have.a.lengthOf(1); + const retrivedFile = contract.files.found[0]; + chai.expect(retrivedFile).to.equal("contracts/create2/Wallet.sol"); + }); - it("should create2 verify with session", (done) => { - callWithAccessToken((accessToken) => { - agent - .post("/session/verify/create2") - .set("Authorization", `Bearer ${accessToken}`) - .send({ - deployerAddress: "0xd9145CCE52D386f254917e481eB44e9943F39138", - salt: 12344, - abiEncodedConstructorArguments: - "0x0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4", - create2Address: "0x65790cc291a234eDCD6F28e1F37B036eD4F01e3B", - verificationId: verificationId, - }) - .end((err, res) => { - assertVerificationSession( - err, - res, - done, - "0x65790cc291a234eDCD6F28e1F37B036eD4F01e3B", - "0", - "perfect" - ); - }); + it("should create2 verify with session", (done) => { + callWithAccessToken((accessToken) => { + agent + .post("/session/verify/create2") + .set("Authorization", `Bearer ${accessToken}`) + .send({ + deployerAddress: "0xd9145CCE52D386f254917e481eB44e9943F39138", + salt: 12344, + abiEncodedConstructorArguments: + "0x0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4", + create2Address: "0x65790cc291a234eDCD6F28e1F37B036eD4F01e3B", + verificationId: verificationId, + }) + .end((err, res) => { + assertVerificationSession( + err, + res, + done, + "0x65790cc291a234eDCD6F28e1F37B036eD4F01e3B", + "0", + "perfect" + ); + }); + }); }); - }); - it("should create2 verify non-session", (done) => { - const metadata = fs - .readFileSync("test/testcontracts/Create2/Wallet_metadata.json") - .toString(); - const source = fs - .readFileSync("test/testcontracts/Create2/Wallet.sol") - .toString(); + it("should create2 verify non-session", (done) => { + const metadata = fs + .readFileSync("test/testcontracts/Create2/Wallet_metadata.json") + .toString(); + const source = fs + .readFileSync("test/testcontracts/Create2/Wallet.sol") + .toString(); - callWithAccessToken((accessToken) => { - chai - .request(server.app) - .post("/verify/create2") - .set("Authorization", `Bearer ${accessToken}`) - .send({ - deployerAddress: "0xd9145CCE52D386f254917e481eB44e9943F39138", - salt: 12345, - abiEncodedConstructorArguments: - "0x0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4", - files: { - "metadata.json": metadata, - "Wallet.sol": source, - }, - create2Address: "0x801B9c0Ee599C3E5ED60e4Ec285C95fC9878Ee64", - }) - .end((err, res) => { - assertVerification( - err, - res, - done, - "0x801B9c0Ee599C3E5ED60e4Ec285C95fC9878Ee64", - "0", - "perfect" - ); - }); + callWithAccessToken((accessToken) => { + chai + .request(server.app) + .post("/verify/create2") + .set("Authorization", `Bearer ${accessToken}`) + .send({ + deployerAddress: "0xd9145CCE52D386f254917e481eB44e9943F39138", + salt: 12345, + abiEncodedConstructorArguments: + "0x0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4", + files: { + "metadata.json": metadata, + "Wallet.sol": source, + }, + create2Address: "0x801B9c0Ee599C3E5ED60e4Ec285C95fC9878Ee64", + }) + .end((err, res) => { + assertVerification( + err, + res, + done, + "0x801B9c0Ee599C3E5ED60e4Ec285C95fC9878Ee64", + "0", + "perfect" + ); + }); + }); }); }); - }); + } describe("/check-by-addresses", function () { this.timeout(EXTENDED_TIME); @@ -942,7 +945,6 @@ describe("Server", function () { defaultContractChain, contractAddress, "sources", - "..contracts", "Storage.sol" ) ); @@ -1708,6 +1710,155 @@ describe("Server", function () { }); }); }); + describe("E2E test path sanitization", async function () { + it("should verify a contract with paths containing misc. chars, save the path translation, and be able access the file over the API", async () => { + const sanitizeArtifact = require("./testcontracts/path-sanitization/ERC20.json"); + const sanitizeMetadata = require("./testcontracts/path-sanitization/metadata.json"); + // read all files under test/testcontracts/path-sanitization/sources/ and put them in an object + const sanitizeSourcesObj = {}; + fs.readdirSync( + path.join("test", "testcontracts", "path-sanitization", "sources") + ).forEach( + (fileName) => + (sanitizeSourcesObj[fileName] = fs.readFileSync( + path.join( + "test", + "testcontracts", + "path-sanitization", + "sources", + fileName + ) + )) + ); + + const sanitizeMetadataBuffer = Buffer.from( + JSON.stringify(sanitizeMetadata) + ); + + const toBeSanitizedContractAddress = await deployFromAbiAndBytecode( + localSigner, + sanitizeArtifact.abi, + sanitizeArtifact.bytecode, + ["TestToken", "TEST", 1000000000] + ); + + const verificationResponse = await chai + .request(server.app) + .post("/") + .send({ + address: toBeSanitizedContractAddress, + chain: defaultContractChain, + files: { + "metadata.json": sanitizeMetadataBuffer.toString(), + ...sanitizeSourcesObj, + }, + }); + + chai.expect(verificationResponse.status).to.equal(StatusCodes.OK); + chai + .expect(verificationResponse.body.result[0].status) + .to.equal("perfect"); + const contractSavedPath = path.join( + server.repository, + "contracts", + "full_match", + defaultContractChain, + toBeSanitizedContractAddress + ); + const pathTranslationPath = path.join( + contractSavedPath, + "path-translation.json" + ); + + let pathTranslationJSON; + try { + pathTranslationJSON = JSON.parse( + fs.readFileSync(pathTranslationPath).toString() + ); + } catch (e) { + throw new Error( + `Path translation file not found at ${pathTranslationPath}` + ); + } + + // Get the contract files from the server + const res = await chai + .request(server.app) + .get(`/files/${defaultContractChain}/${toBeSanitizedContractAddress}`); + chai.expect(res.status).to.equal(StatusCodes.OK); + + // The translation path must inlude the new translated path + const fetchedContractFiles = res.body; + Object.keys(pathTranslationJSON).forEach((originalPath) => { + // The metadata must have the original path + chai + .expect( + sanitizeMetadata.sources, + `Original path ${originalPath} not found in metadata` + ) + .to.include.key(originalPath); + // The path from the server response must be translated + const translatedContractObject = fetchedContractFiles.find( + (obj) => + obj.path === + path.join( + contractSavedPath, + "sources", + pathTranslationJSON[originalPath] + ) + ); + chai.expect(translatedContractObject).to.exist; + // And the saved file must be the same as in the metadata + chai + .expect( + sanitizeMetadata.sources[originalPath].keccak256, + `Keccak of ${originalPath} does not match ${translatedContractObject.path}` + ) + .to.equal(keccak256str(translatedContractObject.content)); + }); + }); + + it("should not save path translation if the path is not sanitized", async () => { + const contractAddress = await deployFromAbiAndBytecode( + localSigner, + artifact.abi, + artifact.bytecode + ); + await chai + .request(server.app) + .post("/") + .send({ + address: defaultContractAddress, + chain: defaultContractChain, + files: { + "metadata.json": metadataBuffer, + "Storage.sol": sourceBuffer, + }, + }) + .end((err, res) => + assertVerification( + err, + res, + null, + defaultContractAddress, + defaultContractChain, + "perfect" + ) + ); + const contractSavedPath = path.join( + server.repository, + "contracts", + "full_match", + defaultContractChain, + contractAddress + ); + const pathTranslationPath = path.join( + contractSavedPath, + "path-translation.json" + ); + chai.expect(fs.existsSync(pathTranslationPath)).to.be.false; + }); + }); describe("Verify repository endpoints", function () { const agent = chai.request.agent(server.app); it("should fetch files of specific address", async function () { diff --git a/test/testcontracts/path-sanitization/ERC20.json b/test/testcontracts/path-sanitization/ERC20.json new file mode 100644 index 000000000..f286abe47 --- /dev/null +++ b/test/testcontracts/path-sanitization/ERC20.json @@ -0,0 +1,227 @@ +{ + "abi": [ + { + "inputs": [ + { "internalType": "string", "name": "name_", "type": "string" }, + { "internalType": "string", "name": "symbol_", "type": "string" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "Owner", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "address", "name": "spender", "type": "address" } + ], + "name": "allowance", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "approve", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "balanceOf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "addedValue", "type": "uint256" } + ], + "name": "increaseAllowance", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "transfer", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "from", "type": "address" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "transferFrom", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "newOwner", "type": "address" } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x60806040523480156200001157600080fd5b506040516200289338038062002893833981810160405281019062000037919062000535565b620000576200004b620000dd60201b60201c565b620000e560201b60201c565b620000a26200006b620001a960201b60201c565b6200007b620001d260201b60201c565b600a620000899190620007b7565b83620000969190620008f4565b620001db60201b60201c565b8260049080519060200190620000ba929190620003f0565b508160059080519060200190620000d3929190620003f0565b5050505062000b5f565b600033905090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60006012905090565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146200026c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000263906200062e565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415620002df576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620002d69062000650565b60405180910390fd5b620002f360008383620003e660201b60201c565b8060036000828254620003079190620006ff565b9250508190555080600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546200035f9190620006ff565b925050819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051620003c6919062000672565b60405180910390a3620003e260008383620003eb60201b60201c565b5050565b505050565b505050565b828054620003fe90620009a2565b90600052602060002090601f0160209004810192826200042257600085556200046e565b82601f106200043d57805160ff19168380011785556200046e565b828001600101855582156200046e579182015b828111156200046d57825182559160200191906001019062000450565b5b5090506200047d919062000481565b5090565b5b808211156200049c57600081600090555060010162000482565b5090565b6000620004b7620004b184620006b8565b6200068f565b905082815260208101848484011115620004d657620004d562000aa0565b5b620004e38482856200096c565b509392505050565b600082601f83011262000503576200050262000a9b565b5b815162000515848260208601620004a0565b91505092915050565b6000815190506200052f8162000b45565b92915050565b60008060006060848603121562000551576200055062000aaa565b5b600084015167ffffffffffffffff81111562000572576200057162000aa5565b5b6200058086828701620004eb565b935050602084015167ffffffffffffffff811115620005a457620005a362000aa5565b5b620005b286828701620004eb565b9250506040620005c5868287016200051e565b9150509250925092565b6000620005de602583620006ee565b9150620005eb8262000acd565b604082019050919050565b600062000605601f83620006ee565b9150620006128262000b1c565b602082019050919050565b620006288162000955565b82525050565b600060208201905081810360008301526200064981620005cf565b9050919050565b600060208201905081810360008301526200066b81620005f6565b9050919050565b60006020820190506200068960008301846200061d565b92915050565b60006200069b620006ae565b9050620006a98282620009d8565b919050565b6000604051905090565b600067ffffffffffffffff821115620006d657620006d562000a6c565b5b620006e18262000aaf565b9050602081019050919050565b600082825260208201905092915050565b60006200070c8262000955565b9150620007198362000955565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0382111562000751576200075062000a0e565b5b828201905092915050565b6000808291508390505b6001851115620007ae5780860481111562000786576200078562000a0e565b5b6001851615620007965780820291505b8081029050620007a68562000ac0565b945062000766565b94509492505050565b6000620007c48262000955565b9150620007d1836200095f565b9250620008007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff848462000808565b905092915050565b6000826200081a5760019050620008ed565b816200082a5760009050620008ed565b81600181146200084357600281146200084e5762000884565b6001915050620008ed565b60ff84111562000863576200086262000a0e565b5b8360020a9150848211156200087d576200087c62000a0e565b5b50620008ed565b5060208310610133831016604e8410600b8410161715620008be5782820a905083811115620008b857620008b762000a0e565b5b620008ed565b620008cd84848460016200075c565b92509050818404811115620008e757620008e662000a0e565b5b81810290505b9392505050565b6000620009018262000955565b91506200090e8362000955565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04831182151516156200094a576200094962000a0e565b5b828202905092915050565b6000819050919050565b600060ff82169050919050565b60005b838110156200098c5780820151818401526020810190506200096f565b838111156200099c576000848401525b50505050565b60006002820490506001821680620009bb57607f821691505b60208210811415620009d257620009d162000a3d565b5b50919050565b620009e38262000aaf565b810181811067ffffffffffffffff8211171562000a055762000a0462000a6c565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b60008160011c9050919050565b7f45524332303a204f6e6c7920746865206f776e657220616c6c6f77656420746f60008201527f206d696e74000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a206d696e7420746f20746865207a65726f206164647265737300600082015250565b62000b508162000955565b811462000b5c57600080fd5b50565b611d248062000b6f6000396000f3fe608060405234801561001057600080fd5b50600436106101005760003560e01c806370a0823111610097578063a9059cbb11610066578063a9059cbb146102af578063b4a99a4e146102df578063dd62ed3e146102fd578063f2fde38b1461032d57610100565b806370a0823114610227578063715018a61461025757806395d89b4114610261578063a457c2d71461027f57610100565b8063313ce567116100d3578063313ce567146101a157806339509351146101bf57806340c10f19146101ef57806342966c681461020b57610100565b806306fdde0314610105578063095ea7b31461012357806318160ddd1461015357806323b872dd14610171575b600080fd5b61010d610349565b60405161011a9190611563565b60405180910390f35b61013d60048036038101906101389190611284565b6103db565b60405161014a9190611548565b60405180910390f35b61015b6103fe565b6040516101689190611725565b60405180910390f35b61018b60048036038101906101869190611231565b610408565b6040516101989190611548565b60405180910390f35b6101a9610437565b6040516101b69190611740565b60405180910390f35b6101d960048036038101906101d49190611284565b610440565b6040516101e69190611548565b60405180910390f35b61020960048036038101906102049190611284565b610477565b005b610225600480360381019061022091906112c4565b610666565b005b610241600480360381019061023c91906111c4565b610844565b60405161024e9190611725565b60405180910390f35b61025f61088d565b005b610269610915565b6040516102769190611563565b60405180910390f35b61029960048036038101906102949190611284565b6109a7565b6040516102a69190611548565b60405180910390f35b6102c960048036038101906102c49190611284565b610a1e565b6040516102d69190611548565b60405180910390f35b6102e7610a41565b6040516102f4919061152d565b60405180910390f35b610317600480360381019061031291906111f1565b610a6a565b6040516103249190611725565b60405180910390f35b610347600480360381019061034291906111c4565b610af1565b005b60606004805461035890611889565b80601f016020809104026020016040519081016040528092919081815260200182805461038490611889565b80156103d15780601f106103a6576101008083540402835291602001916103d1565b820191906000526020600020905b8154815290600101906020018083116103b457829003601f168201915b5050505050905090565b6000806103e6610be9565b90506103f3818585610bf1565b600191505092915050565b6000600354905090565b600080610413610be9565b9050610420858285610dbc565b61042b858585610e48565b60019150509392505050565b60006012905090565b60008061044b610be9565b905061046c81858561045d8589610a6a565b6104679190611777565b610bf1565b600191505092915050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610505576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104fc906116a5565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610575576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161056c90611705565b60405180910390fd5b610581600083836110cc565b80600360008282546105939190611777565b9250508190555080600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546105e99190611777565b925050819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8360405161064e9190611725565b60405180910390a3610662600083836110d1565b5050565b6000339050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156106db576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106d290611665565b60405180910390fd5b6106e7816000846110cc565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508281101561076e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610765906115a5565b60405180910390fd5b828103600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555082600360008282546107c691906117cd565b92505081905550600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8560405161082b9190611725565b60405180910390a361083f826000856110d1565b505050565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b610895610be9565b73ffffffffffffffffffffffffffffffffffffffff166108b3610a41565b73ffffffffffffffffffffffffffffffffffffffff1614610909576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161090090611645565b60405180910390fd5b61091360006110d6565b565b60606005805461092490611889565b80601f016020809104026020016040519081016040528092919081815260200182805461095090611889565b801561099d5780601f106109725761010080835404028352916020019161099d565b820191906000526020600020905b81548152906001019060200180831161098057829003601f168201915b5050505050905090565b6000806109b2610be9565b905060006109c08286610a6a565b905083811015610a05576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109fc906116e5565b60405180910390fd5b610a128286868403610bf1565b60019250505092915050565b600080610a29610be9565b9050610a36818585610e48565b600191505092915050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b610af9610be9565b73ffffffffffffffffffffffffffffffffffffffff16610b17610a41565b73ffffffffffffffffffffffffffffffffffffffff1614610b6d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b6490611645565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415610bdd576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610bd4906115c5565b60405180910390fd5b610be6816110d6565b50565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610c61576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610c58906116c5565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610cd1576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610cc8906115e5565b60405180910390fd5b80600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92583604051610daf9190611725565b60405180910390a3505050565b6000610dc88484610a6a565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114610e425781811015610e34576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e2b90611605565b60405180910390fd5b610e418484848403610bf1565b5b50505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610eb8576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610eaf90611685565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610f28576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f1f90611585565b60405180910390fd5b610f338383836110cc565b6000600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015610fba576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610fb190611625565b60405180910390fd5b818103600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555081600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461104f9190611777565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516110b39190611725565b60405180910390a36110c68484846110d1565b50505050565b505050565b505050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b6000813590506111a981611cc0565b92915050565b6000813590506111be81611cd7565b92915050565b6000602082840312156111da576111d9611919565b5b60006111e88482850161119a565b91505092915050565b6000806040838503121561120857611207611919565b5b60006112168582860161119a565b92505060206112278582860161119a565b9150509250929050565b60008060006060848603121561124a57611249611919565b5b60006112588682870161119a565b93505060206112698682870161119a565b925050604061127a868287016111af565b9150509250925092565b6000806040838503121561129b5761129a611919565b5b60006112a98582860161119a565b92505060206112ba858286016111af565b9150509250929050565b6000602082840312156112da576112d9611919565b5b60006112e8848285016111af565b91505092915050565b6112fa81611801565b82525050565b61130981611813565b82525050565b600061131a8261175b565b6113248185611766565b9350611334818560208601611856565b61133d8161191e565b840191505092915050565b6000611355602383611766565b91506113608261192f565b604082019050919050565b6000611378602283611766565b91506113838261197e565b604082019050919050565b600061139b602683611766565b91506113a6826119cd565b604082019050919050565b60006113be602283611766565b91506113c982611a1c565b604082019050919050565b60006113e1601d83611766565b91506113ec82611a6b565b602082019050919050565b6000611404602683611766565b915061140f82611a94565b604082019050919050565b6000611427602083611766565b915061143282611ae3565b602082019050919050565b600061144a602183611766565b915061145582611b0c565b604082019050919050565b600061146d602583611766565b915061147882611b5b565b604082019050919050565b6000611490602583611766565b915061149b82611baa565b604082019050919050565b60006114b3602483611766565b91506114be82611bf9565b604082019050919050565b60006114d6602583611766565b91506114e182611c48565b604082019050919050565b60006114f9601f83611766565b915061150482611c97565b602082019050919050565b6115188161183f565b82525050565b61152781611849565b82525050565b600060208201905061154260008301846112f1565b92915050565b600060208201905061155d6000830184611300565b92915050565b6000602082019050818103600083015261157d818461130f565b905092915050565b6000602082019050818103600083015261159e81611348565b9050919050565b600060208201905081810360008301526115be8161136b565b9050919050565b600060208201905081810360008301526115de8161138e565b9050919050565b600060208201905081810360008301526115fe816113b1565b9050919050565b6000602082019050818103600083015261161e816113d4565b9050919050565b6000602082019050818103600083015261163e816113f7565b9050919050565b6000602082019050818103600083015261165e8161141a565b9050919050565b6000602082019050818103600083015261167e8161143d565b9050919050565b6000602082019050818103600083015261169e81611460565b9050919050565b600060208201905081810360008301526116be81611483565b9050919050565b600060208201905081810360008301526116de816114a6565b9050919050565b600060208201905081810360008301526116fe816114c9565b9050919050565b6000602082019050818103600083015261171e816114ec565b9050919050565b600060208201905061173a600083018461150f565b92915050565b6000602082019050611755600083018461151e565b92915050565b600081519050919050565b600082825260208201905092915050565b60006117828261183f565b915061178d8361183f565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156117c2576117c16118bb565b5b828201905092915050565b60006117d88261183f565b91506117e38361183f565b9250828210156117f6576117f56118bb565b5b828203905092915050565b600061180c8261181f565b9050919050565b60008115159050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b60005b83811015611874578082015181840152602081019050611859565b83811115611883576000848401525b50505050565b600060028204905060018216806118a157607f821691505b602082108114156118b5576118b46118ea565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600080fd5b6000601f19601f8301169050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a206275726e20616d6f756e7420657863656564732062616c616e60008201527f6365000000000000000000000000000000000000000000000000000000000000602082015250565b7f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160008201527f6464726573730000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20696e73756666696369656e7420616c6c6f77616e6365000000600082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b7f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572600082015250565b7f45524332303a206275726e2066726f6d20746865207a65726f2061646472657360008201527f7300000000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a204f6e6c7920746865206f776e657220616c6c6f77656420746f60008201527f206d696e74000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a206d696e7420746f20746865207a65726f206164647265737300600082015250565b611cc981611801565b8114611cd457600080fd5b50565b611ce08161183f565b8114611ceb57600080fd5b5056fea2646970667358221220f5d43ead4e9763b9e4cfc096599dd1c7dbf229d38c3b4c83f3cc6022018dc5e864736f6c63430008070033" +} diff --git a/test/testcontracts/path-sanitization/metadata.json b/test/testcontracts/path-sanitization/metadata.json new file mode 100644 index 000000000..43adcb8e8 --- /dev/null +++ b/test/testcontracts/path-sanitization/metadata.json @@ -0,0 +1 @@ +{"compiler":{"version":"0.8.7+commit.e28d00a7"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"string","name":"name_","type":"string"},{"internalType":"string","name":"symbol_","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"Owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}],"devdoc":{"kind":"dev","methods":{"allowance(address,address)":{"details":"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."},"approve(address,uint256)":{"details":"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."},"balanceOf(address)":{"details":"Returns the amount of tokens owned by `account`."},"decimals()":{"details":"Returns the decimals places of the token."},"name()":{"details":"Returns the name of the token."},"symbol()":{"details":"Returns the symbol of the token."},"totalSupply()":{"details":"Returns the amount of tokens in existence."},"transfer(address,uint256)":{"details":"Moves `amount` tokens from the caller's account to `to`. Returns a boolean value indicating whether the operation succeeded. Emits a {Transfer} event."},"transferFrom(address,address,uint256)":{"details":"Moves `amount` tokens from `from` to `to` 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."}},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"compilationTarget":{"SDGC.sol":"ERC20"},"evmVersion":"london","libraries":{},"metadata":{"bytecodeHash":"ipfs"},"optimizer":{"enabled":false,"runs":200},"remappings":[]},"sources":{"SDGC.sol":{"keccak256":"0xc697efd1cbb0bd664793eb945619c172a598a1a1aba660ed4051f3dd4677a843","license":"MIT","urls":["bzz-raw://7c1074eb7a7a11bc85260ad83706535c963d77d4e31988736b81f917b3a42b80","dweb:/ipfs/QmR2rLZ3E6U72YZhCo14PugPjZvLUgUbVmrvpFsKMdrKNk"]},"https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/../../utils/Context.sol":{"keccak256":"0xe2e337e6dde9ef6b680e07338c493ebea1b5fd09b43424112868e9cc1706bca7","license":"MIT","urls":["bzz-raw://6df0ddf21ce9f58271bdfaa85cde98b200ef242a05a3f85c2bc10a8294800a92","dweb:/ipfs/QmRK2Y5Yc6BK7tGKkgsgn3aJEQGi5aakeSPZvS65PV8Xp3"]},"https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol":{"keccak256":"0xbbc8ac883ac3c0078ce5ad3e288fbb3ffcc8a30c3a98c0fda0114d64fc44fca2","license":"MIT","urls":["bzz-raw://87a7a5d2f6f63f84598af02b8c50ca2df2631cb8ba2453e8d95fcb17e4be9824","dweb:/ipfs/QmR76hqtAcRqoFj33tmNjcWTLrgNsAaakYwnKZ8zoJtKei"]},"https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/IERC20Metadata.sol":{"keccak256":"0x8de418a5503946cabe331f35fe242d3201a73f67f77aaeb7110acb1f30423aca","license":"MIT","urls":["bzz-raw://5a376d3dda2cb70536c0a45c208b29b34ac560c4cb4f513a42079f96ba47d2dd","dweb:/ipfs/QmZQg6gn1sUpM8wHzwNvSnihumUCAhxD119MpXeKp8B9s8"]}},"version":1} \ No newline at end of file diff --git a/test/testcontracts/path-sanitization/sources/Context.sol b/test/testcontracts/path-sanitization/sources/Context.sol new file mode 100644 index 000000000..f304065b4 --- /dev/null +++ b/test/testcontracts/path-sanitization/sources/Context.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (utils/Context.sol) + +pragma solidity ^0.8.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 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) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} diff --git a/test/testcontracts/path-sanitization/sources/IERC20.sol b/test/testcontracts/path-sanitization/sources/IERC20.sol new file mode 100644 index 000000000..810ff275f --- /dev/null +++ b/test/testcontracts/path-sanitization/sources/IERC20.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/IERC20.sol) + +pragma solidity ^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 `to`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address to, 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 `from` to `to` 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 from, + address to, + 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/test/testcontracts/path-sanitization/sources/IERC20Metadata.sol b/test/testcontracts/path-sanitization/sources/IERC20Metadata.sol new file mode 100644 index 000000000..83ba6ac5e --- /dev/null +++ b/test/testcontracts/path-sanitization/sources/IERC20Metadata.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) + +pragma solidity ^0.8.0; + +import "../IERC20.sol"; + +/** + * @dev Interface for the optional metadata functions from the ERC20 standard. + * + * _Available since v4.1._ + */ +interface IERC20Metadata is IERC20 { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the decimals places of the token. + */ + function decimals() external view returns (uint8); +} diff --git a/test/testcontracts/path-sanitization/sources/SDGC.sol b/test/testcontracts/path-sanitization/sources/SDGC.sol new file mode 100644 index 000000000..272933772 --- /dev/null +++ b/test/testcontracts/path-sanitization/sources/SDGC.sol @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/ERC20.sol) + +pragma solidity ^0.8.0; + +import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol"; +import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/../../utils/Context.sol"; + +contract ERC20 is Context, IERC20, IERC20Metadata { + + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + mapping(address => uint256) private _balances; + + mapping(address => mapping(address => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + + constructor(string memory name_, string memory symbol_, uint256 amount) { + _transferOwnership(_msgSender()); + mint(Owner(), amount * (10**decimals())); + _name = name_; + _symbol = symbol_; + } + + function Owner() public view virtual returns (address) { + return _owner; + } + + modifier onlyOwner() { + require(Owner() == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + function renounceOwnership() public virtual onlyOwner { + _transferOwnership(address(0)); + } + + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + _transferOwnership(newOwner); + } + + function _transferOwnership(address newOwner) internal virtual { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } + + + function name() public view virtual override returns (string memory) { + return _name; + } + + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + function decimals() public view virtual override returns (uint8) { + return 18; + } + + function totalSupply() public view virtual override returns (uint256) { + return _totalSupply; + } + + function balanceOf(address account) public view virtual override returns (uint256) { + return _balances[account]; + } + + function transfer(address to, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _transfer(owner, to, amount); + return true; + } + + function allowance(address owner, address spender) public view virtual override returns (uint256) { + return _allowances[owner][spender]; + } + + function approve(address spender, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, amount); + return true; + } + + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual override returns (bool) { + address spender = _msgSender(); + _spendAllowance(from, spender, amount); + _transfer(from, to, amount); + return true; + } + + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, allowance(owner, spender) + addedValue); + return true; + } + + function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { + address owner = _msgSender(); + uint256 currentAllowance = allowance(owner, spender); + require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); + unchecked { + _approve(owner, spender, currentAllowance - subtractedValue); + } + + return true; + } + + function _transfer( + address from, + address to, + uint256 amount + ) internal virtual { + require(from != address(0), "ERC20: transfer from the zero address"); + require(to != address(0), "ERC20: transfer to the zero address"); + + _beforeTokenTransfer(from, to, amount); + + uint256 fromBalance = _balances[from]; + require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); + unchecked { + _balances[from] = fromBalance - amount; + } + _balances[to] += amount; + + emit Transfer(from, to, amount); + + _afterTokenTransfer(from, to, amount); + } + + function mint(address account, uint256 amount) public virtual { + require(msg.sender == _owner,"ERC20: Only the owner allowed to mint"); + require(account != address(0), "ERC20: mint to the zero address"); + + _beforeTokenTransfer(address(0), account, amount); + + _totalSupply += amount; + _balances[account] += amount; + emit Transfer(address(0), account, amount); + + _afterTokenTransfer(address(0), account, amount); + } + + function burn(uint256 amount) public virtual { + address account = msg.sender; + require(account != address(0), "ERC20: burn from the zero address"); + + _beforeTokenTransfer(account, address(0), amount); + + uint256 accountBalance = _balances[account]; + require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); + unchecked { + _balances[account] = accountBalance - amount; + } + _totalSupply -= amount; + + emit Transfer(account, address(0), amount); + + _afterTokenTransfer(account, address(0), amount); + } + + 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); + } + + function _spendAllowance( + address owner, + address spender, + uint256 amount + ) internal virtual { + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance != type(uint256).max) { + require(currentAllowance >= amount, "ERC20: insufficient allowance"); + unchecked { + _approve(owner, spender, currentAllowance - amount); + } + } + } + + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual {} + + function _afterTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual {} +} \ No newline at end of file