Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 0 additions & 18 deletions src/firewall/integration/integration.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { resolve } from 'path';
import { StandaloneCommand } from '@/commands/standalone-command.decorator';
import { DESCRIPTION, FULL_NAME, NAME } from '@/firewall/integration/integration.command.descriptor';
import { IntegrationService } from '@/firewall/integration/integration.service';
import type { FirewallModifier } from '@/firewall/integration/integration.utils';
import { FrameworkService } from '@/framework/framework.service';
import { LoggerService } from '@/lib/logging/logger.service';

Expand All @@ -18,7 +17,6 @@ interface CommandOptions {
verbose?: boolean;
internal?: boolean;
msgValue?: boolean;
modifiers?: FirewallModifier[];
}

@SubCommand({
Expand Down Expand Up @@ -57,7 +55,6 @@ export class IntegrationCommand extends CommandRunner {
external: true,
internal: options?.internal,
msgValue: options?.msgValue,
modifiers: options?.modifiers,
};

if (options?.file) {
Expand Down Expand Up @@ -138,19 +135,4 @@ export class IntegrationCommand extends CommandRunner {
parseMsgValue(): boolean {
return true;
}

@Option({
flags: '-m, --modifiers <string...>',
description: 'set advanced modifiers',
})
parseModifiers(val: string): FirewallModifier[] {
const ACCEPTED_MODIFIERS = ['invariantProtected'];
const thisOption = this.getCommandOption('modifiers');
// This is a hotfix.
// NestJS commander overriding "parseArg" immediately after setting it via the decorator.
thisOption.choices(ACCEPTED_MODIFIERS);
// @ts-expect-error because of the hotfix above
const previous = this.command._optionValues['modifiers'];
return thisOption.parseArg(val, previous);
}
}
106 changes: 5 additions & 101 deletions src/firewall/integration/integration.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ import {
ModifierInvocation,
PragmaDirective,
SourceUnit,
TypeName,
} from '@solidity-parser/parser/dist/src/ast-types';
import { ethers } from 'ethers';
import { readdir, readFile, stat, writeFile } from 'fs/promises';
import { any as pathMatch } from 'micromatch';
import { InquirerService } from 'nest-commander';
Expand All @@ -29,16 +27,6 @@ const FW_CONTRACT = 'VennFirewallConsumer';
const FW_IMPORT = `import {${FW_CONTRACT}} from "${FW_IMPORT_PATH}";`;

const FW_PROTECTED_MODIFIER = 'firewallProtected';
const FW_PROTECTED_CUSTOM_MODIFIER = 'firewallProtectedCustom';
const FW_PROTECTED_SIG_MODIFIER = 'firewallProtectedSig';
const FW_INVARIANT_PROTECTED_MODIFIER = 'invariantProtected';

const FIREWALL_MODIFIERS = [
FW_PROTECTED_MODIFIER,
FW_PROTECTED_CUSTOM_MODIFIER,
FW_PROTECTED_SIG_MODIFIER,
FW_INVARIANT_PROTECTED_MODIFIER,
] as const;

const FW_STORAGE_SLOT = 'bytes32(uint256(keccak256("eip1967.firewall")) - 1)';
const FW_ADMIN_STORAGE_SLOT = 'bytes32(uint256(keccak256("eip1967.firewall.admin")) - 1)';
Expand All @@ -50,14 +38,11 @@ const FW_PROXY_SETUP = `_setAddressBySlot(${FW_STORAGE_SLOT}, address(0));`;
const FW_PROXY_ADMIN_SETUP = () => `_setAddressBySlot(${FW_ADMIN_STORAGE_SLOT}, ${MSG_SENDER});`;
const FW_PROXY_FULL_SETUP = () => `\n\t\t${FW_PROXY_SETUP}\n\t\t${FW_PROXY_ADMIN_SETUP()}`;

export type FirewallModifier = (typeof FIREWALL_MODIFIERS)[number];

export interface IntegrateOptions {
verbose?: boolean;
external?: boolean;
internal?: boolean;
msgValue?: boolean;
modifiers?: FirewallModifier[];
}

export const SUPPORTED_SOLIDITY_VERSIONS = '>= 0.8';
Expand Down Expand Up @@ -101,7 +86,6 @@ const RE_CONTRACT_DEFINITION = new RegExp(`^${RE_CONTRACT_DECLARATION.source}${R
*/
const RE_FUNCTION = new RegExp(`(?<func>function)`, 'g');
const RE_PARAMS = new RegExp(`(?<params>${RE_BLANK_SPACE.source}*[\\w,\\.\\[\\]]+(?:${RE_BLANK_SPACE.source}*))*`, 'g');
const RE_ARGS = new RegExp(`(?:${RE_BLANK_SPACE.source}*[\\w,\\.\\(\\)\\[\\]]+(?:${RE_BLANK_SPACE.source}*))*`, 'g');
const RE_SIGNATURE = new RegExp(
`(?<signature>${RE_BLANK_SPACE.source}+(?<name>${RE_NAME.source})${RE_BLANK_SPACE.source}*\\(${RE_PARAMS.source}\\))`,
'g',
Expand All @@ -115,41 +99,18 @@ const RE_METHOD_DEFINITION = new RegExp(
'g',
);

/**
* Gradually composing a regex to match the following pattern:
*
* <firewallModifier>(...params)(\s\r\n)?
*/
const RE_FW_MODIFIER_NO_ARGS = new RegExp(`(?:${FIREWALL_MODIFIERS.map(mod => `\\b${mod}\\b`).join('|')})`, 'g');
const RE_FW_MODIFIER_WITH_ARGS = new RegExp(
`${RE_FW_MODIFIER_NO_ARGS.source}(?:${RE_BLANK_SPACE.source}*\\(${RE_ARGS.source}\\))?`,
'g',
);
const RE_FW_MODIFIER = new RegExp(
`${RE_BLANK_SPACE.source}*${RE_FW_MODIFIER_WITH_ARGS.source}(?:${RE_BLANK_SPACE.source}*)?`,
`${RE_BLANK_SPACE.source}*${FW_PROTECTED_MODIFIER}(?:${RE_BLANK_SPACE.source}*)?`,
'g',
);

@Injectable()
export class IntegrationUtils {
private serializerByModifier: Partial<
Record<FirewallModifier, (contract: ContractDefinition, method: FunctionDefinition) => string>
>;

constructor(
private readonly inquirer: InquirerService,
private readonly config: ConfigService,
private readonly logger: LoggerService,
) {
this.serializerByModifier = {
[FW_PROTECTED_MODIFIER]: () => FW_PROTECTED_MODIFIER,
[FW_PROTECTED_SIG_MODIFIER]: (contract: ContractDefinition, method: FunctionDefinition) => {
const sigHash = this.calcSighash(contract, method);
return `${FW_PROTECTED_SIG_MODIFIER}(bytes4(${sigHash}))`;
},
[FW_INVARIANT_PROTECTED_MODIFIER]: () => FW_INVARIANT_PROTECTED_MODIFIER,
};
}
) {}

async assertFileExists(path: string): Promise<void> {
try {
Expand Down Expand Up @@ -422,11 +383,7 @@ export class IntegrationUtils {
options?: IntegrateOptions,
): string {
const isAbstract = !method.body;
const firewallModifiers = (method.modifiers || []).filter(modifier =>
FIREWALL_MODIFIERS.includes(modifier?.name as FirewallModifier),
);
const requiredModifiers = this.getModifiersToAdd(method, options);
const hasMismatchingModifiers = firewallModifiers.length !== requiredModifiers.length;
const hasMismatchingModifiers = method.modifiers?.length !== FW_PROTECTED_MODIFIER.length;
const shouldCustomize = !isAbstract && hasMismatchingModifiers && !!options[method.visibility];
if (!shouldCustomize) {
return methodCode;
Expand All @@ -452,9 +409,7 @@ export class IntegrationUtils {
}

const [indentation] = modifiers.match(RE_INDENTATION) || [' '];
const modifiersToAdd = requiredModifiers
.map(name => this.serializerByModifier[name](contract, method))
.join(indentation);
const modifiersToAdd = FW_PROTECTED_MODIFIER;

if (modifiers) {
// Remove existing firewall modifiers.
Expand Down Expand Up @@ -586,7 +541,7 @@ export class IntegrationUtils {
}

private alreadyCustomizedContractMethod(method: FunctionDefinition): boolean {
return (method.modifiers || []).some(modifier => !!this.serializerByModifier[modifier.name]);
return (method.modifiers || []).some(modifier => modifier.name === FW_PROTECTED_MODIFIER);
}

private proxyModifiersAreDetected(modifiers: ModifierInvocation[]): boolean {
Expand All @@ -601,55 +556,4 @@ export class IntegrationUtils {
modifier.arguments[0].type == 'NumberLiteral'),
);
}

private getModifiersToAdd(method: FunctionDefinition, options?: IntegrateOptions): FirewallModifier[] {
switch (method.visibility) {
case 'external':
if (options?.modifiers?.includes(FW_INVARIANT_PROTECTED_MODIFIER)) {
return [FW_PROTECTED_MODIFIER, FW_INVARIANT_PROTECTED_MODIFIER];
}
return [FW_PROTECTED_MODIFIER];
case 'internal':
return [FW_PROTECTED_SIG_MODIFIER];
default:
return [];
}
}

private calcSighash(contract: ContractDefinition, method: FunctionDefinition): string {
const contractName = contract.name;
const methodName = method.name!;
const paramTypes = (method.parameters || []).map(param => {
try {
return this.getParamTypeName(param.typeName);
} catch (_err) {
throw new UnsupportedParamTypeError(`unsupported type of param "${param.name}"`);
}
});
const sig = `${contractName}.${methodName}(${paramTypes.join(',')})`;
const sigHash = ethers.id(sig).slice(0, 10);
return sigHash;
}

private getParamTypeName(paramType: TypeName): string {
switch (paramType?.type) {
case 'ArrayTypeName':
// eslint-disable-next-line no-case-declarations
const baseTypeName = this.getParamTypeName(paramType?.baseTypeName);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return `${baseTypeName}[${(paramType?.length as any)?.number || ''}]`;
case 'UserDefinedTypeName':
return paramType?.namePath;
case 'ElementaryTypeName':
// eslint-disable-next-line no-case-declarations
const rawTypeName = paramType?.name;
if (rawTypeName === 'int' || rawTypeName === 'uint') {
// Explicit type conversions: int => int256, uint => uint256.
return `${rawTypeName}256`;
}
return rawTypeName;
default:
throw new Error();
}
}
}