Skip to content

Commit

Permalink
First suggest extension
Browse files Browse the repository at this point in the history
  • Loading branch information
eternauta1337 committed Mar 3, 2024
1 parent 5e6a558 commit f18f29a
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 80 deletions.
1 change: 1 addition & 0 deletions packages/ethernaut-interact-ui/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
71 changes: 0 additions & 71 deletions packages/ethernaut-interact-ui/src/prompts/abi.js
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -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')
Expand Down Expand Up @@ -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')

Expand Down Expand Up @@ -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
}
80 changes: 80 additions & 0 deletions packages/ethernaut-interact-ui/src/suggest/abi.js
Original file line number Diff line number Diff line change
@@ -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
}
39 changes: 30 additions & 9 deletions packages/ethernaut-ui/src/internal/collect-args.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down

0 comments on commit f18f29a

Please sign in to comment.