From 0eef39200c681a6062a8eb2fe956c01a0654a07e Mon Sep 17 00:00:00 2001 From: LeoSagan Date: Tue, 17 Sep 2024 14:49:23 -0400 Subject: [PATCH] Adds calldata decoding task --- packages/ethernaut-util/src/tasks/calldata.js | 41 ++ .../test/tasks/calldata.test.js | 365 ++++++++++++++++++ 2 files changed, 406 insertions(+) create mode 100644 packages/ethernaut-util/src/tasks/calldata.js create mode 100644 packages/ethernaut-util/test/tasks/calldata.test.js diff --git a/packages/ethernaut-util/src/tasks/calldata.js b/packages/ethernaut-util/src/tasks/calldata.js new file mode 100644 index 0000000..0c917a8 --- /dev/null +++ b/packages/ethernaut-util/src/tasks/calldata.js @@ -0,0 +1,41 @@ +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 new file mode 100644 index 0000000..d989d46 --- /dev/null +++ b/packages/ethernaut-util/test/tasks/calldata.test.js @@ -0,0 +1,365 @@ +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')) + }) + }) +})