sevm
Version:
A Symbolic Ethereum Virtual Machine (EVM) bytecode decompiler & analyzer library & CLI
241 lines (205 loc) • 6.52 kB
text/typescript
import { type IInst, Tag, type Expr, evalE } from '.';
function info(...args: Expr[]): [depth: number, count: number] {
return [
Math.max(...args.map(e => e.depth)) + 1,
(args?.reduce((accum, curr) => accum + curr.count, 0) ?? 0) + 1
];
}
export class Sha3 extends Tag {
readonly tag = 'Sha3';
constructor(readonly offset: Expr, readonly size: Expr, readonly args?: Expr[]) {
super(...info(offset, size, ...args ?? []));
}
eval(): Sha3 {
return new Sha3(this.offset.eval(), this.size.eval(), this.args?.map(evalE));
}
override children(): Expr[] {
return [...super.children(), ...this.args ?? []];
}
}
export class Create extends Tag {
readonly tag = 'Create';
override readonly type = 'address';
/**
* Creates a new account with associated code.
*
* @param value Value in _wei_ to send to the new account.
* @param offset Byte offset in the memory in bytes, the initialisation code for the new account.
* @param size Byte size to copy (size of the initialisation code).
* @param bytecode
*/
constructor(
readonly value: Expr,
readonly offset: Expr,
readonly size: Expr,
readonly bytecode: Uint8Array | null = null
) {
super(...info(value, offset, size));
}
eval(): Expr {
return new Create(this.value.eval(), this.offset.eval(), this.size.eval(), this.bytecode);
}
}
export class Call extends Tag {
readonly tag = 'Call';
throwOnFail = false;
constructor(
readonly gas: Expr,
readonly address: Expr,
readonly value: Expr,
readonly argsStart: Expr,
readonly argsLen: Expr,
readonly retStart: Expr,
readonly retLen: Expr
) {
super(...info(gas, address, value, argsStart, argsLen, retStart, retLen));
}
eval(): Expr {
return this;
}
}
export class ReturnData extends Tag {
readonly tag = 'ReturnData';
override readonly type = 'bytes';
readonly wrapped = false;
constructor(readonly retOffset: Expr, readonly retSize: Expr) {
super(...info(retOffset, retSize));
}
eval(): Expr {
return this;
}
}
export class CallCode extends Tag {
readonly tag = 'CallCode';
constructor(
readonly gas: Expr,
readonly address: Expr,
readonly value: Expr,
readonly memoryStart: Expr,
readonly memoryLength: Expr,
readonly outputStart: Expr,
readonly outputLength: Expr
) {
super(...info(gas, address, value, memoryStart, memoryLength, outputStart, outputLength));
}
eval(): Expr {
return this;
}
}
export class Create2 extends Tag {
readonly tag = 'Create2';
constructor(readonly offset: Expr, readonly size: Expr, readonly value: Expr) {
super(...info(offset, size, value));
}
eval(): Expr {
return this;
}
}
export class StaticCall extends Tag {
readonly tag = 'StaticCall';
constructor(
readonly gas: Expr,
readonly address: Expr,
readonly memoryStart: Expr,
readonly memoryLength: Expr,
readonly outputStart: Expr,
readonly outputLength: Expr
) {
super(...info(gas, address, memoryStart, memoryLength, outputStart, outputLength));
}
eval(): Expr {
return this;
}
}
export class DelegateCall extends Tag {
readonly tag = 'DelegateCall';
constructor(
readonly gas: Expr,
readonly address: Expr,
readonly memoryStart: Expr,
readonly memoryLength: Expr,
readonly outputStart: Expr,
readonly outputLength: Expr
) {
super(...info(gas, address, memoryStart, memoryLength, outputStart, outputLength));
}
eval(): Expr {
return this;
}
}
export class Stop implements IInst {
readonly name = 'Stop';
eval() {
return this;
}
}
export class Return implements IInst {
readonly name = 'Return';
/**
* Exits the current context successfully.
*
* @param offset Byte offset in the memory in bytes, to copy what will be the return data of this context.
* @param size Byte size to copy (size of the return data).
* @param args
*/
constructor(readonly offset: Expr, readonly size: Expr, readonly args?: Expr[]) { }
eval() {
return new Return(this.offset.eval(), this.size.eval(), this.args?.map(evalE));
}
}
/**
*
*/
export interface IReverts {
[selector: string]: {
/**
*
*/
sig?: string;
};
}
export class Revert implements IInst {
readonly name = 'Revert';
static readonly ERROR = '08c379a0';
static readonly PANIC = '4e487b71';
/**
* Stop the current context execution, revert the state changes (see `STATICCALL` for a list
* of state changing opcodes) and return the unused gas to the caller.
*
* It also reverts the gas refund to its value before the current context.
* If the execution is stopped with `REVERT`, the value 0 is put on the stack of the calling context,
* which continues to execute normally.
* The return data of the calling context is set as the given chunk of memory of this context.
*
* @param offset byte offset in the memory in bytes. The return data of the calling context.
* @param size byte size to copy (size of the return data).
* @param args
*/
constructor(readonly offset: Expr, readonly size: Expr, readonly selector?: string, readonly sig?: IReverts[string], readonly args?: Expr[]) { }
eval() {
return new Revert(this.offset.eval(), this.size.eval(), this.selector, this.sig, this.args?.map(evalE));
}
/**
* https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require
*/
static isRequireOrAssert(selector: string | undefined): boolean {
return selector === undefined || selector === Revert.ERROR || selector === Revert.PANIC;
}
isRequireOrAssert(): boolean {
return Revert.isRequireOrAssert(this.selector);
}
}
export class Invalid implements IInst {
readonly name = 'Invalid';
constructor(readonly opcode: number) { }
eval() {
return this;
}
}
export class SelfDestruct implements IInst {
readonly name = 'SelfDestruct';
constructor(readonly address: Expr) { }
eval() {
return this;
}
}