Skip to content

Commit

Permalink
Verification flow verification classes (#1879)
Browse files Browse the repository at this point in the history
* implement Verifcation.ts

* Refactor Verification.ts

* Add tests

* Add more tests

* Add comment to tests

* Add Vyper tests in Verification

* refactor Verification class

* handle ContructorTransformation

* add comment

* Refactor runtime bytecode matching and add call protection transformation

* Refactor Verification class to use getter methods and improve encapsulation

* Refactor AbstractCompilation to use protected properties and getter methods

* Move check transformation functions into Verification class
Refactor project structure and import paths in lib-sourcify

* restore old `verification.ts` tests

* fix constructor arguments trasformantion tests

* restore old `types.ts`

* add VerificationError class

* Implement Vyper constructor argument transformation test

* Pass forceEmscripten option to compilation call

* Split library map into runtime and creation maps

* replace expectMatch with expectVerification

* fix "should verify a contract with viaIR:true, optimizer disabled, and compiler <0.8.21" test

* Add test for library verification with call protection transformation

* fix call protection test

* increase coverage

* add missing tests from verification.spec.ts increasing coverage

* Refactor bytecode transformations and verification logic
- Extracted transformation functions from the Verification class to a separate module
- Added a new SolidityBugType enum to handle specific compilation scenarios
- Improved error handling for RPC unavailability

* fix linting

* Refactor error handling and type definitions in Sourcify library

* Update error handling for bytecode fetching in Sourcify verification

* Remove unnecessary compilationTarget deletion in SolidityCompilation

* Refactor bytecode matching method signature and remove unused context interface

* Improve bytecode matching readability with descriptive variable names

* Remove unused getter abiEncodedConstructorArguments in Verification class

* Refactor Solidity settings type and improve bug handling in verification

* Update SolidityBugType and improve error handling in verification tests

* Renamed functions in Transformations.ts to use 'extract' prefix instead of 'checkAndCreate'

* Simplify extra file input bug detection and remove redundant error handling

* Refactor Solidity metadata and compiler settings types

* fixes for PR comments

* Validate the bytecode length for Solidity and Vyper compilations before proceeding with bytecode matching

* do not use existing transformations/values in `matchBytecode`.

* Fix extra-file-input-bug at bytecode mismatch error

* fix `should return null match when there is no perfect match and no auxdata`

* fix "maliciously verify with creation bytecode that startsWith the creatorTx input" test

* fixes after PR review

* Improve source file reading and verification error handling

- Update source file reading to support nested directory structures
- Clarify error message for bytecode matching with no auxdata
- Improve test description for bytecode matching scenario

---------

Co-authored-by: Manuel Wedler <[email protected]>
  • Loading branch information
marcocastignoli and manuelwedler authored Feb 26, 2025
1 parent 02b208a commit 337fd91
Show file tree
Hide file tree
Showing 157 changed files with 22,277 additions and 168 deletions.
47 changes: 33 additions & 14 deletions packages/lib-sourcify/src/Compilation/AbstractCompilation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
CompilationTarget,
CompiledContractCborAuxdata,
Metadata,
LinkReferences,
} from './CompilationTypes';
import {
ImmutableReferences,
Expand All @@ -17,7 +18,7 @@ import {
VyperOutput,
VyperOutputContract,
} from './VyperTypes';
import { logInfo, logSilly, logWarn } from '../lib/logger';
import { logInfo, logSilly, logWarn } from '../logger';

export abstract class AbstractCompilation {
/**
Expand All @@ -28,14 +29,14 @@ export abstract class AbstractCompilation {
abstract compilationTarget: CompilationTarget;
abstract jsonInput: SolidityJsonInput | VyperJsonInput;

metadata?: Metadata;
protected _metadata?: Metadata;
compilerOutput?: SolidityOutput | VyperOutput;

abstract auxdataStyle: AuxdataStyle;

/** Marks the positions of the CborAuxdata parts in the bytecode */
creationBytecodeCborAuxdata?: CompiledContractCborAuxdata;
runtimeBytecodeCborAuxdata?: CompiledContractCborAuxdata;
protected _creationBytecodeCborAuxdata?: CompiledContractCborAuxdata;
protected _runtimeBytecodeCborAuxdata?: CompiledContractCborAuxdata;

/**
* Recompiles the contract with the specified compiler settings
Expand Down Expand Up @@ -73,7 +74,7 @@ export abstract class AbstractCompilation {
}

// We call getCompilationTarget() before logging because it can throw an error
const compilationTarget = this.getCompilationTarget();
const compilationTarget = this.compilationTargetContract;

const compilationEndTime = Date.now();
const compilationDuration = compilationEndTime - compilationStartTime;
Expand All @@ -89,7 +90,9 @@ export abstract class AbstractCompilation {
return compilationTarget;
}

getCompilationTarget(): SolidityOutputContract | VyperOutputContract {
get compilationTargetContract():
| SolidityOutputContract
| VyperOutputContract {
if (!this.compilerOutput) {
logWarn('Compiler output is undefined');
throw new Error('Compiler output is undefined');
Expand All @@ -110,20 +113,36 @@ export abstract class AbstractCompilation {
];
}

getCreationBytecode() {
return `0x${this.getCompilationTarget().evm.bytecode.object}`;
get creationBytecode() {
return `0x${this.compilationTargetContract.evm.bytecode.object}`;
}

getRuntimeBytecode() {
return `0x${this.getCompilationTarget().evm.deployedBytecode.object}`;
get runtimeBytecode() {
return `0x${this.compilationTargetContract.evm.deployedBytecode.object}`;
}

getMetadata(): Metadata {
if (!this.metadata) {
get metadata() {
if (!this._metadata) {
throw new Error('Metadata is not set');
}
return this.metadata;
return this._metadata;
}

abstract getImmutableReferences(): ImmutableReferences;
abstract get immutableReferences(): ImmutableReferences;
abstract get runtimeLinkReferences(): LinkReferences;
abstract get creationLinkReferences(): LinkReferences;

get creationBytecodeCborAuxdata(): CompiledContractCborAuxdata {
if (!this._creationBytecodeCborAuxdata) {
throw new Error('Creation bytecode cbor auxdata is not set');
}
return this._creationBytecodeCborAuxdata;
}

get runtimeBytecodeCborAuxdata(): CompiledContractCborAuxdata {
if (!this._runtimeBytecodeCborAuxdata) {
throw new Error('Runtime bytecode cbor auxdata is not set');
}
return this._runtimeBytecodeCborAuxdata;
}
}
48 changes: 14 additions & 34 deletions packages/lib-sourcify/src/Compilation/CompilationTypes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Abi } from 'abitype';
import { SoliditySettings } from './SolidityTypes';

export interface LinkReferences {
[filePath: string]: {
Expand Down Expand Up @@ -75,6 +76,18 @@ export interface MetadataOutput {
userdoc: Userdoc;
}

// Metadata JSON's "settings" does have extra "compilationTarget" and its "libraries" field is in a different format
// ( libraries["MyContract.sol:Mycontract"]:"0xab..cd" vs libraries["MyContract.sol"]["MyContract"]:"0xab..cd")
export interface MetadataCompilerSettings
extends Omit<SoliditySettings, 'libraries' | 'outputSelection'> {
compilationTarget: {
[sourceName: string]: string;
};
libraries?: {
[index: string]: string;
};
}

// Metadata type that reflects the metadata object from
// https://docs.soliditylang.org/en/latest/metadata.html
export interface Metadata {
Expand All @@ -84,40 +97,7 @@ export interface Metadata {
};
language: string;
output: MetadataOutput;
settings: {
compilationTarget: {
[sourceName: string]: string;
};
evmVersion?: string;
libraries?: {
[index: string]: string;
};
metadata?: {
appendCBOR?: boolean;
bytecodeHash?: 'none' | 'ipfs' | 'bzzr0' | 'bzzr1';
useLiteralContent?: boolean;
};
optimizer?: {
details?: {
constantOptimizer?: boolean;
cse?: boolean;
deduplicate?: boolean;
inliner?: boolean;
jumpdestRemover?: boolean;
orderLiterals?: boolean;
peephole?: boolean;
yul?: boolean;
yulDetails?: {
optimizerSteps?: string;
stackAllocation?: boolean;
};
};
enabled: boolean;
runs: number;
};
viaIR?: boolean;
outputSelection?: any;
};
settings: MetadataCompilerSettings;
sources: MetadataSourceMap;
version: number;
}
Expand Down
57 changes: 35 additions & 22 deletions packages/lib-sourcify/src/Compilation/SolidityCompilation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
SolidityOutput,
SolidityOutputContract,
} from './SolidityTypes';
import { CompilationTarget } from './CompilationTypes';
import { CompilationTarget, LinkReferences } from './CompilationTypes';
import {
findAuxdataPositions,
findAuxdatasInLegacyAssembly,
Expand All @@ -22,7 +22,6 @@ export class SolidityCompilation extends AbstractCompilation {
declare compileAndReturnCompilationTarget: (
forceEmscripten: boolean,
) => Promise<SolidityOutputContract>;
declare getCompilationTarget: () => SolidityOutputContract;

// Specify the auxdata style, used for extracting the auxdata from the compiler output
readonly auxdataStyle: AuxdataStyle.SOLIDITY = AuxdataStyle.SOLIDITY;
Expand Down Expand Up @@ -91,31 +90,32 @@ export class SolidityCompilation extends AbstractCompilation {
public async generateCborAuxdataPositions(forceEmscripten = false) {
// Auxdata array extracted from the compiler's `legacyAssembly` field
const auxdatasFromCompilerOutput = findAuxdatasInLegacyAssembly(
this.getCompilationTarget().evm.legacyAssembly,
(this.compilationTargetContract as SolidityOutputContract).evm
.legacyAssembly,
);

// Case: there is not auxadata
if (auxdatasFromCompilerOutput.length === 0) {
this.creationBytecodeCborAuxdata = {};
this.runtimeBytecodeCborAuxdata = {};
this._creationBytecodeCborAuxdata = {};
this._runtimeBytecodeCborAuxdata = {};
return true;
}

// Case: there is only one auxdata, no need to recompile
// Case: there is only one auxdata, no need to recompile if we find both runtime and creation auxdata at the end of the bytecode (creation auxdata can be in a different place)
if (auxdatasFromCompilerOutput.length === 1) {
// Extract the auxdata from the end of the recompiled runtime bytecode
const [, runtimeAuxdataCbor, runtimeCborLengthHex] = splitAuxdata(
this.getRuntimeBytecode(),
this.runtimeBytecode,
this.auxdataStyle,
);

const auxdataFromRawRuntimeBytecode = `${runtimeAuxdataCbor}${runtimeCborLengthHex}`;

// we divide by 2 because we store the length in bytes (without 0x)
this.runtimeBytecodeCborAuxdata = {
this._runtimeBytecodeCborAuxdata = {
'1': {
offset:
this.getRuntimeBytecode().substring(2).length / 2 -
this.runtimeBytecode.substring(2).length / 2 -
parseInt(runtimeCborLengthHex, 16) -
2, // bytecode has 2 bytes of cbor length prefix at the end
value: `0x${auxdataFromRawRuntimeBytecode}`,
Expand All @@ -124,18 +124,18 @@ export class SolidityCompilation extends AbstractCompilation {

// Try to extract the auxdata from the end of the recompiled creation bytecode
const [, creationAuxdataCbor, creationCborLengthHex] = splitAuxdata(
this.getCreationBytecode(),
this.creationBytecode,
this.auxdataStyle,
);

// If we can find the auxdata at the end of the bytecode return; otherwise continue with `generateEditedContract`
if (creationAuxdataCbor) {
const auxdataFromRawCreationBytecode = `${creationAuxdataCbor}${creationCborLengthHex}`;
// we divide by 2 because we store the length in bytes (without 0x)
this.creationBytecodeCborAuxdata = {
this._creationBytecodeCborAuxdata = {
'1': {
offset:
this.getCreationBytecode().substring(2).length / 2 -
this.creationBytecode.substring(2).length / 2 -
parseInt(creationCborLengthHex, 16) -
2, // bytecode has 2 bytes of cbor length prefix at the end
value: `0x${auxdataFromRawCreationBytecode}`,
Expand All @@ -145,7 +145,7 @@ export class SolidityCompilation extends AbstractCompilation {
}
}

// Case: multiple auxdatas or failing creation auxdata,
// Case: multiple auxdatas or creation auxdata not found at the end of the bytecode,
// we need to recompile with a slightly edited file to check the differences
const editedContractCompilerOutput = await this.generateEditedContract({
version: this.compilerVersion,
Expand All @@ -160,19 +160,19 @@ export class SolidityCompilation extends AbstractCompilation {
const editedContractAuxdatasFromCompilerOutput =
findAuxdatasInLegacyAssembly(editedContract.evm.legacyAssembly);

// Potentially we already found runtimeBytecodeCborAuxdata in the case of failing creation auxdata
// Potentially we already found runtimeBytecodeCborAuxdata in the case of creation auxdata not found at the end of the bytecode
// so no need to call `findAuxdataPositions`
if (this.runtimeBytecodeCborAuxdata === undefined) {
this.runtimeBytecodeCborAuxdata = findAuxdataPositions(
this.getRuntimeBytecode(),
if (this._runtimeBytecodeCborAuxdata === undefined) {
this._runtimeBytecodeCborAuxdata = findAuxdataPositions(
this.runtimeBytecode,
`0x${editedContract.evm.deployedBytecode.object}`,
auxdatasFromCompilerOutput,
editedContractAuxdatasFromCompilerOutput,
);
}

this.creationBytecodeCborAuxdata = findAuxdataPositions(
this.getCreationBytecode(),
this._creationBytecodeCborAuxdata = findAuxdataPositions(
this.creationBytecode,
`0x${editedContract.evm.bytecode.object}`,
auxdatasFromCompilerOutput,
editedContractAuxdatasFromCompilerOutput,
Expand All @@ -184,11 +184,24 @@ export class SolidityCompilation extends AbstractCompilation {
public async compile(forceEmscripten = false) {
const contract =
await this.compileAndReturnCompilationTarget(forceEmscripten);
this.metadata = JSON.parse(contract.metadata.trim());
this._metadata = JSON.parse(contract.metadata.trim());
}

getImmutableReferences(): ImmutableReferences {
const compilationTarget = this.getCompilationTarget();
get immutableReferences(): ImmutableReferences {
const compilationTarget = this
.compilationTargetContract as SolidityOutputContract;
return compilationTarget.evm.deployedBytecode.immutableReferences || {};
}

get runtimeLinkReferences(): LinkReferences {
const compilationTarget = this
.compilationTargetContract as SolidityOutputContract;
return compilationTarget.evm.deployedBytecode.linkReferences || {};
}

get creationLinkReferences(): LinkReferences {
const compilationTarget = this
.compilationTargetContract as SolidityOutputContract;
return compilationTarget.evm.bytecode.linkReferences || {};
}
}
5 changes: 2 additions & 3 deletions packages/lib-sourcify/src/Compilation/SolidityTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ interface ModelChecker {
timeout?: number;
}

interface Settings {
export interface SoliditySettings {
stopAfter?: string;
remappings?: string[];
optimizer?: Optimizer;
Expand All @@ -89,13 +89,12 @@ interface Settings {
libraries?: Libraries;
outputSelection: OutputSelection;
modelChecker?: ModelChecker;
compilationTarget?: string;
}

export interface SolidityJsonInput {
language: string;
sources: Sources;
settings: Settings;
settings: SoliditySettings;
}

interface SolidityOutputError {
Expand Down
Loading

0 comments on commit 337fd91

Please sign in to comment.