@playmint/lallafa
v1.0.1
Published
Gas profiling tool for Ethereum smart contracts
Downloads
4
Readme
Lallafa
An EVM gas profiling tool written in Typescript.
Installation
npm install --save-dev @playmint/lallafa
How it works
Lallafa produces a gas usage report by ingesting a debug trace, along with some compiler input and output. It outputs profiling data which is collated at both the instruction level, and the line-of-code level.
How to use
To profile a transaction, use profile
:
function profile(trace: DebugTrace, isDeploymentTransaction: boolean, address: string, contracts: ContractInfoMap)
- trace - a debug trace generated by a node using
debug_traceTransaction
- isDeploymentTransaction - is the tx the deployment of the contract, determines which bytecode the profiler will use
- address - address of the contract where the transaction originated, needed for properly profiling re-entrant calls
- contracts - map of
address
=>ContractInfo
type ContractInfo = {
// standard input JSON
input: CompilerInput;
// standard output JSON
output?: CompilerOutput;
// if output is omitted, Lallafa will attempt to compile the contract from input. If you do this
// then solcVersion needs to be defined
solcVersion?: string;
// name of source file which contains the contract definition
sourceName: string;
// name of contract within the source file
contractName: string;
};
profile
returns a Profile
object, which maps contract address to an object containing an instruction profile and a source profile:
type Profile = {
[address: string]: {
instructionsProfile: InstructionProfile[];
sourcesProfile: SourcesProfile;
}
};
type InstructionProfile = {
// gas spent
gas: number;
// bytecode hex string
bytecode: string;
// op, arg if any, jumpdest name if relevant
asm: string;
// program counter
pc: number;
// opcode as string, e.g. ISZERO, PUSH1, etc
op: string;
// id of source this instruction was generated from
sourceId: number;
sourceRangeStart: number;
sourceRangeLength: number;
// line of code where this source range starts (0 being the first line)
sourceLine: number;
};
type SourcesProfile = {
[sourceId: number]: {
name: string;
content: string;
lines: {
// gas spent
gas: number;
text: string;
// instructions generated from this line of code
instructions: InstructionProfile[]
}[];
}
};
You might choose to visualise profiles in a bespoke way, but you can use instructionsProfileToString
and sourcesProfileToString
to quickly do so.
Example
Check out the test project for detailed examples.
Here's a way to profile a simple contract Storage.sol:
//SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
contract Storage {
uint256 _value;
function setValue(uint256 value) public {
_value = value;
}
}
profileStorage.ts:
import hre from "hardhat";
import { Storage__factory } from "../typechain-types";
import fs from "fs";
import { profile, sourcesProfileToString, instructionsProfileToString, ContractInfoMap } from "../../src";
async function main() {
const storageFactory = new Storage__factory((await hre.ethers.getSigners())[0]);
const storage = await storageFactory.deploy();
const tx = await (await storage.setValue(42)).wait();
const debugTrace = await hre.ethers.provider.send("debug_traceTransaction",
[tx.transactionHash, {
"disableStack": true,
"disableMemory": true,
"disableStorage": true
}]);
const buildInfo = await hre.artifacts.getBuildInfo("contracts/Storage.sol:Storage");
if (!buildInfo) {
throw new Error("couldn't find build info");
}
const contracts: ContractInfoMap = {}
contracts[storage.address] = {
input: buildInfo.input,
output: buildInfo.output,
sourceName: "contracts/Storage.sol",
contractName: "Storage"
};
// or if you want Lallafa to compile from input
/*
contracts[storage.address] = {
input: buildInfo.input,
solcVersion: buildInfo.solcLongVersion,
sourceName: "contracts/Storage.sol",
contractName: "Storage"
};
*/
const result = await profile(
debugTrace,
false, // isDeployment
storage.address,
contracts);
fs.writeFileSync("sources_profile.txt", sourcesProfileToString(result));
fs.writeFileSync("instruction_profile.txt", instructionsProfileToString(result));
}
main().catch(e => console.error(e));
This script outputs profiles of both the source code and the asm instructions to show what these both look like. sources_profile.txt:
// 5FBDB2315678AFECB367F032D93F642F64180AA3
============================================
// contracts/Storage.sol
Gas |
---------------------------------------------------------------------------------------------------------------------------------------
97 | //SPDX-License-Identifier: UNLICENSED
0 | pragma solidity ^0.8.0;
0 |
0 | contract Storage {
0 | uint256 _value;
0 |
68 | function setValue(uint256 value) public {
22114 | _value = value;
0 | }
0 | }
0 |
// #utility.yul
Gas |
---------------------------------------------------------------------------------------------------------------------------------------
0 | {
0 |
0 | function allocate_unbounded() -> memPtr {
0 | memPtr := mload(64)
0 | }
0 |
0 | function revert_error_dbdddcbe895c83990c08b3492a0e83918d802a52331272ac6fdb6a7c4aea3b1b() {
0 | revert(0, 0)
0 | }
0 |
0 | function revert_error_c1322bf8034eace5e0b5c7295db60986aa89aae5e0ea0873e4689e076861a5db() {
0 | revert(0, 0)
0 | }
0 |
20 | function cleanup_t_uint256(value) -> cleaned {
8 | cleaned := value
0 | }
0 |
11 | function validator_revert_t_uint256(value) {
38 | if iszero(eq(value, cleanup_t_uint256(value))) { revert(0, 0) }
0 | }
0 |
22 | function abi_decode_t_uint256(offset, end) -> value {
11 | value := calldataload(offset)
18 | validator_revert_t_uint256(value)
0 | }
0 |
22 | function abi_decode_tuple_t_uint256(headStart, dataEnd) -> value0 {
32 | if slt(sub(dataEnd, headStart), 32) { revert_error_dbdddcbe895c83990c08b3492a0e83918d802a52331272ac6fdb6a7c4aea3b1b() }
0 |
2 | {
0 |
3 | let offset := 0
0 |
32 | value0 := abi_decode_t_uint256(add(headStart, offset), dataEnd)
0 | }
0 |
0 | }
0 |
0 | }
0 |
instructions_profile.txt:
// 5FBDB2315678AFECB367F032D93F642F64180AA3
============================================
Gas | PC | Asm | Bytecode
------------------------------------------------------------------------------------------------------------------------
3 | 0 | PUSH1 0x80 | 0x6080
3 | 2 | PUSH1 0x40 | 0x6040
12 | 4 | MSTORE | 0x52
2 | 5 | CALLVALUE | 0x34
3 | 6 | DUP1 | 0x80
3 | 7 | ISZERO | 0x15
3 | 8 | PUSH1 0xF | 0x600F
10 | A | JUMPI [in] | 0x57
0 | B | PUSH1 0x0 | 0x6000
0 | D | DUP1 | 0x80
0 | E | REVERT | 0xFD
1 | F | JUMPDEST | 0x5B
2 | 10 | POP | 0x50
3 | 11 | PUSH1 0x4 | 0x6004
2 | 13 | CALLDATASIZE | 0x36
3 | 14 | LT | 0x10
3 | 15 | PUSH1 0x28 | 0x6028
10 | 17 | JUMPI [in] | 0x57
3 | 18 | PUSH1 0x0 | 0x6000
3 | 1A | CALLDATALOAD | 0x35
3 | 1B | PUSH1 0xE0 | 0x60E0
3 | 1D | SHR | 0x1C
3 | 1E | DUP1 | 0x80
3 | 1F | PUSH4 0x55241077 | 0x6355241077
3 | 24 | EQ | 0x14
3 | 25 | PUSH1 0x2D [setValue_uint256_0] | 0x602D
10 | 27 | JUMPI [in] | 0x57
0 | 28 | JUMPDEST | 0x5B
0 | 29 | PUSH1 0x0 | 0x6000
0 | 2B | DUP1 | 0x80
0 | 2C | REVERT | 0xFD
1 | 2D | JUMPDEST [setValue_uint256_0] | 0x5B
3 | 2E | PUSH1 0x43 | 0x6043
3 | 30 | PUSH1 0x4 | 0x6004
3 | 32 | DUP1 | 0x80
2 | 33 | CALLDATASIZE | 0x36
3 | 34 | SUB | 0x03
3 | 35 | DUP2 | 0x81
3 | 36 | ADD | 0x01
3 | 37 | SWAP1 | 0x90
3 | 38 | PUSH1 0x3F | 0x603F
3 | 3A | SWAP2 | 0x91
3 | 3B | SWAP1 | 0x90
3 | 3C | PUSH1 0x85 [abi_decode_tuple_t_uint256_0] | 0x6085
8 | 3E | JUMP [in] | 0x56
1 | 3F | JUMPDEST [setValue_uint256_1] | 0x5B
3 | 40 | PUSH1 0x45 [setValue_uint256_3] | 0x6045
8 | 42 | JUMP [in] | 0x56
1 | 43 | JUMPDEST [setValue_uint256_2] | 0x5B
0 | 44 | STOP | 0x00
1 | 45 | JUMPDEST [setValue_uint256_3] | 0x5B
3 | 46 | DUP1 | 0x80
3 | 47 | PUSH1 0x0 | 0x6000
3 | 49 | DUP2 | 0x81
3 | 4A | SWAP1 | 0x90
22100 | 4B | SSTORE | 0x55
2 | 4C | POP | 0x50
2 | 4D | POP | 0x50
8 | 4E | JUMP [out] | 0x56
0 | 4F | JUMPDEST [revert_error_dbdddcbe895c83990c08b3492a0e83918d802a52331272ac6fdb6a7c4aea3b1b_0] | 0x5B
0 | 50 | PUSH1 0x0 | 0x6000
0 | 52 | DUP1 | 0x80
0 | 53 | REVERT | 0xFD
1 | 54 | JUMPDEST [cleanup_t_uint256_0] | 0x5B
3 | 55 | PUSH1 0x0 | 0x6000
3 | 57 | DUP2 | 0x81
3 | 58 | SWAP1 | 0x90
2 | 59 | POP | 0x50
3 | 5A | SWAP2 | 0x91
3 | 5B | SWAP1 | 0x90
2 | 5C | POP | 0x50
8 | 5D | JUMP [out] | 0x56
1 | 5E | JUMPDEST [validator_revert_t_uint256_0] | 0x5B
3 | 5F | PUSH1 0x65 | 0x6065
3 | 61 | DUP2 | 0x81
3 | 62 | PUSH1 0x54 [cleanup_t_uint256_0] | 0x6054
8 | 64 | JUMP [in] | 0x56
1 | 65 | JUMPDEST [validator_revert_t_uint256_1] | 0x5B
3 | 66 | DUP2 | 0x81
3 | 67 | EQ | 0x14
3 | 68 | PUSH1 0x6F [validator_revert_t_uint256_2] | 0x606F
10 | 6A | JUMPI [in] | 0x57
0 | 6B | PUSH1 0x0 | 0x6000
0 | 6D | DUP1 | 0x80
0 | 6E | REVERT | 0xFD
1 | 6F | JUMPDEST [validator_revert_t_uint256_2] | 0x5B
2 | 70 | POP | 0x50
8 | 71 | JUMP [out] | 0x56
1 | 72 | JUMPDEST [abi_decode_t_uint256_0] | 0x5B
3 | 73 | PUSH1 0x0 | 0x6000
3 | 75 | DUP2 | 0x81
3 | 76 | CALLDATALOAD | 0x35
3 | 77 | SWAP1 | 0x90
2 | 78 | POP | 0x50
3 | 79 | PUSH1 0x7F | 0x607F
3 | 7B | DUP2 | 0x81
3 | 7C | PUSH1 0x5E [validator_revert_t_uint256_0] | 0x605E
8 | 7E | JUMP [in] | 0x56
1 | 7F | JUMPDEST [abi_decode_t_uint256_1] | 0x5B
3 | 80 | SWAP3 | 0x92
3 | 81 | SWAP2 | 0x91
2 | 82 | POP | 0x50
2 | 83 | POP | 0x50
8 | 84 | JUMP [out] | 0x56
1 | 85 | JUMPDEST [abi_decode_tuple_t_uint256_0] | 0x5B
3 | 86 | PUSH1 0x0 | 0x6000
3 | 88 | PUSH1 0x20 | 0x6020
3 | 8A | DUP3 | 0x82
3 | 8B | DUP5 | 0x84
3 | 8C | SUB | 0x03
3 | 8D | SLT | 0x12
3 | 8E | ISZERO | 0x15
3 | 8F | PUSH1 0x98 [abi_decode_tuple_t_uint256_2] | 0x6098
10 | 91 | JUMPI [in] | 0x57
0 | 92 | PUSH1 0x97 | 0x6097
0 | 94 | PUSH1 0x4F [revert_error_dbdddcbe895c83990c08b3492a0e83918d802a52331272ac6fdb6a7c4aea3b1b_0] | 0x604F
0 | 96 | JUMP [in] | 0x56
0 | 97 | JUMPDEST [abi_decode_tuple_t_uint256_1] | 0x5B
1 | 98 | JUMPDEST [abi_decode_tuple_t_uint256_2] | 0x5B
3 | 99 | PUSH1 0x0 | 0x6000
3 | 9B | PUSH1 0xA4 | 0x60A4
3 | 9D | DUP5 | 0x84
3 | 9E | DUP3 | 0x82
3 | 9F | DUP6 | 0x85
3 | A0 | ADD | 0x01
3 | A1 | PUSH1 0x72 [abi_decode_t_uint256_0] | 0x6072
8 | A3 | JUMP [in] | 0x56
1 | A4 | JUMPDEST [abi_decode_tuple_t_uint256_3] | 0x5B
3 | A5 | SWAP2 | 0x91
2 | A6 | POP | 0x50
2 | A7 | POP | 0x50
3 | A8 | SWAP3 | 0x92
3 | A9 | SWAP2 | 0x91
2 | AA | POP | 0x50
2 | AB | POP | 0x50
8 | AC | JUMP [out] | 0x56