From 138f1c264bf113f82b14a3433c8dbe180a1cbc60 Mon Sep 17 00:00:00 2001 From: Luis Videla Date: Tue, 8 Oct 2024 15:32:57 -0300 Subject: [PATCH] Add ethernaut-optigov pluguin and login task --- packages/ethernaut-cli/hardhat.config.js | 1 + packages/ethernaut-optigov/README.md | 47 +++++ packages/ethernaut-optigov/package.json | 31 ++++ packages/ethernaut-optigov/src/index.js | 11 ++ .../ethernaut-optigov/src/internal/Siwe.js | 22 +++ .../src/internal/agora/Auth.js | 63 +++++++ .../ethernaut-optigov/src/scopes/optigov.js | 4 + packages/ethernaut-optigov/src/tasks/login.js | 43 +++++ .../basic-project/hardhat.config.js | 9 + .../basic-project/package-lock.json | 166 ++++++++++++++++++ .../basic-project/package.json | 7 + .../test/scopes/optigov.test.js | 7 + packages/ethernaut-optigov/test/setup.js | 4 + 13 files changed, 415 insertions(+) create mode 100644 packages/ethernaut-optigov/README.md create mode 100644 packages/ethernaut-optigov/package.json create mode 100644 packages/ethernaut-optigov/src/index.js create mode 100644 packages/ethernaut-optigov/src/internal/Siwe.js create mode 100644 packages/ethernaut-optigov/src/internal/agora/Auth.js create mode 100644 packages/ethernaut-optigov/src/scopes/optigov.js create mode 100644 packages/ethernaut-optigov/src/tasks/login.js create mode 100644 packages/ethernaut-optigov/test/fixture-projects/basic-project/hardhat.config.js create mode 100644 packages/ethernaut-optigov/test/fixture-projects/basic-project/package-lock.json create mode 100644 packages/ethernaut-optigov/test/fixture-projects/basic-project/package.json create mode 100644 packages/ethernaut-optigov/test/scopes/optigov.test.js create mode 100644 packages/ethernaut-optigov/test/setup.js diff --git a/packages/ethernaut-cli/hardhat.config.js b/packages/ethernaut-cli/hardhat.config.js index 98c29794..a000d185 100644 --- a/packages/ethernaut-cli/hardhat.config.js +++ b/packages/ethernaut-cli/hardhat.config.js @@ -27,6 +27,7 @@ require('ethernaut-challenges') require('ethernaut-ui') require('ethernaut-oso') require('ethernaut-ai-ui') +require('ethernaut-optigov') async function main() { const txt = figlet.textSync('ethernaut-cli', { font: 'Graffiti' }) diff --git a/packages/ethernaut-optigov/README.md b/packages/ethernaut-optigov/README.md new file mode 100644 index 00000000..ca7aafcd --- /dev/null +++ b/packages/ethernaut-optigov/README.md @@ -0,0 +1,47 @@ +# ethernaut-optigov + +Tasks for interacting the Optimism retroactive public goods funding rounds (RetroPGF) + +## What + +This plugin enables seamless interaction with Optimism’s Retroactive Public Goods Funding (RetroPGF) rounds. It allows users to access data on rounds, projects, and votes, streamlining participation and analysis within the Optimism ecosystem. Perfect for tracking funding and engaging with the RetroPGF process. + +## Installation + +```bash +npm install ethernaut-optigov +``` + +Import the plugin in your `hardhat.config.js`: + +```js +require('ethernaut-optigov') +``` + +Or if you are using TypeScript, in your `hardhat.config.ts`: + +```ts +import 'ethernaut-optigov' +``` + +## Required plugins + +This plugin doesn't depend on any other plugins. + +## Tasks + +This plugin adds the tasks listed below. + +- login Logs in to the Agora RetroPGF API with SIWE (Sign in with Ethereum) + +## Environment extensions + +This plugin doesn't extend the hre. + +## Configuration + +This plugin doesn't add any fields to the hardhat configuration file. + +## Usage + +Just install it and all tasks will be available with `npx hardhat ` diff --git a/packages/ethernaut-optigov/package.json b/packages/ethernaut-optigov/package.json new file mode 100644 index 00000000..b5ae7b29 --- /dev/null +++ b/packages/ethernaut-optigov/package.json @@ -0,0 +1,31 @@ +{ + "name": "ethernaut-optigov", + "version": "1.0.0", + "description": "Tasks for interacting the Optimism Agora retroactive public goods funding rounds (RetroPGF)", + "main": "src/index.js", + "scripts": { + "build": "npm run install:test", + "compile": "echo 'No compile step'", + "install:test": "cd test/fixture-projects/basic-project && npm install", + "test": "nyc --nycrc-path ../../.nycrc.json mocha --config ../../.mocharc.json" + }, + "author": "Alejandro Santander", + "license": "ISC", + "peerDependencies": { + "hardhat": "^2.22.2" + }, + "devDependencies": { + "hardhat": "^2.22.2", + "mocha": "^10.4.0", + "nyc": "^15.1.0", + "nyc-report-lcov-absolute": "^1.0.0" + }, + "dependencies": { + "siwe": "^2.3.2" + }, + "directories": { + "test": "test" + }, + "keywords": [], + "gitHead": "5ffea95c7b8186e6365da19d189a79f09a78d475" +} diff --git a/packages/ethernaut-optigov/src/index.js b/packages/ethernaut-optigov/src/index.js new file mode 100644 index 00000000..019ddb55 --- /dev/null +++ b/packages/ethernaut-optigov/src/index.js @@ -0,0 +1,11 @@ +const { extendEnvironment } = require('hardhat/config') +const requireAll = require('ethernaut-common/src/io/require-all') +const spinner = require('ethernaut-common/src/ui/spinner') +const output = require('ethernaut-common/src/ui/output') + +requireAll(__dirname, 'tasks') + +extendEnvironment((hre) => { + spinner.enable(!hre.hardhatArguments.verbose) + output.setErrorVerbose(hre.hardhatArguments.verbose) +}) diff --git a/packages/ethernaut-optigov/src/internal/Siwe.js b/packages/ethernaut-optigov/src/internal/Siwe.js new file mode 100644 index 00000000..9536907e --- /dev/null +++ b/packages/ethernaut-optigov/src/internal/Siwe.js @@ -0,0 +1,22 @@ +const { SiweMessage } = require('siwe') + +const domain = 'com.ethernaut.cli' +const uri = 'https://github.com/eternauta1337/ethernaut-cli' + +function createSiweMessage(address, statement, nonce) { + const siweMessage = new SiweMessage({ + domain, + address, + statement, + uri, + version: '1', + chainId: '1 ', + nonce, + }) + + return siweMessage.prepareMessage() +} + +module.exports = { + createSiweMessage, +} diff --git a/packages/ethernaut-optigov/src/internal/agora/Auth.js b/packages/ethernaut-optigov/src/internal/agora/Auth.js new file mode 100644 index 00000000..95fbb812 --- /dev/null +++ b/packages/ethernaut-optigov/src/internal/agora/Auth.js @@ -0,0 +1,63 @@ +const axios = require('axios') +const debug = require('ethernaut-common/src/ui/debug') +const EthernautCliError = require('ethernaut-common/src/error/error') +const API_BASE_URL = 'https://vote.optimism.io/api/v1' + +class Auth { + constructor() {} + + // Get a nonce from the Agora API + async getNonce() { + try { + const response = await axios.get(`${API_BASE_URL}/auth/nonce`) + + debug.log(`Nonce: ${response.data.nonce}`, 'ethernaut-optigov') + return response.data + } catch (error) { + throw new EthernautCliError( + 'ethernaut-optigov', + `Http status error: ${error.message}`, + ) + } + } + + // Authenticate with the Agora API + async authenticateWithAgora(message, signature, nonce) { + try { + const response = await axios.post( + `${API_BASE_URL}/auth/verify`, + { + message, + signature, + nonce, + }, + { + headers: { + 'Content-Type': 'application/json', + }, + }, + ) + + debug.log('Auth Response Status:', response.status) + // save the Bearer token + this.bearerToken = response.data.access_token + return response.data.access_token + } catch (error) { + if (error.response) { + // Server responded with a status other than 2xx + throw new EthernautCliError( + 'ethernaut-optigov', + `Http status error: ${error.response.data}`, + ) + } else { + // Other errors (e.g., network issue) + throw new EthernautCliError( + 'ethernaut-optigov', + `Http status error: ${error.message}`, + ) + } + } + } +} + +module.exports = Auth diff --git a/packages/ethernaut-optigov/src/scopes/optigov.js b/packages/ethernaut-optigov/src/scopes/optigov.js new file mode 100644 index 00000000..b573f0ca --- /dev/null +++ b/packages/ethernaut-optigov/src/scopes/optigov.js @@ -0,0 +1,4 @@ +const { scope } = require('hardhat/config') +const { description } = require('../../package.json') + +module.exports = scope('optigov', description) diff --git a/packages/ethernaut-optigov/src/tasks/login.js b/packages/ethernaut-optigov/src/tasks/login.js new file mode 100644 index 00000000..f79de0ce --- /dev/null +++ b/packages/ethernaut-optigov/src/tasks/login.js @@ -0,0 +1,43 @@ +const Auth = require('../internal/agora/Auth') +const output = require('ethernaut-common/src/ui/output') +const { createSiweMessage } = require('../internal/Siwe') +const { EthernautCliError } = require('ethernaut-common/src/error/error') + +require('../scopes/optigov') + .task( + 'login', + 'Logs in to the Agora RetroPGF API with SIWE (Sign in with Ethereum)', + ) + .setAction(async (_, hre) => { + try { + const signers = await hre.ethers.getSigners() + if (signers.length === 0) { + throw new EthernautCliError( + 'ethernaut-optigov', + 'No signers available - If you are using the ethernaut-cli, please add one with `ethernaut wallet create`', + ) + } + const signer = signers[0] + output.info( + `Logging in with address: ${signer.address}`, + 'ethernaut-optigov', + ) + + const auth = new Auth() + + const statement = 'Log in to Agoras RetroPGF API with SIWE.' + const nonce = await auth.getNonce() + const preparedMessage = createSiweMessage( + signer.address, + statement, + nonce, + ) + const signature = await signer.signMessage(preparedMessage) + + await auth.authenticateWithAgora(preparedMessage, signature, nonce) + + return output.resultBox(`Logged in with address: ${signer.address})`) + } catch (err) { + return output.errorBox(err) + } + }) diff --git a/packages/ethernaut-optigov/test/fixture-projects/basic-project/hardhat.config.js b/packages/ethernaut-optigov/test/fixture-projects/basic-project/hardhat.config.js new file mode 100644 index 00000000..e7abfeb1 --- /dev/null +++ b/packages/ethernaut-optigov/test/fixture-projects/basic-project/hardhat.config.js @@ -0,0 +1,9 @@ +/** @type import('hardhat/config').HardhatUserConfig */ +require('@nomicfoundation/hardhat-ethers') +require('../../../src/index') +require('../../../../ethernaut-ui/src/index') + +module.exports = { + solidity: '0.8.24', + defaultNetwork: 'localhost', +} diff --git a/packages/ethernaut-optigov/test/fixture-projects/basic-project/package-lock.json b/packages/ethernaut-optigov/test/fixture-projects/basic-project/package-lock.json new file mode 100644 index 00000000..ef88bcca --- /dev/null +++ b/packages/ethernaut-optigov/test/fixture-projects/basic-project/package-lock.json @@ -0,0 +1,166 @@ +{ + "name": "basic-project", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "basic-project", + "devDependencies": { + "@nomicfoundation/hardhat-ethers": "^3.0.5", + "ethers": "^6.11.1" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", + "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nomicfoundation/hardhat-ethers": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ethers/-/hardhat-ethers-3.0.8.tgz", + "integrity": "sha512-zhOZ4hdRORls31DTOqg+GmEZM0ujly8GGIuRY7t7szEk2zW/arY1qDug/py8AEktT00v5K+b6RvbVog+va51IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "lodash.isequal": "^4.5.0" + }, + "peerDependencies": { + "ethers": "^6.1.0", + "hardhat": "^2.0.0" + } + }, + "node_modules/@types/node": { + "version": "18.15.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz", + "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/aes-js": { + "version": "4.0.0-beta.5", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", + "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/ethers": { + "version": "6.13.3", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.3.tgz", + "integrity": "sha512-/DzbZOLVtoO4fKvvQwpEucHAQgIwBGWuRvBdwE/lMXgXvvHHTSkn7XqAQ2b+gjJzZDJjWA9OD05bVceVOsBHbg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/ethers-io/" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "1.10.1", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.2", + "@types/node": "18.15.13", + "aes-js": "4.0.0-beta.5", + "tslib": "2.4.0", + "ws": "8.17.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true, + "license": "0BSD" + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/packages/ethernaut-optigov/test/fixture-projects/basic-project/package.json b/packages/ethernaut-optigov/test/fixture-projects/basic-project/package.json new file mode 100644 index 00000000..fb15ae57 --- /dev/null +++ b/packages/ethernaut-optigov/test/fixture-projects/basic-project/package.json @@ -0,0 +1,7 @@ +{ + "name": "basic-project", + "devDependencies": { + "@nomicfoundation/hardhat-ethers": "^3.0.5", + "ethers": "^6.11.1" + } +} diff --git a/packages/ethernaut-optigov/test/scopes/optigov.test.js b/packages/ethernaut-optigov/test/scopes/optigov.test.js new file mode 100644 index 00000000..660a6364 --- /dev/null +++ b/packages/ethernaut-optigov/test/scopes/optigov.test.js @@ -0,0 +1,7 @@ +const assert = require('assert') + +describe('optigov', function () { + it('has a "optigov" scope', async function () { + assert.notEqual(hre.scopes['optigov'], undefined) + }) +}) diff --git a/packages/ethernaut-optigov/test/setup.js b/packages/ethernaut-optigov/test/setup.js new file mode 100644 index 00000000..5bcef874 --- /dev/null +++ b/packages/ethernaut-optigov/test/setup.js @@ -0,0 +1,4 @@ +const { loadProject, muteOutput } = require('ethernaut-common/src/test/setup') + +muteOutput() +loadProject('test/fixture-projects/basic-project')