From f18f29a5aa0883f754bcd0580f207542bce59ee1 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Sun, 3 Mar 2024 10:04:15 -0300 Subject: [PATCH] First suggest extension --- packages/ethernaut-interact-ui/src/index.js | 1 + .../ethernaut-interact-ui/src/prompts/abi.js | 71 ---------------- .../ethernaut-interact-ui/src/suggest/abi.js | 80 +++++++++++++++++++ .../ethernaut-ui/src/internal/collect-args.js | 39 ++++++--- 4 files changed, 111 insertions(+), 80 deletions(-) create mode 100644 packages/ethernaut-interact-ui/src/suggest/abi.js diff --git a/packages/ethernaut-interact-ui/src/index.js b/packages/ethernaut-interact-ui/src/index.js index d7dc0a0a..599d9040 100644 --- a/packages/ethernaut-interact-ui/src/index.js +++ b/packages/ethernaut-interact-ui/src/index.js @@ -5,6 +5,7 @@ require('ethernaut-interact/src/index') extendEnvironment((hre) => { const contract = hre.scopes.interact.tasks.contract + contract.paramDefinitions.abi.suggest = require('./suggest/abi') contract.paramDefinitions.abi.prompt = require('./prompts/abi') contract.paramDefinitions.address.prompt = require('./prompts/address') contract.paramDefinitions.fn.prompt = require('./prompts/fn') diff --git a/packages/ethernaut-interact-ui/src/prompts/abi.js b/packages/ethernaut-interact-ui/src/prompts/abi.js index 001ab396..b0dd9bb9 100644 --- a/packages/ethernaut-interact-ui/src/prompts/abi.js +++ b/packages/ethernaut-interact-ui/src/prompts/abi.js @@ -1,11 +1,8 @@ -const fs = require('fs') -const path = require('path') const storage = require('ethernaut-interact/src/internal/storage') const EtherscanApi = require('ethernaut-interact/src/internal/etherscan') const prompt = require('common/src/prompt') const spinner = require('common/src/spinner') const debug = require('common/src/debug') -const similarity = require('string-similarity') const { getNetworkName } = require('common/src/network') const strategies = { @@ -18,40 +15,6 @@ module.exports = async function promptAbi({ abi, hre, address }) { try { const network = await getNetworkName(hre) - // Try to complete a partial abi path - if (abi && !isValidJsonFile(abi)) { - const abis = storage.readAbiFiles() - const matches = similarity.findBestMatch( - abi, - abis.map((a) => a.name), - ) - if (matches) { - const match = abis.find((a) => a.name === matches.bestMatch.target) - if (match) { - debug.log( - `Matched incoming ABI "${abi}" with known ABI "${match.path}"`, - 'interact', - ) - - return match.path - } - } - } - - // If there is an abi at this point, - // return undefined, signaling that there was - // no need to prompt. - if (abi) return undefined - - // Try to deduce the abi from previous interactions - // in the current network - if (address) { - abi = deduceAbiFromAddress(address, network) - if (abi) return abi - } else { - debug.log('Cannot deduce from address', 'interact') - } - // Let the user select a strategy const choice = await selectStrategy({ address, network }) debug.log(`Chosen strategy: ${choice}`, 'interact') @@ -142,28 +105,6 @@ async function browseKnwonAbis() { }) } -function deduceAbiFromAddress(address, network) { - const addresses = storage.readAddresses()[network] - if (!addresses) { - debug.log(`No addresses found for ${network}`, 'interact') - return undefined - } - debug.log( - `Found ${Object.keys(addresses).length} addresses on ${network}`, - 'interact', - ) - - const abi = addresses[address] - if (!abi) { - debug.log(`No address entry found for ${address} on ${network}`, 'interact') - return - } - - debug.log(`Found ...${abi.split('/').pop()} for ${address}`, 'interact') - - return abi -} - async function getAbiFromEtherscan(address, network) { spinner.progress('Fetching ABI from Etherscan...', 'etherscan') @@ -193,15 +134,3 @@ async function getAbiFromEtherscan(address, network) { debug.log(err.message, 'etherscan') } } - -function isValidJsonFile(abi) { - if (path.extname(abi) !== '.json') { - return false - } - - if (!fs.existsSync(abi)) { - return false - } - - return true -} diff --git a/packages/ethernaut-interact-ui/src/suggest/abi.js b/packages/ethernaut-interact-ui/src/suggest/abi.js new file mode 100644 index 00000000..fc9e2b0f --- /dev/null +++ b/packages/ethernaut-interact-ui/src/suggest/abi.js @@ -0,0 +1,80 @@ +const fs = require('fs') +const path = require('path') +const { getNetworkName } = require('common/src/network') +const similarity = require('string-similarity') +const storage = require('ethernaut-interact/src/internal/storage') +const debug = require('common/src/debug') + +module.exports = async function suggestAbi({ abi, hre, address }) { + try { + const network = await getNetworkName(hre) + + // Try to complete a partial abi path + if (abi && !isValidJsonFile(abi)) { + const abis = storage.readAbiFiles() + const matches = similarity.findBestMatch( + abi, + abis.map((a) => a.name), + ) + if (matches) { + const match = abis.find((a) => a.name === matches.bestMatch.target) + if (match) { + debug.log( + `Matched incoming ABI "${abi}" with known ABI "${match.path}"`, + 'interact', + ) + + return match.path + } + } + } + if (abi) return abi + + // Try to deduce the abi from previous interactions + // in the current network + if (address) { + abi = deduceAbiFromAddress(address, network) + if (abi) return abi + } else { + debug.log('Cannot deduce from address', 'interact') + } + + return abi + } catch (err) { + debug.log(err, 'interact') + } +} + +function deduceAbiFromAddress(address, network) { + const addresses = storage.readAddresses()[network] + if (!addresses) { + debug.log(`No addresses found for ${network}`, 'interact') + return undefined + } + debug.log( + `Found ${Object.keys(addresses).length} addresses on ${network}`, + 'interact', + ) + + const abi = addresses[address] + if (!abi) { + debug.log(`No address entry found for ${address} on ${network}`, 'interact') + return + } + + debug.log(`Found ...${abi.split('/').pop()} for ${address}`, 'interact') + + return abi +} + +function isValidJsonFile(abi) { + if (path.extname(abi) !== '.json') { + return false + } + + if (!fs.existsSync(abi)) { + return false + } + + return true +} diff --git a/packages/ethernaut-ui/src/internal/collect-args.js b/packages/ethernaut-ui/src/internal/collect-args.js index 9dca5bfa..8b244fa0 100644 --- a/packages/ethernaut-ui/src/internal/collect-args.js +++ b/packages/ethernaut-ui/src/internal/collect-args.js @@ -61,20 +61,39 @@ async function collectArg(paramDef, providedArg, parsedArg, argsSoFar) { } } - // Does the parameter provide its own prompt function? - if (paramDef.prompt) { + // Does the parameter provide suggestions? + let suggested + if (paramDef.suggest) { + suggested = await suggest(paramDef, argsSoFar) + } + + // Does the parameter provide its own custom prompt function? + if (!suggested && paramDef.prompt) { collectedArg = await customPrompt(paramDef, argsSoFar) if (collectedArg !== undefined) return collectedArg } // Mmnope, ok. Ask the user to input the parameter in raw text - if (collectedArg === undefined) collectedArg = await rawPrompt(paramDef) + if (collectedArg === undefined) + collectedArg = await rawPrompt(paramDef, suggested || paramDef.defaultValue) return collectedArg } +async function suggest(paramDef, argsSoFar) { + debug.log(`Running suggestions for "${paramDef.name}"`, 'ui') + + const suggestion = await paramDef.suggest({ + hre: _hre, + ...argsSoFar, + }) + debug.log(`Suggested value for "${paramDef.name}": "${suggestion}"`, 'ui') + + return suggestion +} + async function customPrompt(paramDef, argsSoFar) { - debug.log(`Running autocompletion for "${paramDef.name}"`, 'ui') + debug.log(`Running custom prompt for "${paramDef.name}"`, 'ui') const collectedArg = await paramDef.prompt({ hre: _hre, @@ -87,15 +106,17 @@ async function customPrompt(paramDef, argsSoFar) { return collectedArg } -async function rawPrompt(param) { - const description = param.description - ? ` (${param.description.split('.')[0].substring(0, 150)})` +async function rawPrompt(paramDef, suggested) { + debug.log(`Running raw prompt for "${paramDef.name}"`, 'ui') + + const description = paramDef.description + ? ` (${paramDef.description.split('.')[0].substring(0, 150)})` : '' const result = await prompt({ type: 'input', - message: `Enter ${param.name}${description}:`, - initial: param.defaultValue, + message: `Enter ${paramDef.name}${description}:`, + initial: suggested, }) if (result === 'false') return false