Skip to content

Commit 7793abf

Browse files
authored
Inspect storage, storageLayout, bytecode, opcodes (#23)
* command: .inspect * .fetch storage <slot> <num> [address] * fix hexview col headers show if fork-mode is enabled * prep v0.2.2 * update changelog * fix example * simplify getDeployed
1 parent 17d0570 commit 7793abf

File tree

7 files changed

+171
-6
lines changed

7 files changed

+171
-6
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
# Change Log
22
All notable changes will be documented in this file.
33

4+
## v0.2.2
5+
- new: inspection commands: `.inspect` contract raw storage, show generated bytecode, opcodes, storageLayout - #23
6+
7+
<img width="941" alt="image" src="https://user-images.githubusercontent.com/2865694/183737870-8faa103d-2564-435b-8789-d9b4cde94c10.png">
8+
9+
```
10+
.inspect
11+
bytecode ... show bytecode of underlying contract
12+
opcodes ... show disassembled opcodes of underlying contract
13+
storageLayout ... show variable to storage slot mapping for underlying contract
14+
storage <slot> <num> [<address>] ... show raw storage at slot of underlying deployed contract
15+
deployed ... debug: show internal contract object
16+
```
17+
418
## v0.2.1
519
- fix: feed current compiler version into abi-to-sol; strip attribution and other code #20 #21
620
- update: compiler list

README.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,17 @@ BNB
8585
.help ... this help :)
8686
.exit ... exit the shell
8787

88+
8889
Source:
8990
.fetch
9091
interface <address> <name> [chain=mainnet] ... fetch and load an interface declaration from an ABI spec on etherscan.io
92+
.inspect
93+
bytecode ... show bytecode of underlying contract
94+
opcodes ... show disassembled opcodes of underlying contract
95+
storageLayout ... show variable to storage slot mapping for underlying contract
96+
storage <slot> <num> [<address>] ... show raw storage at slot of underlying deployed contract
97+
deployed ... debug: show internal contract object
98+
9199

92100
Blockchain:
93101
.chain
@@ -227,6 +235,61 @@ contract MainContract {
227235
» t.symbol()
228236
MGX
229237
```
238+
239+
### Inspect Contract Storage on Ganache Fork
240+
241+
1. Run solidity shell in **fork-mode**.
242+
2. Display contract storage at latest block.
243+
244+
245+
```
246+
⇒ solidity-shell --fork https://mainnet.infura.io/v3/<yourApiKey>
247+
248+
🚀 Entering interactive Solidity ^0.8.16 shell (🧁:Ganache built-in, ⇉ fork-mode). '.help' and '.exit' are your friends.
249+
» .inspect storage 0 10 0x40cfEe8D71D67108Db46F772B7e2CD55813Bf2FB
250+
»
251+
📚 Contract: 0x40cfee8d71d67108db46f772b7e2cd55813bf2fb @ latest block
252+
253+
slot 1f 1e 1d 1c 1b 1a 19 18 17 16 15 14 13 12 11 10 0f 0e 0d 0c 0b 0a 09 08 07 06 05 04 03 02 01 00
254+
--------------------------------------------------------------------------------------------------------------------
255+
0x000000 ( 0) 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 1d c7 ................................
256+
0x000001 ( 1) 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
257+
0x000002 ( 2) 54 68 65 20 4d 61 67 69 78 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 12 The Magix.......................
258+
0x000003 ( 3) 4d 47 58 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 06 MGX.............................
259+
0x000004 ( 4) 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
260+
0x000005 ( 5) 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
261+
0x000006 ( 6) 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
262+
0x000007 ( 7) 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
263+
0x000008 ( 8) 00 00 00 00 00 00 00 00 00 00 00 00 d7 4e 84 57 2f 5f 7b 5d 41 47 4e be d9 b3 02 0a 2e 52 6f c6 .............N.W/_{]AGN......Ro.
264+
0x000009 ( 9) 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 27 0f ..............................'.
265+
```
266+
267+
### Inspect Generated Contract
268+
269+
```
270+
solidity-shell
271+
272+
🚀 Entering interactive Solidity ^0.8.16 shell (🧁:Ganache built-in, ⇉ fork-mode). '.help' and '.exit' are your friends.
273+
» 1+1
274+
2
275+
» .inspect bytecode
276+
6080604052348015610010576000 ... 03a7bab64736f6c63430008100033
277+
» .inspect opcodes
278+
PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE ... SLOAD 0xDA POP GASPRICE PUSH28 0xAB64736F6C6343000810003300000000000000000000000000000000
279+
» .inspect storageLayout
280+
{ storage: [], types: null }
281+
» .inspect storage 0 4
282+
»
283+
📚 Contract: 0xCa1061046396daF801dEB0D848FcfeA055fAfBFC @ latest block
284+
285+
slot 1f 1e 1d 1c 1b 1a 19 18 17 16 15 14 13 12 11 10 0f 0e 0d 0c 0b 0a 09 08 07 06 05 04 03 02 01 00
286+
--------------------------------------------------------------------------------------------------------------------
287+
0x000000 ( 0) 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
288+
0x000001 ( 1) 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
289+
0x000002 ( 2) 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
290+
0x000003 ( 3) 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................
291+
```
292+
230293
____
231294

232295

bin/main.js

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,13 @@ function handleRepl(input, cb) {
123123
${c.bold('Source:')}
124124
.fetch
125125
interface <address> <name> [chain=mainnet] ... fetch and load an interface declaration from an ABI spec on etherscan.io
126+
.inspect
127+
bytecode ... show bytecode of underlying contract
128+
opcodes ... show disassembled opcodes of underlying contract
129+
storageLayout ... show variable to storage slot mapping for underlying contract
130+
storage <slot> <num> [<address>] ... show raw storage at slot of underlying deployed contract
131+
deployed ... debug: show internal contract object
132+
126133
127134
${c.bold('Blockchain:')}
128135
.chain
@@ -239,6 +246,79 @@ cheers 🙌
239246
return cb();
240247
}
241248
return cb(`${c.bold(c.yellow(shell.blockchain.proc.pid))} - ${shell.blockchain.proc.spawnargs.join(', ')}`)
249+
case '.inspect':
250+
let deployed = shell.blockchain.getDeployed();
251+
switch (commandParts[1]) {
252+
case 'storage':
253+
254+
function getStorageAt(target, start, num, atBlock) {
255+
256+
atBlock = atBlock || "latest";
257+
start = start || 0;
258+
num = num || 1;
259+
let slots = [...Array(num).keys()].map((idx) => (start + idx))
260+
261+
return slots.map(slot => shell.blockchain.web3.eth.getStorageAt(target, slot));
262+
}
263+
let start = commandParts.length > 2 ? parseInt(commandParts[2]) : 0;
264+
let num = commandParts.length > 3 ? parseInt(commandParts[3]) : 10;
265+
266+
if (isNaN(start) || isNaN(num)) {
267+
console.error("start and num must be numbers!")
268+
break;
269+
}
270+
271+
var target;
272+
if (commandParts.length > 4) {
273+
target = commandParts[4].trim().toLowerCase();
274+
} else if (deployed) {
275+
target = deployed.instance.options.address;
276+
} else {
277+
console.error("not yet ready. execute repl command first.");
278+
break;
279+
}
280+
281+
Promise.all(getStorageAt(target, start, num))
282+
.then(r => {
283+
284+
let head = `
285+
📚 Contract: ${target} @ latest block
286+
287+
slot 1f 1e 1d 1c 1b 1a 19 18 17 16 15 14 13 12 11 10 0f 0e 0d 0c 0b 0a 09 08 07 06 05 04 03 02 01 00
288+
--------------------------------------------------------------------------------------------------------------------
289+
`;
290+
291+
let lines = r.map((v, i) => {
292+
let datawideBytes = v.replace("0x", "").padStart(256 / 8 * 2, '0').match(/.{2}/g);
293+
let strline = datawideBytes.map(b => {
294+
let bint = parseInt(b, 16);
295+
if (bint >= 32 && bint <= 126) {
296+
return String.fromCharCode(bint);
297+
} else {
298+
return '.';
299+
}
300+
}).join("");
301+
return ` 0x${(start + i).toString(16).padStart(6, '0')} (${(start + i).toString().padStart(4, ' ')}) ${datawideBytes.map(b => b == "00" ? b : c.bold(c.bgYellowBright(b))).join(" ")} ${strline}`;
302+
}).join('\n');
303+
304+
console.log(head + lines);
305+
});
306+
307+
break;
308+
case 'bytecode':
309+
deployed && cb(c.yellow(deployed.bytecode));
310+
break;
311+
case 'deployed':
312+
cb(deployed);
313+
break;
314+
case 'storageLayout':
315+
deployed && cb(deployed.storageLayout);
316+
break;
317+
case 'opcodes':
318+
deployed && cb(deployed.opcodes);
319+
break;
320+
}
321+
break;
242322
case '.fetch':
243323
if (commandParts.length < 4) {
244324
cb("Invalid params: .fetch interface <address> <name> [chain=mainnet] ... fetch and load an interface declaration from an ABI spec on etherscan.io")
@@ -310,7 +390,7 @@ vorpal
310390
.mode('repl', 'Enters Solidity Shell Mode')
311391
.delimiter(c.bold('» '))
312392
.init(function (args, cb) {
313-
this.log(`🚀 Entering interactive Solidity ${c.bold(shell.settings.installedSolidityVersion)} shell (🧁:${c.bold(shell.blockchain.name)}). '${c.bold('.help')}' and '${c.bold('.exit')}' are your friends.`);
393+
this.log(`🚀 Entering interactive Solidity ${c.bold(shell.settings.installedSolidityVersion)} shell (🧁:${c.bold(shell.blockchain.name)}${shell.settings.ganacheOptions && shell.settings.ganacheOptions.fork ? c.bold(', ⇉ fork-mode'):''}). '${c.bold('.help')}' and '${c.bold('.exit')}' are your friends.`);
314394
return cb();
315395
})
316396
.action(handleRepl);
@@ -347,6 +427,9 @@ vorpal
347427
vorpal
348428
.command(".fetch")
349429
.autocomplete(["interface"])
430+
vorpal
431+
.command(".inspect")
432+
.autocomplete(["bytecode", "opcodes", "storage", "storageLayout", "deployed"])
350433
vorpal
351434
.command(".echo <msg>")
352435
vorpal

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "solidity-shell",
3-
"version": "0.2.1",
3+
"version": "0.2.2",
44
"description": "An interactive Solidity shell with lightweight session recording and remote compiler support",
55
"main": "src/index.js",
66
"bin": {

src/blockchain.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,10 @@ class AbsBlockchainBase {
8888
return resolve(result);
8989
});
9090
});
91+
}
9192

93+
getDeployed(){
94+
return this.deployed[this.shell.settings.templateContractName];
9295
}
9396

9497
async deploy(contracts, callback) {
@@ -100,10 +103,12 @@ class AbsBlockchainBase {
100103

101104
let thisContract = {
102105
bytecode: o.evm.bytecode.object,
106+
opcodes: o.evm.bytecode.opcodes,
103107
abi: o.abi,
104108
proxy: new this.web3.eth.Contract(o.abi, null),
105109
instance: undefined,
106110
main: o.main,
111+
storageLayout: o.storageLayout,
107112
accounts: undefined
108113
}
109114

@@ -112,10 +117,10 @@ class AbsBlockchainBase {
112117
.then(accounts => {
113118
thisContract.accounts = accounts;
114119
let instance = thisContract.proxy.deploy({ data: thisContract.bytecode }).send({ from: accounts[0], gas: this.shell.settings.deployGas })
115-
thisContract.instance = instance;
116120
return instance;
117121
})
118122
.then(contract => {
123+
thisContract.instance = contract;
119124
if (thisContract.main) {
120125
contract.methods[thisContract.main]().call({ from: thisContract.accounts[0], gas: this.shell.settings.callGas }, callback);
121126
}

src/handler.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ contract ${this.settings.templateContractName} {
300300
},
301301
},
302302
}
303-
input.settings.outputSelection['*']['*'] = ['abi', 'evm.bytecode']
303+
input.settings.outputSelection['*']['*'] = ['abi', 'evm.bytecode', 'storageLayout']
304304

305305
const callbacks = {
306306
'import': (sourcePath) => readFileCallback(

0 commit comments

Comments
 (0)