Skip to content

Commit 6154658

Browse files
committed
Merge imports library comments from OpenZeppelin#628
1 parent c41dc8b commit 6154658

File tree

8 files changed

+146
-42
lines changed

8 files changed

+146
-42
lines changed

packages/core/solidity/src/contract.ts

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export interface Contract {
55
license: string;
66
parents: Parent[];
77
natspecTags: NatspecTag[];
8+
libraries: Library[];
89
imports: ImportContract[];
910
functions: ContractFunction[];
1011
constructorCode: string[];
@@ -30,9 +31,9 @@ export interface ReferencedContract {
3031
transpiled?: boolean;
3132
}
3233

33-
export interface Using {
34+
export interface Library {
3435
library: ImportContract;
35-
usingFor: string;
36+
usingFor: Set<string>;
3637
}
3738

3839
export interface BaseFunction {
@@ -52,7 +53,7 @@ export interface ContractFunction extends BaseFunction {
5253
comments: string[];
5354
}
5455

55-
export type FunctionKind = 'internal' | 'public';
56+
export type FunctionKind = 'private' | 'internal' | 'public' | 'external';
5657
export type FunctionMutability = (typeof mutabilityRank)[number];
5758

5859
// Order is important
@@ -77,15 +78,15 @@ export class ContractBuilder implements Contract {
7778
license: string = 'MIT';
7879
upgradeable = false;
7980

80-
readonly using: Using[] = [];
8181
readonly natspecTags: NatspecTag[] = [];
8282

8383
readonly constructorArgs: FunctionArgument[] = [];
8484
readonly constructorCode: string[] = [];
8585
readonly variableSet: Set<string> = new Set();
8686

8787
private parentMap: Map<string, Parent> = new Map<string, Parent>();
88-
private functionMap: Map<string, ContractFunction> = new Map();
88+
private functionMap: Map<string, ContractFunction> = new Map<string, ContractFunction>();
89+
private libraryMap: Map<string, Library> = new Map<string, Library>();
8990

9091
constructor(name: string) {
9192
this.name = toIdentifier(name, true);
@@ -106,7 +107,13 @@ export class ContractBuilder implements Contract {
106107
}
107108

108109
get imports(): ImportContract[] {
109-
return [...[...this.parentMap.values()].map(p => p.contract), ...this.using.map(u => u.library)];
110+
const parents = [...this.parentMap.values()].map(p => p.contract);
111+
const libraries = [...this.libraryMap.values()].map(l => l.library);
112+
return [...parents, ...libraries];
113+
}
114+
115+
get libraries(): Library[] {
116+
return [...this.libraryMap.values()];
110117
}
111118

112119
get functions(): ContractFunction[] {
@@ -133,6 +140,21 @@ export class ContractBuilder implements Contract {
133140
return !present;
134141
}
135142

143+
addLibrary(library: ImportContract, usingFor: string[]): boolean {
144+
let modified = false;
145+
if (this.libraryMap.has(library.name)) {
146+
const existing = this.libraryMap.get(library.name)!;
147+
const initialSize = existing.usingFor.size;
148+
usingFor.forEach(type => existing.usingFor.add(type));
149+
modified = existing.usingFor.size > initialSize;
150+
} else {
151+
this.libraryMap.set(library.name, { library, usingFor: new Set(usingFor) });
152+
modified = true;
153+
}
154+
155+
return modified;
156+
}
157+
136158
addOverride(parent: ReferencedContract, baseFn: BaseFunction, mutability?: FunctionMutability) {
137159
const fn = this.addFunction(baseFn);
138160
fn.override.add(parent);
@@ -219,4 +241,4 @@ export class ContractBuilder implements Contract {
219241
this.variableSet.add(code);
220242
return !present;
221243
}
222-
}
244+
}

packages/core/solidity/src/options.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ const upgradeableImport = (p: ImportContract): ImportContract => {
2727

2828
export interface Options {
2929
transformImport?: (parent: ImportContract) => ImportContract;
30+
/**
31+
* Add additional libraries to the compatibility banner printed at the top of the contract.
32+
*/
33+
additionalCompatibleLibraries?: { name: string; path: string; version: string }[];
3034
}
3135

3236
export interface Helpers extends Required<Options> {
@@ -45,5 +49,6 @@ export function withHelpers(contract: Contract, opts: Options = {}): Helpers {
4549
const p2 = contractUpgradeable && inferTranspiled(p1) ? upgradeableImport(p1) : p1;
4650
return opts.transformImport?.(p2) ?? p2;
4751
},
52+
additionalCompatibleLibraries: opts.additionalCompatibleLibraries ?? [],
4853
};
49-
}
54+
}

packages/core/solidity/src/print.ts

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import SOLIDITY_VERSION from './solidity-version.json';
1717
import { inferTranspiled } from './infer-transpiled';
1818
import { compatibleContractsSemver } from './utils/version';
1919
import { stringifyUnicodeSafe } from './utils/sanitize';
20-
import { importsCommunityContracts } from './utils/imports-libraries';
20+
import { importsLibrary } from './utils/imports-libraries';
2121
import { getCommunityContractsGitCommit } from './utils/community-contracts-git-commit';
2222

2323
export function printContract(contract: Contract, opts?: Options): string {
@@ -31,7 +31,7 @@ export function printContract(contract: Contract, opts?: Options): string {
3131
...spaceBetween(
3232
[
3333
`// SPDX-License-Identifier: ${contract.license}`,
34-
printCompatibleLibraryVersions(contract),
34+
printCompatibleLibraryVersions(contract, opts),
3535
`pragma solidity ^${SOLIDITY_VERSION};`,
3636
],
3737

@@ -42,6 +42,7 @@ export function printContract(contract: Contract, opts?: Options): string {
4242
[`contract ${contract.name}`, ...printInheritance(contract, helpers), '{'].join(' '),
4343

4444
spaceBetween(
45+
printLibraries(contract, helpers),
4546
contract.variables,
4647
printConstructor(contract, helpers),
4748
...fns.code,
@@ -56,17 +57,30 @@ export function printContract(contract: Contract, opts?: Options): string {
5657
);
5758
}
5859

59-
function printCompatibleLibraryVersions(contract: Contract): string {
60-
let result = `// Compatible with OpenZeppelin Contracts ${compatibleContractsSemver}`;
61-
if (importsCommunityContracts(contract)) {
60+
function printCompatibleLibraryVersions(contract: Contract, opts?: Options): string {
61+
const libraries: string[] = [];
62+
if (importsLibrary(contract, '@openzeppelin/contracts')) {
63+
libraries.push(`OpenZeppelin Contracts ${compatibleContractsSemver}`);
64+
}
65+
if (importsLibrary(contract, '@openzeppelin/community-contracts')) {
6266
try {
6367
const commit = getCommunityContractsGitCommit();
64-
result += ` and Community Contracts commit ${commit}`;
68+
libraries.push(`Community Contracts commit ${commit}`);
6569
} catch (e) {
6670
console.error(e);
6771
}
6872
}
69-
return result;
73+
if (opts?.additionalCompatibleLibraries) {
74+
for (const library of opts.additionalCompatibleLibraries) {
75+
if (importsLibrary(contract, library.path)) {
76+
libraries.push(`${library.name} ${library.version}`);
77+
}
78+
}
79+
}
80+
81+
if (libraries.length === 0) return '';
82+
if (libraries.length === 1) return `// Compatible with ${libraries[0]}`;
83+
return `// Compatible with ${libraries.slice(0, -1).join(', ')} and ${libraries.slice(-1)}`;
7084
}
7185

7286
function printInheritance(contract: Contract, { transformName }: Helpers): [] | [string] {
@@ -87,9 +101,9 @@ function printConstructor(contract: Contract, helpers: Helpers): Lines[] {
87101
const args = contract.constructorArgs.map(a => printArgument(a, helpers));
88102
const body = helpers.upgradeable
89103
? spaceBetween(
90-
parents.map(p => p + ';'),
91-
contract.constructorCode,
92-
)
104+
parents.map(p => p + ';'),
105+
contract.constructorCode,
106+
)
93107
: contract.constructorCode;
94108
const head = helpers.upgradeable ? 'function initialize' : 'constructor';
95109
const constructor = printFunction2([], head, args, modifiers, body);
@@ -275,3 +289,14 @@ function printImports(imports: ImportContract[], helpers: Helpers): string[] {
275289

276290
return lines;
277291
}
292+
293+
function printLibraries(contract: Contract, { transformName }: Helpers): string[] {
294+
if (!contract.libraries || contract.libraries.length === 0) return [];
295+
296+
return contract.libraries
297+
.sort((a, b) => a.library.name.localeCompare(b.library.name)) // Sort by library name
298+
.map(lib => {
299+
const sortedTypes = Array.from(lib.usingFor).sort((a, b) => a.localeCompare(b)); // Sort types
300+
return `using ${transformName(lib.library)} for ${sortedTypes.join(', ')};`;
301+
});
302+
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Contract } from '../contract';
22

3-
export function importsCommunityContracts(contract: Contract) {
4-
return contract.imports.some(i => i.path.startsWith('@openzeppelin/community-contracts/'));
5-
}
3+
export function importsLibrary(contract: Contract, library: string) {
4+
return contract.imports.some(i => i.path.startsWith(library));
5+
}

packages/core/zama/src/contract.ts

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export interface Contract {
55
license: string;
66
parents: Parent[];
77
natspecTags: NatspecTag[];
8+
libraries: Library[];
89
imports: ImportContract[];
910
functions: ContractFunction[];
1011
constructorCode: string[];
@@ -30,9 +31,9 @@ export interface ReferencedContract {
3031
transpiled?: boolean;
3132
}
3233

33-
export interface Using {
34+
export interface Library {
3435
library: ImportContract;
35-
usingFor: string;
36+
usingFor: Set<string>;
3637
}
3738

3839
export interface BaseFunction {
@@ -52,7 +53,7 @@ export interface ContractFunction extends BaseFunction {
5253
comments: string[];
5354
}
5455

55-
export type FunctionKind = 'internal' | 'public';
56+
export type FunctionKind = 'private' | 'internal' | 'public' | 'external';
5657
export type FunctionMutability = (typeof mutabilityRank)[number];
5758

5859
// Order is important
@@ -77,15 +78,15 @@ export class ContractBuilder implements Contract {
7778
license: string = 'MIT';
7879
upgradeable = false;
7980

80-
readonly using: Using[] = [];
8181
readonly natspecTags: NatspecTag[] = [];
8282

8383
readonly constructorArgs: FunctionArgument[] = [];
8484
readonly constructorCode: string[] = [];
8585
readonly variableSet: Set<string> = new Set();
8686

8787
private parentMap: Map<string, Parent> = new Map<string, Parent>();
88-
private functionMap: Map<string, ContractFunction> = new Map();
88+
private functionMap: Map<string, ContractFunction> = new Map<string, ContractFunction>();
89+
private libraryMap: Map<string, Library> = new Map<string, Library>();
8990

9091
constructor(name: string) {
9192
this.name = toIdentifier(name, true);
@@ -106,7 +107,13 @@ export class ContractBuilder implements Contract {
106107
}
107108

108109
get imports(): ImportContract[] {
109-
return [...[...this.parentMap.values()].map(p => p.contract), ...this.using.map(u => u.library)];
110+
const parents = [...this.parentMap.values()].map(p => p.contract);
111+
const libraries = [...this.libraryMap.values()].map(l => l.library);
112+
return [...parents, ...libraries];
113+
}
114+
115+
get libraries(): Library[] {
116+
return [...this.libraryMap.values()];
110117
}
111118

112119
get functions(): ContractFunction[] {
@@ -133,6 +140,21 @@ export class ContractBuilder implements Contract {
133140
return !present;
134141
}
135142

143+
addLibrary(library: ImportContract, usingFor: string[]): boolean {
144+
let modified = false;
145+
if (this.libraryMap.has(library.name)) {
146+
const existing = this.libraryMap.get(library.name)!;
147+
const initialSize = existing.usingFor.size;
148+
usingFor.forEach(type => existing.usingFor.add(type));
149+
modified = existing.usingFor.size > initialSize;
150+
} else {
151+
this.libraryMap.set(library.name, { library, usingFor: new Set(usingFor) });
152+
modified = true;
153+
}
154+
155+
return modified;
156+
}
157+
136158
addOverride(parent: ReferencedContract, baseFn: BaseFunction, mutability?: FunctionMutability) {
137159
const fn = this.addFunction(baseFn);
138160
fn.override.add(parent);
@@ -219,4 +241,4 @@ export class ContractBuilder implements Contract {
219241
this.variableSet.add(code);
220242
return !present;
221243
}
222-
}
244+
}

packages/core/zama/src/options.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ const upgradeableImport = (p: ImportContract): ImportContract => {
2727

2828
export interface Options {
2929
transformImport?: (parent: ImportContract) => ImportContract;
30+
/**
31+
* Add additional libraries to the compatibility banner printed at the top of the contract.
32+
*/
33+
additionalCompatibleLibraries?: { name: string; path: string; version: string }[];
3034
}
3135

3236
export interface Helpers extends Required<Options> {
@@ -45,5 +49,6 @@ export function withHelpers(contract: Contract, opts: Options = {}): Helpers {
4549
const p2 = contractUpgradeable && inferTranspiled(p1) ? upgradeableImport(p1) : p1;
4650
return opts.transformImport?.(p2) ?? p2;
4751
},
52+
additionalCompatibleLibraries: opts.additionalCompatibleLibraries ?? [],
4853
};
49-
}
54+
}

0 commit comments

Comments
 (0)