From 9f65eb30b7d5ab62f8e5c042bb5e05af29ed9978 Mon Sep 17 00:00:00 2001 From: LeoSagan Date: Tue, 24 Sep 2024 14:54:16 -0400 Subject: [PATCH] Moves calldata to interact --- package.json | 3 - packages/ethernaut-interact/README.md | 1 + packages/ethernaut-interact/src/abis/dai.json | 333 ++++++++++++++++ .../ethernaut-interact/src/tasks/calldata.js | 71 ++++ .../test/tasks/calldata.test.js | 31 ++ packages/ethernaut-util/README.md | 1 - packages/ethernaut-util/src/tasks/calldata.js | 41 -- .../test/tasks/calldata.test.js | 365 ------------------ 8 files changed, 436 insertions(+), 410 deletions(-) create mode 100644 packages/ethernaut-interact/src/abis/dai.json create mode 100644 packages/ethernaut-interact/src/tasks/calldata.js create mode 100644 packages/ethernaut-interact/test/tasks/calldata.test.js delete mode 100644 packages/ethernaut-util/src/tasks/calldata.js delete mode 100644 packages/ethernaut-util/test/tasks/calldata.test.js diff --git a/package.json b/package.json index 31c278af..81f9aedb 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,5 @@ "lerna": "^8.1.2", "lint-staged": "^15.2.2", "prettier": "^3.2.5" - }, - "dependencies": { - "ethernaut-util": "^1.1.11" } } diff --git a/packages/ethernaut-interact/README.md b/packages/ethernaut-interact/README.md index ff139b55..153d2709 100644 --- a/packages/ethernaut-interact/README.md +++ b/packages/ethernaut-interact/README.md @@ -41,6 +41,7 @@ This plugin adds the following tasks: - logs Finds logs emitted by a contract - send Sends ether to an address - token Interacts with any ERC20 token +- calldata Decodes calldata from a transaction id ## Environment extensions diff --git a/packages/ethernaut-interact/src/abis/dai.json b/packages/ethernaut-interact/src/abis/dai.json new file mode 100644 index 00000000..025f28ec --- /dev/null +++ b/packages/ethernaut-interact/src/abis/dai.json @@ -0,0 +1,333 @@ +[ + { + "inputs": [ + { "internalType": "uint256", "name": "chainId_", "type": "uint256" } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "src", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "guy", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "wad", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": true, + "inputs": [ + { + "indexed": true, + "internalType": "bytes4", + "name": "sig", + "type": "bytes4" + }, + { + "indexed": true, + "internalType": "address", + "name": "usr", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "arg1", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "arg2", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "LogNote", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "src", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "dst", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "wad", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "constant": true, + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "PERMIT_TYPEHASH", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" } + ], + "name": "allowance", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "internalType": "address", "name": "usr", "type": "address" }, + { "internalType": "uint256", "name": "wad", "type": "uint256" } + ], + "name": "approve", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "balanceOf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "internalType": "address", "name": "usr", "type": "address" }, + { "internalType": "uint256", "name": "wad", "type": "uint256" } + ], + "name": "burn", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [{ "internalType": "address", "name": "guy", "type": "address" }], + "name": "deny", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "internalType": "address", "name": "usr", "type": "address" }, + { "internalType": "uint256", "name": "wad", "type": "uint256" } + ], + "name": "mint", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "internalType": "address", "name": "src", "type": "address" }, + { "internalType": "address", "name": "dst", "type": "address" }, + { "internalType": "uint256", "name": "wad", "type": "uint256" } + ], + "name": "move", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "nonces", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "internalType": "address", "name": "holder", "type": "address" }, + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "nonce", "type": "uint256" }, + { "internalType": "uint256", "name": "expiry", "type": "uint256" }, + { "internalType": "bool", "name": "allowed", "type": "bool" }, + { "internalType": "uint8", "name": "v", "type": "uint8" }, + { "internalType": "bytes32", "name": "r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "s", "type": "bytes32" } + ], + "name": "permit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "internalType": "address", "name": "usr", "type": "address" }, + { "internalType": "uint256", "name": "wad", "type": "uint256" } + ], + "name": "pull", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "internalType": "address", "name": "usr", "type": "address" }, + { "internalType": "uint256", "name": "wad", "type": "uint256" } + ], + "name": "push", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [{ "internalType": "address", "name": "guy", "type": "address" }], + "name": "rely", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "internalType": "address", "name": "dst", "type": "address" }, + { "internalType": "uint256", "name": "wad", "type": "uint256" } + ], + "name": "transfer", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "internalType": "address", "name": "src", "type": "address" }, + { "internalType": "address", "name": "dst", "type": "address" }, + { "internalType": "uint256", "name": "wad", "type": "uint256" } + ], + "name": "transferFrom", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "version", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "wards", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + } +] diff --git a/packages/ethernaut-interact/src/tasks/calldata.js b/packages/ethernaut-interact/src/tasks/calldata.js new file mode 100644 index 00000000..0436d0af --- /dev/null +++ b/packages/ethernaut-interact/src/tasks/calldata.js @@ -0,0 +1,71 @@ +const { ethers } = require('ethers') +const types = require('ethernaut-common/src/validation/types') +const output = require('ethernaut-common/src/ui/output') +const EthernautCliError = require('ethernaut-common/src/error/error') +const abi = require('../../../ethernaut-interact-ui/src/suggest/abi') + +require('../scopes/interact') + .task( + 'calldata', + 'Decodes calldata of a provided one, or searches for it from a given tx id', + ) + .addOptionalPositionalParam( + 'calldata', + 'The calldata to decode', + undefined, + types.string, + ) + .addOptionalPositionalParam( + 'transactionId', + 'The transaction to lookup', + undefined, + types.bytes32, + ) + .addPositionalParam( + 'abi', + 'The ABI of the contract to decode the calldata', + undefined, + types.array, + ) + .addPositionalParam( + 'filter', + 'Some text to filter the list of known ABIs. Leave empty to list all. Results will be ordered by similarity to the filter text, so the first result will be the best match.', + undefined, + types.string, + ) + .setAction(async ({ calldata, transactionId }, hre) => { + try { + const iface = new ethers.utils.Interface(abi) + + if (calldata) { + const decodedData = iface.parseTransaction({ data: calldata }) + return output.resultBox({ + functionName: decodedData.name, + args: decodedData.args, + }) + } + + if (transactionId) { + const tx = await hre.ethers.provider.getTransaction(transactionId) + if (!tx) + throw new EthernautCliError( + 'ethernaut-network', + 'No tx found.', + false, + ) + const decodedData = iface.parseTransaction({ data: tx.data }) + return output.resultBox({ + functionName: decodedData.name, + args: decodedData.args, + }) + } + + throw new EthernautCliError( + 'ethernaut-interact', + 'Either transactionId or calldata must be provided', + false, + ) + } catch (err) { + return output.errorBox(err) + } + }) diff --git a/packages/ethernaut-interact/test/tasks/calldata.test.js b/packages/ethernaut-interact/test/tasks/calldata.test.js new file mode 100644 index 00000000..412a99a9 --- /dev/null +++ b/packages/ethernaut-interact/test/tasks/calldata.test.js @@ -0,0 +1,31 @@ +const assert = require('assert') + +const DAI_ABI = require('ethernaut-util/src/abis/dai.json') + +describe('calldata', function () { + let output + describe('when decoding 0x4054d7d608e735608ac6fe70609f9a9616215309e576e975414c4f73374b0b78', function () { + before('calldata', async function () { + hre.ethers.provider = new hre.ethers.JsonRpcProvider( + 'https://ethereum-rpc.publicnode.com', + ) + output = await hre.run( + { + scope: 'interact', + task: 'calldata', + }, + { + transactionId: + '0x4054d7d608e735608ac6fe70609f9a9616215309e576e975414c4f73374b0b78', + }, + { + abi: DAI_ABI, + }, + ) + }) + + it('finds the functionName', async function () { + assert.ok(output.includes('functionName')) + }) + }) +}) diff --git a/packages/ethernaut-util/README.md b/packages/ethernaut-util/README.md index 2411c1cf..38b43a40 100644 --- a/packages/ethernaut-util/README.md +++ b/packages/ethernaut-util/README.md @@ -41,7 +41,6 @@ This plugin adds the tasks listed below. - unit Converts between different units of Ether - gas Fetch gas info on the current network - chain Finds a network name from a chain ID, or vice versa -- calldata Decodes calldata from a transaction id ## Environment extensions diff --git a/packages/ethernaut-util/src/tasks/calldata.js b/packages/ethernaut-util/src/tasks/calldata.js deleted file mode 100644 index 0c917a8b..00000000 --- a/packages/ethernaut-util/src/tasks/calldata.js +++ /dev/null @@ -1,41 +0,0 @@ -const { ethers } = require('ethers') -const types = require('ethernaut-common/src/validation/types') -const output = require('ethernaut-common/src/ui/output') -const EthernautCliError = require('ethernaut-common/src/error/error') - -require('../scopes/util') - .task('calldata', 'Decodes calldata of a tx') - .addPositionalParam( - 'transactionId', - 'The transaction to lookup', - undefined, - types.string, - ) - .addPositionalParam( - 'abi', - 'The ABI of the contract to decode the calldata', - undefined, - types.array, - ) - .setAction(async ({ transactionId, abi }, hre) => { - try { - const tx = await hre.ethers.provider.getTransaction(transactionId) - - if (!tx) { - throw new EthernautCliError('ethernaut-network', 'No tx found.', false) - } - - const calldata = tx.data - const iface = new ethers.utils.Interface(abi) - const decodedData = iface.parseTransaction({ data: calldata }) - - const callData = { - functionName: decodedData.name, - args: decodedData.args, - } - - return output.resultBox(callData) - } catch (err) { - return output.errorBox(err) - } - }) diff --git a/packages/ethernaut-util/test/tasks/calldata.test.js b/packages/ethernaut-util/test/tasks/calldata.test.js deleted file mode 100644 index d989d46f..00000000 --- a/packages/ethernaut-util/test/tasks/calldata.test.js +++ /dev/null @@ -1,365 +0,0 @@ -const assert = require('assert') - -describe('calldata', function () { - let output - describe('when decoding 0x4054d7d608e735608ac6fe70609f9a9616215309e576e975414c4f73374b0b78', function () { - before('calldata', async function () { - hre.ethers.provider = new hre.ethers.JsonRpcProvider( - 'https://ethereum-rpc.publicnode.com', - ) - output = await hre.run( - { - scope: 'util', - task: 'calldata', - }, - { - transactionId: - '0x4054d7d608e735608ac6fe70609f9a9616215309e576e975414c4f73374b0b78', - }, - { - abi: [ - { - inputs: [ - { internalType: 'uint256', name: 'chainId_', type: 'uint256' }, - ], - payable: false, - stateMutability: 'nonpayable', - type: 'constructor', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'src', - type: 'address', - }, - { - indexed: true, - internalType: 'address', - name: 'guy', - type: 'address', - }, - { - indexed: false, - internalType: 'uint256', - name: 'wad', - type: 'uint256', - }, - ], - name: 'Approval', - type: 'event', - }, - { - anonymous: true, - inputs: [ - { - indexed: true, - internalType: 'bytes4', - name: 'sig', - type: 'bytes4', - }, - { - indexed: true, - internalType: 'address', - name: 'usr', - type: 'address', - }, - { - indexed: true, - internalType: 'bytes32', - name: 'arg1', - type: 'bytes32', - }, - { - indexed: true, - internalType: 'bytes32', - name: 'arg2', - type: 'bytes32', - }, - { - indexed: false, - internalType: 'bytes', - name: 'data', - type: 'bytes', - }, - ], - name: 'LogNote', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'src', - type: 'address', - }, - { - indexed: true, - internalType: 'address', - name: 'dst', - type: 'address', - }, - { - indexed: false, - internalType: 'uint256', - name: 'wad', - type: 'uint256', - }, - ], - name: 'Transfer', - type: 'event', - }, - { - constant: true, - inputs: [], - name: 'DOMAIN_SEPARATOR', - outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }], - payable: false, - stateMutability: 'view', - type: 'function', - }, - { - constant: true, - inputs: [], - name: 'PERMIT_TYPEHASH', - outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }], - payable: false, - stateMutability: 'view', - type: 'function', - }, - { - constant: true, - inputs: [ - { internalType: 'address', name: '', type: 'address' }, - { internalType: 'address', name: '', type: 'address' }, - ], - name: 'allowance', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - payable: false, - stateMutability: 'view', - type: 'function', - }, - { - constant: false, - inputs: [ - { internalType: 'address', name: 'usr', type: 'address' }, - { internalType: 'uint256', name: 'wad', type: 'uint256' }, - ], - name: 'approve', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], - payable: false, - stateMutability: 'nonpayable', - type: 'function', - }, - { - constant: true, - inputs: [{ internalType: 'address', name: '', type: 'address' }], - name: 'balanceOf', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - payable: false, - stateMutability: 'view', - type: 'function', - }, - { - constant: false, - inputs: [ - { internalType: 'address', name: 'usr', type: 'address' }, - { internalType: 'uint256', name: 'wad', type: 'uint256' }, - ], - name: 'burn', - outputs: [], - payable: false, - stateMutability: 'nonpayable', - type: 'function', - }, - { - constant: true, - inputs: [], - name: 'decimals', - outputs: [{ internalType: 'uint8', name: '', type: 'uint8' }], - payable: false, - stateMutability: 'view', - type: 'function', - }, - { - constant: false, - inputs: [ - { internalType: 'address', name: 'guy', type: 'address' }, - ], - name: 'deny', - outputs: [], - payable: false, - stateMutability: 'nonpayable', - type: 'function', - }, - { - constant: false, - inputs: [ - { internalType: 'address', name: 'usr', type: 'address' }, - { internalType: 'uint256', name: 'wad', type: 'uint256' }, - ], - name: 'mint', - outputs: [], - payable: false, - stateMutability: 'nonpayable', - type: 'function', - }, - { - constant: false, - inputs: [ - { internalType: 'address', name: 'src', type: 'address' }, - { internalType: 'address', name: 'dst', type: 'address' }, - { internalType: 'uint256', name: 'wad', type: 'uint256' }, - ], - name: 'move', - outputs: [], - payable: false, - stateMutability: 'nonpayable', - type: 'function', - }, - { - constant: true, - inputs: [], - name: 'name', - outputs: [{ internalType: 'string', name: '', type: 'string' }], - payable: false, - stateMutability: 'view', - type: 'function', - }, - { - constant: true, - inputs: [{ internalType: 'address', name: '', type: 'address' }], - name: 'nonces', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - payable: false, - stateMutability: 'view', - type: 'function', - }, - { - constant: false, - inputs: [ - { internalType: 'address', name: 'holder', type: 'address' }, - { internalType: 'address', name: 'spender', type: 'address' }, - { internalType: 'uint256', name: 'nonce', type: 'uint256' }, - { internalType: 'uint256', name: 'expiry', type: 'uint256' }, - { internalType: 'bool', name: 'allowed', type: 'bool' }, - { internalType: 'uint8', name: 'v', type: 'uint8' }, - { internalType: 'bytes32', name: 'r', type: 'bytes32' }, - { internalType: 'bytes32', name: 's', type: 'bytes32' }, - ], - name: 'permit', - outputs: [], - payable: false, - stateMutability: 'nonpayable', - type: 'function', - }, - { - constant: false, - inputs: [ - { internalType: 'address', name: 'usr', type: 'address' }, - { internalType: 'uint256', name: 'wad', type: 'uint256' }, - ], - name: 'pull', - outputs: [], - payable: false, - stateMutability: 'nonpayable', - type: 'function', - }, - { - constant: false, - inputs: [ - { internalType: 'address', name: 'usr', type: 'address' }, - { internalType: 'uint256', name: 'wad', type: 'uint256' }, - ], - name: 'push', - outputs: [], - payable: false, - stateMutability: 'nonpayable', - type: 'function', - }, - { - constant: false, - inputs: [ - { internalType: 'address', name: 'guy', type: 'address' }, - ], - name: 'rely', - outputs: [], - payable: false, - stateMutability: 'nonpayable', - type: 'function', - }, - { - constant: true, - inputs: [], - name: 'symbol', - outputs: [{ internalType: 'string', name: '', type: 'string' }], - payable: false, - stateMutability: 'view', - type: 'function', - }, - { - constant: true, - inputs: [], - name: 'totalSupply', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - payable: false, - stateMutability: 'view', - type: 'function', - }, - { - constant: false, - inputs: [ - { internalType: 'address', name: 'dst', type: 'address' }, - { internalType: 'uint256', name: 'wad', type: 'uint256' }, - ], - name: 'transfer', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], - payable: false, - stateMutability: 'nonpayable', - type: 'function', - }, - { - constant: false, - inputs: [ - { internalType: 'address', name: 'src', type: 'address' }, - { internalType: 'address', name: 'dst', type: 'address' }, - { internalType: 'uint256', name: 'wad', type: 'uint256' }, - ], - name: 'transferFrom', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], - payable: false, - stateMutability: 'nonpayable', - type: 'function', - }, - { - constant: true, - inputs: [], - name: 'version', - outputs: [{ internalType: 'string', name: '', type: 'string' }], - payable: false, - stateMutability: 'view', - type: 'function', - }, - { - constant: true, - inputs: [{ internalType: 'address', name: '', type: 'address' }], - name: 'wards', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - payable: false, - stateMutability: 'view', - type: 'function', - }, - ], - }, - ) - }) - - it('finds the functionName', async function () { - assert.ok(output.includes('functionName')) - }) - }) -})