Skip to content

Commit

Permalink
Merge pull request #5 from theethernaut/feature/hardhat-required
Browse files Browse the repository at this point in the history
Feature/hardhat required
  • Loading branch information
eternauta1337 authored Mar 1, 2024
2 parents cb84d9f + b826b99 commit b462dc7
Show file tree
Hide file tree
Showing 24 changed files with 119 additions and 79 deletions.
3 changes: 2 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/ethernaut-ai/src/tasks/interpret.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ let _query

require('../scopes/ai')
.task('interpret', 'Interprets natural language into CLI commands')
.addOptionalPositionalParam(
.addPositionalParam(
'query',
'The natural language query to convert to CLI commands',
undefined,
Expand Down
7 changes: 1 addition & 6 deletions packages/ethernaut-challenges/src/tasks/check.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,7 @@ require('../scopes/oz')
'check',
'Checks if the player has completed the specified level by submitting an instance modified as per the levels requirements',
)
.addOptionalPositionalParam(
'level',
'The level number',
undefined,
types.string,
)
.addPositionalParam('level', 'The level number', undefined, types.string)
.setAction(async ({ level }, hre) => {
try {
const completed = await checkLevel(level, hre)
Expand Down
7 changes: 1 addition & 6 deletions packages/ethernaut-challenges/src/tasks/info.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,7 @@ require('../scopes/oz')
'info',
'Shows information about an open zeppelin challenges level. The info includes the level name, contract name, ABI path, address, and description. The ABI path can be used with the interact package call task to interact with the contract.',
)
.addOptionalPositionalParam(
'level',
'The level number',
undefined,
types.string,
)
.addPositionalParam('level', 'The level number', undefined, types.string)
.setAction(async ({ level }) => {
try {
const info = await getLevelInfo(level)
Expand Down
7 changes: 1 addition & 6 deletions packages/ethernaut-challenges/src/tasks/instance.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,7 @@ require('../scopes/oz')
'instance',
'Creates an instance of a level, so that it can be played. The address of the instance is printed to the console. Use this address to interact with the contract using the ethernaut-cli contract command. Make sure to use the info command to get instructions on how to complete the level.',
)
.addOptionalPositionalParam(
'level',
'The level number',
undefined,
types.string,
)
.addPositionalParam('level', 'The level number', undefined, types.string)
.setAction(async ({ level }, hre) => {
try {
const instanceAddress = await createInstance(level, hre)
Expand Down
2 changes: 1 addition & 1 deletion packages/ethernaut-challenges/src/tasks/submit.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require('../scopes/oz')
'submit',
'Submits an instance created by the instance task, and later manipulated as required by the level. The instance must be submitted to the games main contract in order to complete the level. Use the info command to get instructions on how to complete the level.',
)
.addOptionalPositionalParam(
.addPositionalParam(
'address',
'The address of the instance to submit',
undefined,
Expand Down
11 changes: 3 additions & 8 deletions packages/ethernaut-interact/src/tasks/contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,14 @@ const { getNetworkName } = require('common/src/network')

const contract = require('../scopes/interact')
.task('contract', 'Interacts with a contract')
.addOptionalParam(
.addParam(
'abi',
'The path to a json file specifying the abi of the contract',
undefined,
types.string,
)
.addOptionalParam(
'address',
'The address of the contract',
undefined,
types.string,
)
.addOptionalParam(
.addParam('address', 'The address of the contract', undefined, types.string)
.addParam(
'fn',
'The function of the contract to call',
undefined,
Expand Down
2 changes: 1 addition & 1 deletion packages/ethernaut-network/src/tasks/activate.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const { setNetwork } = require('../internal/set-network')

const set = require('../scopes/network')
.task('activate', 'Activates a network')
.addOptionalPositionalParam(
.addPositionalParam(
'alias',
'The name of the network',
undefined,
Expand Down
4 changes: 2 additions & 2 deletions packages/ethernaut-network/src/tasks/add.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ const { validateVarName } = require('common/src/name')

const add = require('../scopes/network')
.task('add', 'Adds a network to the cli')
.addOptionalPositionalParam(
.addPositionalParam(
'alias',
'The name of the network',
undefined,
types.string,
)
.addOptionalParam(
.addParam(
'url',
'The url of the network provider, e.g. https://ethereum-rpc.publicnode.com. Note: Environment variables may be included, e.g. https://eth-mainnet.alchemyapi.io/v2/${INFURA_API_KEY}. Make sure to specify these in your .env file.',
undefined,
Expand Down
4 changes: 2 additions & 2 deletions packages/ethernaut-network/src/tasks/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ const storage = require('../internal/storage')

const edit = require('../scopes/network')
.task('edit', 'Edits a network')
.addOptionalPositionalParam(
.addPositionalParam(
'alias',
'The name of the network',
undefined,
types.string,
)
.addOptionalParam('url', 'The network url', undefined, types.string)
.addParam('url', 'The network url', undefined, types.string)
.setAction(async ({ alias, url }) => {
try {
const networks = storage.readNetworks()
Expand Down
2 changes: 1 addition & 1 deletion packages/ethernaut-network/src/tasks/info.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const autocompleteAlias = require('./autocomplete/alias')

const info = require('../scopes/network')
.task('info', 'Provides information about a network')
.addOptionalPositionalParam(
.addPositionalParam(
'alias',
'The name or url of the network',
undefined,
Expand Down
6 changes: 3 additions & 3 deletions packages/ethernaut-network/src/tasks/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ const local = require('../scopes/network')
.addOptionalParam(
'fork',
'The alias or url of the network to fork',
undefined,
'none',
types.string,
)
.addOptionalParam(
'port',
'The port to run the local chain on',
undefined,
'8545',
types.string,
)
.setAction(async ({ fork, port }) => {
Expand All @@ -26,7 +26,7 @@ const local = require('../scopes/network')

port = Number(port) || 8545

if (forkUrl) {
if (forkUrl.url) {
output.info(`Starting local chain with fork ${forkUrl.url}...`)
} else {
output.info('Starting local chain...')
Expand Down
2 changes: 1 addition & 1 deletion packages/ethernaut-network/src/tasks/remove.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const storage = require('../internal/storage')

const remove = require('../scopes/network')
.task('remove', 'Removes a network from the cli')
.addOptionalPositionalParam(
.addPositionalParam(
'alias',
'The name of the network',
undefined,
Expand Down
31 changes: 24 additions & 7 deletions packages/ethernaut-ui/src/internal/collect-args.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const prompt = require('common/src/prompt')
let _hre

module.exports = async function collectArguments(providedArgs, task, hre) {
debug.log(`Collecting arguments for task ${task.name}`, 'ui')
debug.log(`Collecting parameters for task ${task.name}`, 'ui')

_hre = hre

Expand All @@ -20,10 +20,15 @@ module.exports = async function collectArguments(providedArgs, task, hre) {
// if (paramDef.isFlag) continue;

const providedArg = providedArgs[paramDef.name]

const parsedArg = paramDef.parsedValue
const argsSoFar = { ...providedArgs, ...collectedArgs }

const collectedArg = await collectArg(paramDef, providedArg, argsSoFar)
const collectedArg = await collectArg(
paramDef,
providedArg,
parsedArg,
argsSoFar,
)
if (collectedArg !== undefined) {
debug.log(
`Autocompletion for "${paramDef.name}" collected "${collectedArg}"`,
Expand All @@ -37,22 +42,33 @@ module.exports = async function collectArguments(providedArgs, task, hre) {
return collectedArgs
}

async function collectArg(paramDef, providedArg, argsSoFar) {
async function collectArg(paramDef, providedArg, parsedArg, argsSoFar) {
debug.log(
`Collecting parameter "${paramDef.name}" - Provided: ${providedArg}"`,
`Collecting "${paramDef.name}" - Provided: "${providedArg}", Parsed: "${parsedArg}"`,
'ui',
)

let collectedArg

// Is the parameter already provided?
// (But is not the default value injected by hardhat)
if (providedArg) {
const isInjectedDefault =
providedArg === paramDef.defaultValue && parsedArg === undefined
if (!isInjectedDefault) {
debug.log('Value was provided by the user, skipping autocompletion', 'ui')
return providedArg
}
}

// Does the parameter provide its own autocomplete function?
if (paramDef.autocomplete) {
collectedArg = await autocomplete(paramDef, argsSoFar)
if (collectedArg !== undefined) return collectedArg
}

// Mmnope, ok. Ask the user to input the parameter in raw text
if (collectedArg === undefined && providedArg === undefined)
collectedArg = await rawPrompt(paramDef)
if (collectedArg === undefined) collectedArg = await rawPrompt(paramDef)

return collectedArg
}
Expand All @@ -63,6 +79,7 @@ async function autocomplete(paramDef, argsSoFar) {
const collectedArg = await paramDef.autocomplete({
hre: _hre,
paramName: paramDef.name,
paramDefault: paramDef.defaultValue,
description: paramDef.description,
...argsSoFar,
})
Expand Down
46 changes: 46 additions & 0 deletions packages/ethernaut-ui/src/internal/make-interactive.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,52 @@ function makeInteractive(task) {
// Rn it will throw if this flag is used
// task.addFlag('nonInteractive', 'Disable interactivity', false);

// Note:
// The next blocks of code rely on a small change in hardhat/internal/cli/cli.js,
// that allows this environment extension code to run before hardhat parses cli arguments.
// Issue: https://github.com/NomicFoundation/hardhat/issues/4950
// PR: https://github.com/NomicFoundation/hardhat/pull/4951

// Combine all of the task's parameter definitions in the same array,
// for the operations that follow.
const paramDefinitions = task.positionalParamDefinitions.concat(
Object.values(task.paramDefinitions),
)

// Make all parameters optional so that hardhat doesnt
// throw when required task arguments are not provided.
// We want to collect them interactively.
for (let paramDef of paramDefinitions) {
paramDef.originallyOptional = paramDef.isOptional
paramDef.isOptional = true
}

// Also, intercept parsing of parameters because
// hardhat automatically injects default values into the action's args.
// We want to identify when this happens so that we still show prompts
// with the default value merely suggested instead of directly injected.
const special = (originalType, paramDef) => ({
name: 'special',
parse: (argName, argValue) => {
const parsedValue = originalType.parse(argName, argValue)
paramDef.parsedValue = parsedValue
debug.log(
`Parsing "${argName}" - Provided: "${argValue}", Default: "${paramDef.defaultValue}", Parsed: "${parsedValue}"`,
'ui',
)
return parsedValue
},
validate: (argName, argValue) => {
return originalType.validate(argName, argValue)
},
})
for (let paramDef of paramDefinitions) {
debug.log(` Modifying parser for param "${paramDef.name}"`, 'ui-deep')
paramDef.originallyOptional = paramDef.isOptional
paramDef.isOptional = true
paramDef.type = special(paramDef.type, paramDef)
}

// Override the action so that we can
// collect parameters from the user before runnint it
const action = async (args, hre, runSuper) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/ethernaut-ui/test/tasks/interact-unit.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ describe('interact unit', function () {

describe('when entering unit with all params provided', function () {
before('run command', async function () {
await terminal.run('npx hardhat util unit 1 --from ether --to wei', 1000)
await terminal.run('npx hardhat util unit 1 --from ether --to wei')
})

it('contains the word "result"', async function () {
Expand Down
2 changes: 1 addition & 1 deletion packages/ethernaut-util/src/tasks/bytes.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const output = require('common/src/output')

require('../scopes/util')
.task('bytes', 'Converts strings to bytes32')
.addOptionalPositionalParam(
.addPositionalParam(
'value',
'The value to convert. Will always be treated as a string. Cannot be longer than a bytes32 string.',
undefined,
Expand Down
7 changes: 1 addition & 6 deletions packages/ethernaut-util/src/tasks/string.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,7 @@ const output = require('common/src/output')

require('../scopes/util')
.task('string', 'Converts bytes32 to string')
.addOptionalPositionalParam(
'value',
'The value to convert',
undefined,
types.string,
)
.addPositionalParam('value', 'The value to convert', undefined, types.string)
.setAction(async ({ value }, hre) => {
try {
const strNullPadded = hre.ethers.toUtf8String(value)
Expand Down
28 changes: 17 additions & 11 deletions packages/ethernaut-util/src/tasks/unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,9 @@ const unit = require('../scopes/util')
',',
)}.`,
)
.addOptionalPositionalParam(
'value',
'The value to convert',
undefined,
types.string,
)
.addOptionalParam('from', 'The unit to convert from', undefined, types.string)
.addOptionalParam('to', 'The unit to convert to', undefined, types.string)
.addPositionalParam('value', 'The value to convert', undefined, types.string)
.addOptionalParam('from', 'The unit to convert from', 'ether', types.string)
.addOptionalParam('to', 'The unit to convert to', 'wei', types.string)
.setAction(async ({ value, from, to }, hre) => {
try {
const valueWei = hre.ethers.parseUnits(value, from)
Expand All @@ -33,10 +28,20 @@ const unit = require('../scopes/util')
}
})

async function autocompleteUnit({ paramName, description, from, to }) {
async function autocompleteUnit({
paramName,
paramDefault,
description,
from,
to,
}) {
const valueProvided =
paramName === 'from' ? from !== undefined : to !== undefined
const isDefault =
paramName === 'from' ? from === paramDefault : to === paramDefault

// No need to autocomplete?
if (paramName === 'from' && from) return undefined
if (paramName === 'to' && to) return undefined
if (valueProvided && !isDefault) return undefined

// Choices are all units minus the one used
let choices = units.concat()
Expand All @@ -49,6 +54,7 @@ async function autocompleteUnit({ paramName, description, from, to }) {
type: 'autocomplete',
message: `Enter ${paramName} (${description})`,
choices,
initial: choices.indexOf(paramDefault),
})
}

Expand Down
2 changes: 1 addition & 1 deletion packages/ethernaut-wallet/src/tasks/activate.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const { setSigner } = require('../internal/signers')

const set = require('../scopes/wallet')
.task('activate', 'Activates a wallet')
.addOptionalPositionalParam(
.addPositionalParam(
'alias',
'The name of the wallet',
undefined,
Expand Down
Loading

0 comments on commit b462dc7

Please sign in to comment.