Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c4ac4cc
Implement partial transpilation
Amxx Sep 19, 2023
06f00f1
missing newlines
Amxx Sep 19, 2023
8064ec1
use getData to mark object that are not transpiled
Amxx Sep 19, 2023
890d8de
remove non-transpiled objects from files that are transpiled
Amxx Sep 19, 2023
48d3f61
fix
Amxx Sep 20, 2023
71e16a9
lint
Amxx Sep 20, 2023
f6cd41a
remove unecessary eslint-disable
Amxx Sep 20, 2023
4b19e44
declare TransformData.importPath in the fix-import-directives file
Amxx Sep 20, 2023
bfcb1e1
fix bug when a.local === undefined
Amxx Sep 20, 2023
9b831ea
refactor import for Initializable
Amxx Sep 20, 2023
46c62ab
update test cache
Amxx Sep 20, 2023
d216e47
change linting of the produced solidity code
Amxx Sep 20, 2023
904500b
Apply suggestions from code review
Amxx Sep 21, 2023
d4faaf8
Update src/transformations/rename-identifiers.ts
Amxx Sep 21, 2023
7f53af6
Rename importPath to importFromPeer
Amxx Sep 21, 2023
94c1892
move partial transpilation form class Transform to function transpile
Amxx Sep 21, 2023
8d5a339
add helper function excludeAndImportPathsForPeer
Amxx Sep 21, 2023
1bc7930
Update index.ts
Amxx Sep 21, 2023
2b95625
change exclude return
Amxx Sep 23, 2023
b12b69d
renames
frangio Sep 25, 2023
7d7cb3f
use for..of
frangio Sep 25, 2023
fd1302d
move variable definition inside
frangio Sep 25, 2023
0a3b6cf
Apply suggestions from code review
Amxx Sep 25, 2023
30ab602
resolve any imported node
Amxx Sep 25, 2023
be412df
lint
Amxx Sep 25, 2023
b806261
Switch node type
Amxx Sep 25, 2023
13ddd81
peer import constant variable declarations
Amxx Sep 25, 2023
e2456ab
Update peer-import.ts
Amxx Sep 25, 2023
3037c78
explicit comparaison against undefined
Amxx Sep 25, 2023
eb9a435
minimize change
Amxx Sep 25, 2023
d4a70dd
Update src/transformations/peer-import.ts
Amxx Sep 25, 2023
1f7c353
add assertion
frangio Sep 25, 2023
ea04008
add struct to test
frangio Sep 25, 2023
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
7 changes: 7 additions & 0 deletions contracts/project/ISomeInterface.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6;

interface ISomeInterface {
function someFunction() external returns (bool);
function someOtherFunction() external returns (bool);
}
21 changes: 21 additions & 0 deletions contracts/project/SomeContract.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6;

import { ISomeInterface } from "./ISomeInterface.sol";
import { SomeLibrary } from "./SomeLibrary.sol";

interface ISomeContract is ISomeInterface {}

contract SomeContract is ISomeContract {
function someFunction() public override returns (bool) {
return false;
}

function someOtherFunction() public override returns (bool) {
return true;
}

function test(ISomeInterface other) public returns (bool) {
return SomeLibrary.bothFunctions(this) && SomeLibrary.bothFunctions(other);
}
}
10 changes: 10 additions & 0 deletions contracts/project/SomeLibrary.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6;

import { ISomeInterface } from "./ISomeInterface.sol";

library SomeLibrary {
function bothFunctions(ISomeInterface instance) internal returns (bool) {
return instance.someFunction() && instance.someOtherFunction();
}
}
12 changes: 12 additions & 0 deletions contracts/project/SomeOtherContract.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6;

import { ISomeInterface } from "./ISomeInterface.sol";
import { SomeLibrary } from "./SomeLibrary.sol";
import { ISomeContract, SomeContract } from "./SomeContract.sol";

contract SomeOtherContract is SomeContract {
function extraFunction() public returns (uint256) {
return 42;
}
}
3 changes: 3 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ interface Options {
publicInitializers: string[];
namespaced: boolean;
namespaceExclude: string[];
peerProject?: string;
}

function readCommandFlags(resolveRootRelative: (p: string) => string): Options {
Expand All @@ -36,12 +37,14 @@ function readCommandFlags(resolveRootRelative: (p: string) => string): Options {
W: skipWithInit = false,
n: namespaced = false,
N: namespaceExclude = [],
q: peerProject = false,
} = minimist(process.argv.slice(2));
return {
buildInfo,
deleteOriginals,
skipWithInit,
namespaced,
peerProject,
namespaceExclude: ensureArray(namespaceExclude).map(resolveRootRelative),
initializablePath: initializablePath && resolveRootRelative(initializablePath),
publicInitializers: ensureArray(publicInitializers).map(resolveRootRelative),
Expand Down
24 changes: 12 additions & 12 deletions src/index.test.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Generated by [AVA](https://avajs.dev).
fileName: 'ClassInheritance.sol',
path: 'contracts/solc-0.6/ClassInheritanceUpgradeable.sol',
source: `pragma solidity ^0.6.0;␊
import "../Initializable.sol";␊
import {Initializable} from "../Initializable.sol";␊
interface CICUpgradeable {␊
function fooBar(uint a) external;␊
Expand Down Expand Up @@ -89,7 +89,7 @@ Generated by [AVA](https://avajs.dev).
fileName: 'Override.sol',
path: 'contracts/solc-0.6/OverrideUpgradeable.sol',
source: `pragma solidity ^0.6.0;␊
import "../Initializable.sol";␊
import {Initializable} from "../Initializable.sol";␊
contract Parent1Upgradeable is Initializable {␊
function __Parent1_init() internal onlyInitializing {␊
Expand Down Expand Up @@ -158,7 +158,7 @@ Generated by [AVA](https://avajs.dev).
fileName: 'DiamondInheritance.sol',
path: 'contracts/solc-0.6/DiamondInheritanceUpgradeable.sol',
source: `pragma solidity ^0.6.0;␊
import "../Initializable.sol";␊
import {Initializable} from "../Initializable.sol";␊
contract DAUpgradeable is Initializable {␊
event Log(string);␊
Expand Down Expand Up @@ -254,7 +254,7 @@ Generated by [AVA](https://avajs.dev).
source: `pragma solidity ^0.6.0;␊
import "../../../ElementaryTypesUpgradeable.sol";␊
import "../../../../Initializable.sol";␊
import {Initializable} from "../../../../Initializable.sol";␊
contract DeepUpgradeable is Initializable {␊
function __Deep_init() internal onlyInitializing {␊
Expand All @@ -281,7 +281,7 @@ Generated by [AVA](https://avajs.dev).
fileName: 'ElementaryTypes.sol',
path: 'contracts/solc-0.6/ElementaryTypesUpgradeable.sol',
source: `pragma solidity ^0.6.0;␊
import "../Initializable.sol";␊
import {Initializable} from "../Initializable.sol";␊
contract ElementaryTypesUpgradeable is Initializable {␊
function __ElementaryTypes_init() internal onlyInitializing {␊
Expand Down Expand Up @@ -325,7 +325,7 @@ Generated by [AVA](https://avajs.dev).
fileName: 'ElementaryTypesWithConstructor.sol',
path: 'contracts/solc-0.6/ElementaryTypesWithConstructorUpgradeable.sol',
source: `pragma solidity ^0.6.0;␊
import "../Initializable.sol";␊
import {Initializable} from "../Initializable.sol";␊
contract ElementaryTypesWithConstructorUpgradeable is Initializable {␊
address public owner;␊
Expand Down Expand Up @@ -366,7 +366,7 @@ Generated by [AVA](https://avajs.dev).
fileName: 'Imported.sol',
path: 'contracts/solc-0.6/ImportedUpgradeable.sol',
source: `pragma solidity ^0.6.0;␊
import "../Initializable.sol";␊
import {Initializable} from "../Initializable.sol";␊
contract Imported1Upgradeable is Initializable {␊
function __Imported1_init(uint256 x, uint256 y) internal onlyInitializing {␊
Expand Down Expand Up @@ -409,7 +409,7 @@ Generated by [AVA](https://avajs.dev).
source: `pragma solidity ^0.6.0;␊
import "./ImportedUpgradeable.sol";␊
import "../Initializable.sol";␊
import {Initializable} from "../Initializable.sol";␊
contract LocalUpgradeable is Initializable, Imported2Upgradeable {␊
function __Local_init(uint x, uint y) internal onlyInitializing {␊
Expand Down Expand Up @@ -437,7 +437,7 @@ Generated by [AVA](https://avajs.dev).
fileName: 'SimpleInheritance.sol',
path: 'contracts/solc-0.6/SimpleInheritanceUpgradeable.sol',
source: `pragma solidity ^0.6.0;␊
import "../Initializable.sol";␊
import {Initializable} from "../Initializable.sol";␊
contract SIAUpgradeable is Initializable {␊
uint256 public foo;␊
Expand Down Expand Up @@ -509,7 +509,7 @@ Generated by [AVA](https://avajs.dev).
fileName: 'StringConstructor.sol',
path: 'contracts/solc-0.6/StringConstructorUpgradeable.sol',
source: `pragma solidity ^0.6.0;␊
import "../Initializable.sol";␊
import {Initializable} from "../Initializable.sol";␊
contract StringConstructorUpgradeable is Initializable {␊
Expand Down Expand Up @@ -557,7 +557,7 @@ Generated by [AVA](https://avajs.dev).
fileName: 'AbstractContract.sol',
path: 'contracts/solc-0.6/AbstractContractUpgradeable.sol',
source: `pragma solidity ^0.6.0;␊
import "../Initializable.sol";␊
import {Initializable} from "../Initializable.sol";␊
abstract contract AbstractContractUpgradeable is Initializable {␊
function __AbstractContract_init() internal onlyInitializing {␊
Expand Down Expand Up @@ -598,7 +598,7 @@ Generated by [AVA](https://avajs.dev).
fileName: 'Rename.sol',
path: 'contracts/solc-0.6/RenameUpgradeable.sol',
source: `pragma solidity ^0.6.0;␊
import "../Initializable.sol";␊
import {Initializable} from "../Initializable.sol";␊
library RenameLibraryUpgradeable {␊
function test() external {␊
Expand Down
Binary file modified src/index.test.ts.snap
Binary file not shown.
100 changes: 79 additions & 21 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ import fs from 'fs';
import { mapValues } from 'lodash';
import { minimatch } from 'minimatch';

import { findAll } from 'solidity-ast/utils';
import { Node } from 'solidity-ast/node';

import { matcher } from './utils/matcher';
import { renamePath, isRenamed } from './rename';
import { SolcOutput, SolcInput } from './solc/input-output';
import { Transform } from './transform';
import { Transform, TransformData } from './transform';
import { generateWithInit } from './generate-with-init';
import { findAlreadyInitializable } from './find-already-initializable';

Expand All @@ -15,6 +18,7 @@ import { renameIdentifiers } from './transformations/rename-identifiers';
import { prependInitializableBase } from './transformations/prepend-initializable-base';
import { removeStateVarInits } from './transformations/purge-var-inits';
import { removeImmutable } from './transformations/remove-immutable';
import { removePartial } from './transformations/remove-partial';
import { removeInheritanceListArguments } from './transformations/remove-inheritance-list-args';
import { renameContractDefinition } from './transformations/rename-contract-definition';
import { appendInitializableImport } from './transformations/append-initializable-import';
Expand Down Expand Up @@ -47,11 +51,14 @@ interface TranspileOptions {
skipWithInit?: boolean;
namespaced?: boolean;
namespaceExclude?: string[];
peerProject?: string;
}

type NodeTransformData = { node: Node; data: Partial<TransformData> };

function getExtraOutputPaths(
paths: Paths,
options?: TranspileOptions,
options: TranspileOptions = {},
): Record<'initializable' | 'withInit', string> {
const outputPaths = mapValues(
{
Expand All @@ -61,50 +68,98 @@ function getExtraOutputPaths(
s => path.relative(paths.root, path.join(paths.sources, s)),
);

if (options?.initializablePath) {
outputPaths.initializable = options?.initializablePath;
if (options.initializablePath) {
outputPaths.initializable = options.initializablePath;
}

return outputPaths;
}

function getExcludeAndImportPathsForPeer(
solcOutput: SolcOutput,
peerProject: string,
): [Set<string>, NodeTransformData[]] {
const data: NodeTransformData[] = [];
const exclude: Set<string> = new Set();

for (const [source, { ast }] of Object.entries(solcOutput.sources)) {
let shouldExclude = true;
for (const node of findAll('ContractDefinition', ast)) {
if (node.contractKind === 'contract') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In our spec we had written that we'd also exclude "contracts with @custom:need-not-transpile".

Do we want to do this? Do we need it for OZ Contracts?

If we do it, we will need a new helper similar to extractContractStorageSize in utils/natspec.ts.

Copy link
Contributor Author

@Amxx Amxx Sep 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need it for OZ Contracts?

Not really. Maybe that could replace some of the "initializable" specific logic, but its not something we absolutelly need.
If we do so, do we also add a @custom:force-transpile tag that does the opposit ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@custom:force-transpile is not what we need, I can't imagine a use case for it. I think the only contract where need-not-transpile could be relevant is UUPSUpgradeable. The current logic transpiles UUPSUpgradeable, though for this contract in particular we have a trick which is that we don't add the *Upgradeable suffix because the identifier already has it. Since it doesn't have any storage variables it won't get a namespace either. It does have initializers added.

Perhaps it's best to keep transpiling this contract and others simply for consistency, the initializers might be enough of a reason.

Copy link
Contributor Author

@Amxx Amxx Sep 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should "@Custom:need-not-transpile" apply to contracts or also to other objects (free function, structure definition, ...)

In the context of "peerImport", they would automatically be skiped, but we could imagine a project without "peerImport", where everything is transpiled by default, except for some @custom:need-not-transpile objects.

Also, what if they are used anywhere, and we don't have a peerImport path for them? How should we deal with that? Throw an error?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, what if they are used anywhere, and we don't have a peerImport path for them? How should we deal with that? Throw an error?

Yeah that's a good question.

If we don't need this for OZ Contracts, let's not do it.

shouldExclude = false;
} else {
const importFromPeer = path.join(peerProject, source);
data.push({ node, data: { importFromPeer } });
}
}
if (shouldExclude) {
exclude.add(source);
}
}

return [exclude, data];
}

export async function transpile(
solcInput: SolcInput,
solcOutput: SolcOutput,
paths: Paths,
options?: TranspileOptions,
options: TranspileOptions = {},
): Promise<OutputFile[]> {
const nodeData: NodeTransformData[] = [];

const outputPaths = getExtraOutputPaths(paths, options);
const alreadyInitializable = findAlreadyInitializable(solcOutput, options?.initializablePath);
const alreadyInitializable = findAlreadyInitializable(solcOutput, options.initializablePath);

const excludeSet = new Set([...alreadyInitializable, ...Object.values(outputPaths)]);
const excludeMatch = matcher(options?.exclude ?? []);
const softExcludeSet = new Set();
const excludeMatch = matcher(options.exclude ?? []);

const namespaceInclude = (source: string) => {
const namespaced = options?.namespaced ?? false;
const namespaceExclude = options?.namespaceExclude ?? [];
const namespaced = options.namespaced ?? false;
const namespaceExclude = options.namespaceExclude ?? [];
return namespaced && !namespaceExclude.some(p => minimatch(source, p));
};

// if partial transpilation, extract the list of soft exclude, and the peer import paths.
if (options.peerProject !== undefined) {
const [peerSoftExcludeSet, importFromPeerData] = getExcludeAndImportPathsForPeer(
solcOutput,
options.peerProject,
);
peerSoftExcludeSet.forEach(source => softExcludeSet.add(source));
nodeData.push(...importFromPeerData);
}

const transform = new Transform(solcInput, solcOutput, {
exclude: source => excludeSet.has(source) || (excludeMatch(source) ?? isRenamed(source)),
exclude: source =>
excludeSet.has(source) || (excludeMatch(source) ?? isRenamed(source))
? 'hard'
: softExcludeSet.has(source)
? 'soft'
: false,
});

for (const { node, data } of nodeData) {
Object.assign(transform.getData(node), data);
}

transform.apply(renameIdentifiers);
transform.apply(renameContractDefinition);
transform.apply(renameInheritdoc);
transform.apply(prependInitializableBase);
transform.apply(fixImportDirectives);
transform.apply(fixImportDirectives(!!options.peerProject));
transform.apply(appendInitializableImport(outputPaths.initializable));
transform.apply(fixNewStatement);
transform.apply(transformConstructor(namespaceInclude));
transform.apply(removeLeftoverConstructorHead);
transform.apply(addRequiredPublicInitializer(options?.publicInitializers));
transform.apply(addRequiredPublicInitializer(options.publicInitializers));
transform.apply(removeInheritanceListArguments);
transform.apply(removeStateVarInits);
transform.apply(removeImmutable);
transform.apply(removePartial);

if (options?.namespaced) {
if (options.namespaced) {
transform.apply(addNamespaceStruct(namespaceInclude));
} else {
transform.apply(addStorageGaps);
Expand All @@ -126,8 +181,11 @@ export async function transpile(
}

const initializableSource =
options?.initializablePath !== undefined
? transpileInitializable(solcInput, solcOutput, paths, options?.initializablePath)
options.initializablePath !== undefined
? transpileInitializable(solcInput, solcOutput, paths, {
...options,
initializablePath: options.initializablePath,
})
: fs.readFileSync(require.resolve('../Initializable.sol'), 'utf8');

outputFiles.push({
Expand All @@ -136,9 +194,9 @@ export async function transpile(
fileName: path.basename(outputPaths.initializable),
});

if (!options?.skipWithInit) {
if (!options.skipWithInit) {
outputFiles.push({
source: generateWithInit(transform, outputPaths.withInit, options?.solcVersion),
source: generateWithInit(transform, outputPaths.withInit, options.solcVersion),
path: outputPaths.withInit,
fileName: path.basename(outputPaths.withInit),
});
Expand All @@ -151,16 +209,16 @@ function transpileInitializable(
solcInput: SolcInput,
solcOutput: SolcOutput,
paths: Paths,
initializablePath: string,
options: TranspileOptions & Required<Pick<TranspileOptions, 'initializablePath'>>,
): string {
const transform = new Transform(solcInput, solcOutput);

transform.apply(function* (ast, tools) {
if (ast.absolutePath === initializablePath) {
if (ast.absolutePath === options.initializablePath) {
yield* renameIdentifiers(ast, tools);
yield* fixImportDirectives(ast, tools);
yield* fixImportDirectives(!!options.peerProject)(ast, tools);
}
});

return transform.results()[initializablePath];
return transform.results()[options.initializablePath];
}
Loading