Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix function clash for non-storage arguments #34

Merged
merged 12 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from 10 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## Unreleased

- Fix function clashes when ABI types are not able to disambiguate multiple functions.

## 0.3.14

- Fix import handling to fully consider indirect imports.
Expand Down
31 changes: 31 additions & 0 deletions contracts/Test.sol
Original file line number Diff line number Diff line change
Expand Up @@ -263,3 +263,34 @@ library LibraryHasStruct {

function foo() internal returns (Inner memory) {}
}

library UdvtConflict {
type myFirstType is bytes32;
type mySecondType is bytes32;

function unwrap(myFirstType t) internal pure returns (bytes32) {
return myFirstType.unwrap(t);
}

function unwrap(mySecondType t) internal pure returns (bytes32) {
return mySecondType.unwrap(t);
}
}

library UdvtNoConflict {
type myFirstType is bytes32;
type mySecondType is uint256;

function unwrap(myFirstType t) internal pure returns (bytes32) {
return myFirstType.unwrap(t);
}

function unwrap(mySecondType t) internal pure returns (uint256) {
return mySecondType.unwrap(t);
}
}

contract Conflicts {
function _a(HasEnum) internal {}
function _a(HasReceiveFunction) internal {}
}
51 changes: 51 additions & 0 deletions src/core.test.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,57 @@ Generated by [AVA](https://avajs.dev).
receive() external payable {}␊
}␊
contract $UdvtConflict {␊
bytes32 public constant __hh_exposed_bytecode_marker = "hardhat-exposed";␊
constructor() payable {␊
}␊
function $unwrap_UdvtConflict_myFirstType(UdvtConflict.myFirstType t) external pure returns (bytes32 ret0) {␊
(ret0) = UdvtConflict.unwrap(t);␊
}␊
function $unwrap_UdvtConflict_mySecondType(UdvtConflict.mySecondType t) external pure returns (bytes32 ret0) {␊
(ret0) = UdvtConflict.unwrap(t);␊
}␊
receive() external payable {}␊
}␊
contract $UdvtNoConflict {␊
bytes32 public constant __hh_exposed_bytecode_marker = "hardhat-exposed";␊
constructor() payable {␊
}␊
function $unwrap(UdvtNoConflict.myFirstType t) external pure returns (bytes32 ret0) {␊
(ret0) = UdvtNoConflict.unwrap(t);␊
}␊
function $unwrap(UdvtNoConflict.mySecondType t) external pure returns (uint256 ret0) {␊
(ret0) = UdvtNoConflict.unwrap(t);␊
}␊
receive() external payable {}␊
}␊
contract $Conflicts is Conflicts {␊
bytes32 public constant __hh_exposed_bytecode_marker = "hardhat-exposed";␊
constructor() payable {␊
}␊
function $_a_HasEnum(HasEnum arg0) external {␊
super._a(arg0);␊
}␊
function $_a_HasReceiveFunction(HasReceiveFunction arg0) external {␊
super._a(arg0);␊
}␊
receive() external payable {}␊
}␊
`

## snapshot initializers
Expand Down
Binary file modified src/core.test.ts.snap
Binary file not shown.
85 changes: 72 additions & 13 deletions src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import path from 'path';

import { findAll, astDereferencer, ASTDereferencer } from 'solidity-ast/utils';
import { formatLines, Lines, spaceBetween } from './utils/format-lines';
import type { Visibility, SourceUnit, ContractDefinition, FunctionDefinition, VariableDeclaration, StorageLocation, TypeDescriptions, TypeName, InheritanceSpecifier, ModifierInvocation, FunctionCall } from 'solidity-ast';
import type { Visibility, SourceUnit, ContractDefinition, FunctionDefinition, VariableDeclaration, StorageLocation, TypeName, UserDefinedTypeName } from 'solidity-ast';
import type { FileContent, ProjectPathsConfig, ResolvedFile } from 'hardhat/types';
import type { ExposedConfig, ExposedUserConfig } from './config';
import type { ExposedConfig } from './config';
import assert from 'assert';

export interface SolcOutput {
Expand Down Expand Up @@ -205,7 +205,7 @@ function getExposedContent(ast: SourceUnit, relativizePath: (p: string) => strin
]),
// external functions
...externalizableFunctions.map(fn => {
const fnName = clashingFunctions[getFunctionId(fn, c, deref)] === 1 ? fn.name : getFunctionNameQualified(fn, c, deref);
const fnName = clashingFunctions[getFunctionId(fn, c, deref)] === 1 ? fn.name : getFunctionNameQualified(fn, c, deref, true);
const fnArgs = getFunctionArguments(fn, c, deref);
const fnRets = getFunctionReturnParameters(fn, c, deref);
const evName = isNonViewWithReturns(fn) && (clashingEvents[fn.name] === 1 ? fn.name : getFunctionNameQualified(fn, c, deref, false));
Expand Down Expand Up @@ -282,13 +282,16 @@ function areFunctionsFullyImplemented(contract: ContractDefinition, deref: ASTDe
}

function getFunctionId(fn: FunctionDefinition, context: ContractDefinition, deref: ASTDereferencer): string {
const storageArgs = new Set<Argument>(getStorageArguments(fn, context, deref));
const nonStorageArgs = getFunctionArguments(fn, context, deref).filter(a => !storageArgs.has(a));
return fn.name + nonStorageArgs.map(a => a.type).join('');
const abiTypes = getFunctionArguments(fn, context, deref).map(a => a.abiType);
return fn.name + abiTypes.join(',');
}

function getFunctionNameQualified(fn: FunctionDefinition, context: ContractDefinition, deref: ASTDereferencer, onlyStorage: boolean = true): string {
return fn.name + (onlyStorage ? getStorageArguments(fn, context, deref) : getFunctionArguments(fn, context, deref))
function getFunctionNameQualified(fn: FunctionDefinition, context: ContractDefinition, deref: ASTDereferencer, onlyConflicting: boolean): string {
let args = getFunctionArguments(fn, context, deref);
if (onlyConflicting) {
args = args.filter(a => a.type !== a.abiType || a.storageType !== undefined);
}
return fn.name + args
frangio marked this conversation as resolved.
Show resolved Hide resolved
.map(arg => arg.storageType ?? arg.type)
.map(type => type.replace(/ .*/,'').replace(/[^0-9a-zA-Z$_]+/g, '_')) // sanitize
.join('_')
Expand Down Expand Up @@ -419,7 +422,7 @@ function isTypeExternalizable(typeName: TypeName | null | undefined, deref: ASTD
if (typeName == undefined) {
return true;
} if (typeName.nodeType === 'UserDefinedTypeName') {
frangio marked this conversation as resolved.
Show resolved Hide resolved
const typeDef = deref(['StructDefinition', 'EnumDefinition', 'ContractDefinition', 'UserDefinedValueTypeDefinition'], typeName.referencedDeclaration);
const typeDef = derefUserDefinedTypeName(deref, typeName);
if (typeDef.nodeType !== 'StructDefinition') {
return true;
} else {
Expand All @@ -437,6 +440,7 @@ function isNonViewWithReturns(fnDef: FunctionDefinition): boolean {
interface Argument {
type: string;
name: string;
abiType: string;
storageVar?: string;
storageType?: string;
}
Expand All @@ -450,10 +454,12 @@ function getFunctionArguments(fnDef: FunctionDefinition, context: ContractDefini
const storageType = getVarType(p, context, deref, null);
const storageVar = 'v_' + storageType.replace(/[^0-9a-zA-Z$_]+/g, '_');
// The argument is an index to an array in storage.
return { name, type: 'uint256', storageVar, storageType };
const type = 'uint256';
return { name, type, abiType: type, storageVar, storageType };
} else {
const type = getVarType(p, context, deref, 'calldata');
return { name, type };
const abiType = getVarAbiType(p, context, deref, 'calldata');
return { name, type, abiType };
}
});
}
Expand All @@ -475,7 +481,8 @@ function getFunctionReturnParameters(fnDef: FunctionDefinition, context: Contrac
return fnDef.returnParameters.parameters.map((p, i) => {
const name = p.name || `ret${i}`;
const type = getVarType(p, context, deref, location);
return { name, type };
const abiType = getVarAbiType(p, context, deref, location);
return { name, type, abiType };
});
}

Expand Down Expand Up @@ -508,6 +515,50 @@ function getType(typeName: TypeName, context: ContractDefinition, deref: ASTDere
return type;
}

function getVarAbiType(varDecl: VariableDeclaration, context: ContractDefinition, deref: ASTDereferencer, location: StorageLocation | null = varDecl.storageLocation): string {
if (!varDecl.typeName) {
throw new Error('Missing type information');
}
return getAbiType(varDecl.typeName, context, deref, location);
}

function getAbiType(typeName: TypeName, context: ContractDefinition, deref: ASTDereferencer, location: StorageLocation | null): string {
switch (typeName.nodeType) {
case 'ElementaryTypeName':
case 'ArrayTypeName':
const { typeString } = typeName.typeDescriptions;
assert(typeString != undefined);
return typeString;

case 'UserDefinedTypeName':
const typeDef = derefUserDefinedTypeName(deref, typeName);
switch (typeDef.nodeType) {
case 'UserDefinedValueTypeDefinition':
const { underlyingType } = typeDef;
const { typeString } = underlyingType.typeDescriptions;
frangio marked this conversation as resolved.
Show resolved Hide resolved
assert(typeString != undefined);
return typeString;

case 'EnumDefinition':
assert(typeDef.members.length < 256);
return 'uint8';

case 'ContractDefinition':
return 'address';

case 'StructDefinition':
if (location === 'storage') {
throw new Error('Unexpected error'); // is treated separately in getFunctionArguments
} else {
return '(' + typeDef.members.map(v => getVarAbiType(v, context, deref, location)).join(',') + ')';
}
}

default:
throw new Error('Unknown ABI type');
}
}

function getVariables(contract: ContractDefinition, deref: ASTDereferencer, subset?: Visibility[]): VariableDeclaration[] {
const parents = contract.linearizedBaseContracts.map(deref('ContractDefinition'));

Expand All @@ -530,7 +581,11 @@ function getVarGetterArgs(v: VariableDeclaration, context: ContractDefinition, d
}
const types = [];
for (let t = v.typeName; t.nodeType === 'Mapping'; t = t.valueType) {
types.push({ name: `arg${types.length}`, type: getType(t.keyType, context, deref, 'memory') })
types.push({
name: `arg${types.length}`,
type: getType(t.keyType, context, deref, 'memory'),
abiType: getAbiType(t.keyType, context, deref, 'memory'),
})
}
return types;
}
Expand Down Expand Up @@ -598,3 +653,7 @@ function createNonCryptographicHashBasedIdentifier(input: Buffer): Buffer {
const { createHash } = require("crypto") as typeof import('crypto');
return createHash("md5").update(input).digest();
}

function derefUserDefinedTypeName(deref: ASTDereferencer, typeName: UserDefinedTypeName) {
return deref(['StructDefinition', 'EnumDefinition', 'ContractDefinition', 'UserDefinedValueTypeDefinition'], typeName.referencedDeclaration);
}
Loading